Example #1
0
        /// <summary>
        /// Find all of the properties for this control, via reflection.
        /// </summary>
        /// <returns>A dictionary of properties for the given control type.</returns>
        private static Dictionary <string, ReflectedControlProperty> CollectControlProperties(ICompileContext compileContext, Type controlType, ReflectedControl reflectedControl)
        {
            Dictionary <string, ReflectedControlProperty> controlProperties = new Dictionary <string, ReflectedControlProperty>();

            // We have to include NonPublic properties, since internal, public, or protected properties can all be
            // legally referenced from the markup.
            PropertyInfo[] propertyInfos = controlType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                // Find out what kind of getters and setter this property has.  If it has none, or both of them are private, we can't work with it.
                MethodInfo setMethodInfo      = propertyInfo.GetSetMethod(true);
                MethodInfo getMethodInfo      = propertyInfo.GetGetMethod(true);
                bool       hasUsableSetMethod = (setMethodInfo != null && (setMethodInfo.IsPublic || setMethodInfo.IsFamilyOrAssembly));
                bool       hasUsableGetMethod = (getMethodInfo != null && (getMethodInfo.IsPublic || getMethodInfo.IsFamilyOrAssembly));
                if (!hasUsableSetMethod && !hasUsableGetMethod)
                {
                    continue;
                }

                // We have a public-ish setter.  So add a ReflectedControlProperty instance for this property,
                // since it could be accessible from markup.
                ReflectedControlProperty reflectedControlProperty = new ReflectedControlProperty(reflectedControl, propertyInfo);

                // Add it to the set of known properties for this control.  We don't have the ability to support
                // case differentiation on property names, and ASP.NET will bork if anybody tries.  So if you have
                // multiple properties with the same name that differ only by case, that's just bad mojo, and you
                // should change your controls so that isn't true anymore.
                string lowerName = propertyInfo.Name.ToLower();
                if (controlProperties.ContainsKey(lowerName))
                {
                    ReflectedControlProperty previousProperty  = controlProperties[lowerName];
                    Type         previousPropertyDeclaringType = previousProperty.PropertyInfo.DeclaringType;
                    PropertyInfo baseProperty = previousPropertyDeclaringType.BaseType.GetProperty(lowerName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
                    if (baseProperty == null)
                    {
                        compileContext.Warning(string.Format("The server control \"{0}\" contains multiple properties named \"{1}\".  Keeping the {2} declaration and discarding the {3} declaration.",
                                                             controlType.FullName, propertyInfo.Name, controlProperties[lowerName].PropertyInfo.PropertyType.FullName, propertyInfo.PropertyType.FullName));
                    }
                    else
                    {
                        // This appears to be a case of a child class's property shadowing a parent class's property.
                        // That's a safe thing, or should be, so we don't throw out a warning when it happens.
                        compileContext.Verbose(string.Format("The server control \"{0}\" contains multiple properties named \"{1}\" (but one comes from a parent class, so the 'new' keyword was probably involved, and this should be safe).  Keeping the {2} declaration and discarding the {3} declaration.",
                                                             controlType.FullName, propertyInfo.Name, controlProperties[lowerName].PropertyInfo.PropertyType.FullName, propertyInfo.PropertyType.FullName));
                    }
                    continue;
                }
                controlProperties.Add(lowerName, reflectedControlProperty);
            }

            return(controlProperties);
        }
Example #2
0
        /// <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));
                    }
                }
            }
        }