/// <summary>
        /// Construct a new blob of metadata for a single control, performing any reflection needed to determine its
        /// structure and parsing behavior.
        /// </summary>
        /// <param name="compileContext">The context in which errors should be reported.</param>
        /// <param name="tag">The complete tag for this control, as found in the markup.</param>
        /// <param name="tagRegistrations">The known list of registrations, formed from the directives in the
        /// "web.config" and any &lt;%@ Register %&gt; directives in the markup.</param>
        /// <param name="assemblies">The complete list of known pre-loaded assemblies for reflection.</param>
        /// <param name="allowedTypes">The allowable types of control that may be returned.  If the matching
        /// .NET class type for this control does not match one of these types (or is not derivable from one
        /// of these types), this constructor will throw an exception.</param>
        public ReflectedControl(ICompileContext compileContext, Tag tag, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, IEnumerable <Type> allowedTypes)
        {
            // Decode the tag declaration.
            DecodeFullTagNameWithPrefix(tag.TagName, out TagPrefix, out TagName);

            // Find the matching C# type for that tag declaration.
            if (string.IsNullOrEmpty(TagPrefix))
            {
                TagRegistration = _htmlTagRegistration;
                ControlType     = FindMatchingHtmlControlType(tag);
            }
            else
            {
                ControlType = FindMatchingControlType(TagPrefix, TagName, tagRegistrations, assemblies, out TagRegistration);
            }
            if (ControlType == null)
            {
                throw new InvalidOperationException(string.Format("No matching type for <{0}> was found in any of the loaded assemblies.", tag.TagName));
            }

            // If we are restricted to only load certain types (such as the nested not-a-control instances inside a DataPager control),
            // check that the control we have found matches one of those types.
            if (!allowedTypes.Any(t => t.IsAssignableFrom(ControlType)))
            {
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.AppendFormat("Found matching type for <{0}>, but it is a {1}, not one of the {2} allowed types:\r\n",
                                           tag.TagName, ControlType.FullName, allowedTypes.Count());
                foreach (Type allowedType in allowedTypes)
                {
                    stringBuilder.AppendFormat("- {0}\r\n", allowedType.FullName);
                }
                throw new InvalidOperationException(stringBuilder.ToString());
            }

            // Extract the [ParseChildren] attribute, if it has one.
            System.Web.UI.ParseChildrenAttribute[] parseChildrenAttributes = (System.Web.UI.ParseChildrenAttribute[])ControlType.GetCustomAttributes(typeof(System.Web.UI.ParseChildrenAttribute), true);
            ParseChildrenAttribute = parseChildrenAttributes.Length == 0 ? null : parseChildrenAttributes[0];

            // Extract the [ControlBuilder] attribute, if it has one.
            System.Web.UI.ControlBuilderAttribute[] controlBuilderAttributes = (System.Web.UI.ControlBuilderAttribute[])ControlType.GetCustomAttributes(typeof(System.Web.UI.ControlBuilderAttribute), true);
            ControlBuilderAttribute = controlBuilderAttributes.Length == 0 ? null : controlBuilderAttributes[0];

            // Extract the type's properties, since their declarations control what's legal in the markup.
            ControlProperties = CollectControlProperties(compileContext, ControlType, this);

            // HtmlControls have to be handled specially, since they have [ParseChildren(true)] in many cases but
            // aren't really using it for anything.
            if (ControlType.Namespace == _htmlTagRegistration.Namespace)
            {
                ParseChildrenAttribute = new ParseChildrenAttribute(false);
            }

            // Validate the ParseChildrenAttribute, which may be broken or weird.
            if (ParseChildrenAttribute != null)
            {
                if (!string.IsNullOrEmpty(ParseChildrenAttribute.DefaultProperty))
                {
                    string propertyName = ParseChildrenAttribute.DefaultProperty.ToLower();                             // ASP.NET also ignores case on this; see internals of ControlBuilder.CreateChildBuilder() for details.
                    if (!ControlProperties.ContainsKey(propertyName))
                    {
                        throw new InvalidOperationException(string.Format("The [ParseChildren] attribute on class \"{0}\" names a default property \"{1}\" that does not exist in that class.",
                                                                          ControlType.FullName, ParseChildrenAttribute.DefaultProperty));
                    }

                    DefaultCollectionProperty = ControlProperties[propertyName];
                    if (!DefaultCollectionProperty.IsCollectionProperty)
                    {
                        throw new InvalidOperationException(string.Format("The [ParseChildren] attribute on class \"{0}\" names a default property \"{1}\" that is not a collection property.  The default property must always be a collection property.",
                                                                          ControlType.FullName, ParseChildrenAttribute.DefaultProperty));
                    }
                }
            }
        }
        /// <summary>
        /// Search through the given set of tag registrations and assemblies to find a suitable .NET class type that
        /// matches the given tag prefix and tag name.  (This method handles server controls and user controls, not HTML
        /// controls; the tag prefix must not be empty.)
        /// </summary>
        /// <param name="tagPrefix">The tag prefix of the tag we're searching for.</param>
        /// <param name="tagName">The tag name of the tag we're searching for.</param>
        /// <param name="tagRegistrations">The known list of registrations, formed from the directives in the
        /// "web.config" and any &lt;%@ Register %&gt; directives in the markup.</param>
        /// <param name="assemblies">The complete list of known pre-loaded assemblies for reflection.</param>
        /// <param name="matchingTagRegistration">The matching tag registration for this tag prefix/name, if there is one.</param>
        /// <returns>The matching class type, if one exists, or null if there is no matching class type.</returns>
        private static Type FindMatchingControlType(string tagPrefix, string tagName, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, out TagRegistration matchingTagRegistration)
        {
            // Since multiple tag registrations could match this declaration, we have no choice but to walk through
            // all of the tag registrations and just try to find the first one that matches.
            foreach (TagRegistration tagRegistration in tagRegistrations)
            {
                // Try the prefix first.  If that doesn't match, it's definitely not what we're looking for.
                if (string.Compare(tagRegistration.TagPrefix, tagPrefix, StringComparison.InvariantCultureIgnoreCase) != 0)
                {
                    continue;
                }

                switch (tagRegistration.Kind)
                {
                case TagRegistrationKind.SingleUserControl:
                    if (string.Compare(tagRegistration.TagName, tagName, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        // We matched a registered user control, so we have to partially parse it to find out
                        // what namespace in the website assembly is associated with it.
                        Assembly assembly = assemblies.PrimaryAssembly;
                        Type     type     = assembly.GetType(tagRegistration.Typename);

                        // Make sure we found it, and that it's actually a UserControl of some kind.
                        if (type == null || !typeof(System.Web.UI.UserControl).IsAssignableFrom(type))
                        {
                            break;
                        }

                        // We found it, so return it.
                        matchingTagRegistration = tagRegistration;
                        return(type);
                    }
                    break;

                case TagRegistrationKind.Namespace:
                {
                    // This registration describes an entire namespace worth of controls in an assembly somewhere.
                    // So check see if tagName matches a class inside the given namespace in that assembly.
                    Assembly assembly = assemblies[tagRegistration.AssemblyFilename];
                    Type     type     = assembly.GetType(tagRegistration.Namespace + "." + tagName, false, true);

                    // Make sure we found it.
                    if (type == null)
                    {
                        break;
                    }

                    // We found it, so return it.
                    matchingTagRegistration = tagRegistration;
                    return(type);
                }

                case TagRegistrationKind.HtmlControl:
                    // Shouldn't be able to get here.
                    throw new InvalidOperationException("Internal error; got an HtmlControl when attempting to process a registered user control or namespace.");
                }
            }

            matchingTagRegistration = null;
            return(null);
        }
 /// <summary>
 /// Construct a new control collection that will house all controls defined by the given set of
 /// registrations and assemblies.
 /// </summary>
 /// <param name="tagRegistrations">The tag registrations (from either the "web.config" file or
 /// from &lt;%@ Register %%gt; directives in the markup).</param>
 /// <param name="assemblies">The set of known assemblies, preloaded.</param>
 public ReflectedControlCollection(IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies)
 {
     _tagRegistrations = tagRegistrations;
     _assemblies       = assemblies;
 }
Exemple #4
0
        /// <summary>
        /// For the given set of .aspx or .ascx files, generate all of their designer files.
        /// </summary>
        /// <param name="compileContext">The context in which errors are to be reported.</param>
        /// <param name="filenames">The filenames to generate.</param>
        /// <param name="rootPath">The root disk path of the website (usually the same as the path to "web.config").</param>
        /// <param name="websiteDllFileName">The disk path to the website's DLL.</param>
        public static bool GenerateDesignerFiles(ICompileContext compileContext, IEnumerable <string> filenames, string rootPath, string websiteDllFileName)
        {
            int filenameCount = filenames.Count();

            compileContext.BeginTask(filenameCount);

            // Load and parse the "web.config".
            WebConfigReader webConfigReader = new WebConfigReader();

            try
            {
                webConfigReader.LoadWebConfig(compileContext, Path.Combine(rootPath, WebConfigFilename), rootPath);
            }
            catch (Exception e)
            {
                compileContext.Error("Cannot load {0}:\r\n{1}", Path.Combine(rootPath, WebConfigFilename), e.Message);
                return(false);
            }

            // Load any assemblies we know we'll need.  This includes the default assemblies, any declared
            // in the web.config, and, of course, the website's DLL itself.
            AssemblyLoader assemblyLoader = new AssemblyLoader();
            List <string>  assemblyNames  = new List <string>();

            assemblyNames.AddRange(_standardTagRegistrations.Where(r => !string.IsNullOrEmpty(r.AssemblyFilename)).Select(r => r.AssemblyFilename).Distinct());
            assemblyNames.AddRange(webConfigReader.TagRegistrations.Where(r => !string.IsNullOrEmpty(r.AssemblyFilename)).Select(r => r.AssemblyFilename).Distinct());
            string dllFullPath = Path.GetFullPath(websiteDllFileName);

            assemblyNames.Add(dllFullPath);
            string assemblyDirectory = Path.GetDirectoryName(dllFullPath);

            assemblyLoader.PreloadAssemblies(compileContext, assemblyNames, assemblyDirectory);
            assemblyLoader.PrimaryAssembly = assemblyLoader[dllFullPath];

            // Add the default tag registrations, including those from System.Web and any declared in the "web.config".
            List <TagRegistration> tagRegistrations = new List <TagRegistration>();

            tagRegistrations.AddRange(_standardTagRegistrations);
            tagRegistrations.AddRange(webConfigReader.TagRegistrations);

            // Spin through any user controls that were declared in the web.config and connect them to their actual
            // .NET class types via reflection.
            compileContext.Verbose("Resolving user controls declared in the web.config.");
            compileContext.VerboseNesting++;
            ResolveUserControls(compileContext, tagRegistrations, assemblyLoader, assemblyDirectory, rootPath, rootPath);
            compileContext.VerboseNesting--;
            compileContext.Verbose(string.Empty);

            // Now that all the setup is done, load and parse each individual markup file into its own .designer.cs output file.
            bool result = true;

            foreach (string filename in filenames)
            {
                compileContext.Verbose("Begin processing \"{0}\"...", filename);
                compileContext.Verbose("");
                compileContext.VerboseNesting++;

                compileContext.BeginFile(filename);
                bool succeeded = GenerateDesignerForFilename(compileContext, filename, tagRegistrations, assemblyLoader, assemblyDirectory, rootPath);
                result &= succeeded;
                compileContext.EndFile(filename, succeeded);

                compileContext.VerboseNesting--;
                compileContext.Verbose("");
                compileContext.Verbose("End processing \"{0}\".", filename);
            }
            return(result);
        }
Exemple #5
0
        /// <summary>
        /// Given a set of tag registrations for user controls, attempt to connect those tag registrations to actual
        /// .NET class types in the main website assembly.  This will update the Typename field for each TagRegistration
        /// where a matching class type is found; or if no matching class type is found, this will throw an exception.
        /// </summary>
        /// <param name="compileContext">The context in which errors should be reported.</param>
        /// <param name="tagRegistrations">The set of user-control registrations to resolve to real class types.</param>
        /// <param name="assemblies">The full set of preloaded assemblies.</param>
        /// <param name="assemblyDirectory">The directory where the main website DLL can be found.</param>
        /// <param name="rootPath">The real disk path to the root of the website's virtual directory.</param>
        /// <param name="currentDirectory">The current directory (for resolving relative paths).</param>
        public static void ResolveUserControls(ICompileContext compileContext, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, string assemblyDirectory, string rootPath, string currentDirectory)
        {
            foreach (TagRegistration tagRegistration in tagRegistrations.Where(t => t.Kind == TagRegistrationKind.SingleUserControl))
            {
                compileContext.Verbose("Registering user control <{0}:{1}> as \"{2}\".", tagRegistration.TagPrefix, tagRegistration.TagName, tagRegistration.SourceFilename);

                compileContext.VerboseNesting++;

                string filename = ResolveWebsitePath(compileContext, tagRegistration.SourceFilename, rootPath, currentDirectory);

                MarkupReader userControlReader        = new MarkupReader();
                Tag          userControlMainDirective = userControlReader.ReadMainDirective(compileContext, filename, assemblies, assemblyDirectory, rootPath);

                if (string.IsNullOrEmpty(userControlMainDirective.TagName) &&
                    string.Compare(userControlMainDirective.TagName, "control", StringComparison.InvariantCultureIgnoreCase) != 0)
                {
                    throw new RedesignerException("Cannot register user control \"{0}\":  Its main <% ... %> directive does not start with the \"Control\" keyword.  Is this actually a user control?", tagRegistration.SourceFilename);
                }

                string inheritsAttribute = userControlMainDirective["inherits"];
                if (string.IsNullOrEmpty(inheritsAttribute))
                {
                    throw new RedesignerException("Cannot register user control \"{0}\":  Its main <% Control ... %> directive is missing the required Inherits=\"...\" attribute.", tagRegistration.SourceFilename);
                }

                tagRegistration.Typename = inheritsAttribute;

                compileContext.Verbose("User control registered as type \"{0}\".", inheritsAttribute);
                compileContext.VerboseNesting--;
            }
        }
Exemple #6
0
        /// <summary>
        /// Verify the current .designer.cs file for the given markup file.
        /// </summary>
        /// <returns>True if the file passes inspection, false if it fails.</returns>
        public static bool VerifyDesignerForFilename(ICompileContext compileContext, string filename, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, string assemblyDirectory, string rootPath)
        {
            DesignerInfo designerInfo;
            string       designerFilename = filename + ".designer.cs";

            // Load the markup from the .aspx or .ascx file.
            MarkupReader markup = new MarkupReader();
            MarkupInfo   markupInfo;

            try
            {
                markupInfo = markup.LoadMarkup(compileContext, filename, tagRegistrations, assemblies, assemblyDirectory, rootPath);
            }
            catch (Exception e)
            {
                compileContext.Error("{0}: Failed to load markup file:\r\n{1}", filename, e.Message);
                compileContext.Verbose("Stopping file processing due to exception.  Stack trace:\r\n{0}", e.StackTrace);
                return(false);
            }

            if (markupInfo.ClassType == null)
            {
                compileContext.Verbose("Skipping verification of .designer file, because markup has no Inherits=\"...\" attribute and therefore has no .designer file.", filename);
                return(true);
            }

            compileContext.Verbose(string.Empty);

            // Read and parse the current .designer.cs file.
            try
            {
                DesignerReader designerReader = new DesignerReader();
                designerInfo = designerReader.LoadDesignerFile(compileContext, designerFilename);
            }
            catch (Exception e)
            {
                compileContext.Error("{0}: Cannot load designer file:\r\n{1}", filename, e.Message);
                compileContext.Verbose("Stopping file processing due to exception.  Stack trace:\r\n{0}", e.StackTrace);
                return(false);
            }

            compileContext.Verbose(string.Empty);

            // And finally compare the expectations of the markup against the reality of the .designer.cs file.
            return(CompareMarkupInfoToDesignerInfo(compileContext, filename, markupInfo, designerInfo));
        }
Exemple #7
0
        /// <summary>
        /// Generate a replacement .designer.cs file for the given markup file, overwriting the existing
        /// .designer.cs file if there is one.
        /// </summary>
        public static bool GenerateDesignerForFilename(ICompileContext compileContext, string filename, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, string assemblyDirectory, string rootPath)
        {
            string designer;
            string designerFilename = filename + ".designer.cs";

            // Load the markup from the .aspx or .ascx file.
            MarkupReader markup = new MarkupReader();
            MarkupInfo   markupInfo;

            try
            {
                markupInfo = markup.LoadMarkup(compileContext, filename, tagRegistrations, assemblies, assemblyDirectory, rootPath);
            }
            catch (Exception e)
            {
                compileContext.Error("{0}: Failed to load markup file:\r\n{1}", filename, e.Message);
                compileContext.Verbose("Stopping file processing due to exception.  Stack trace:\r\n{0}", e.StackTrace);
                return(false);
            }

            // If we're not inheriting a real class, there's no reason for a designer file to exist.
            if (markupInfo.ClassType == null)
            {
                compileContext.Verbose("Skipping generating designer file because markup does not have an Inherits=\"...\" attribute.", filename);
                return(true);
            }

            // Generate the output text for the new .designer.cs file.
            try
            {
                DesignerWriter designerWriter = new DesignerWriter();
                designer = designerWriter.CreateDesigner(compileContext, markupInfo);
            }
            catch (Exception e)
            {
                compileContext.Error("{0}: Cannot regenerate designer file:\r\n{1}", filename, e.Message);
                compileContext.Verbose("Stopping file processing due to exception.  Stack trace:\r\n{0}", e.StackTrace);
                return(false);
            }

            // Save the output .designer.cs file to disk.
            try
            {
                File.WriteAllText(designerFilename, designer, Encoding.UTF8);
            }
            catch (Exception e)
            {
                compileContext.Error("{0}: Cannot open designer file for writing:\r\n{1}", designerFilename, e.Message);
                compileContext.Verbose("Stopping file processing due to exception.  Stack trace:\r\n{0}", e.StackTrace);
                return(false);
            }

            return(true);
        }