///// <summary> ///// Given a tag with runat="server" on an HTML element, find a suitable HTML server control for it. ///// </summary> ///// <param name="tag">The tag to find an HTML server control for.</param> ///// <returns>The matching HTML server control.</returns> //private static Type FindMatchingHtmlControlType(Tag tag) //{ // switch (tag.TagName) // { // case "a": return typeof(System.Web.UI.HtmlControls.HtmlAnchor); // case "button": return typeof(System.Web.UI.HtmlControls.HtmlButton); // case "form": return typeof(System.Web.UI.HtmlControls.HtmlForm); // case "head": return typeof(System.Web.UI.HtmlControls.HtmlHead); // case "img": return typeof(System.Web.UI.HtmlControls.HtmlImage); // case "link": return typeof(System.Web.UI.HtmlControls.HtmlLink); // case "meta": return typeof(System.Web.UI.HtmlControls.HtmlMeta); // case "select": return typeof(System.Web.UI.HtmlControls.HtmlSelect); // case "table": return typeof(System.Web.UI.HtmlControls.HtmlTable); // case "td": return typeof(System.Web.UI.HtmlControls.HtmlTableCell); // case "th": return typeof(System.Web.UI.HtmlControls.HtmlTableCell); // case "tr": return typeof(System.Web.UI.HtmlControls.HtmlTableRow); // case "textarea": return typeof(System.Web.UI.HtmlControls.HtmlTextArea); // case "title": return typeof(System.Web.UI.HtmlControls.HtmlTitle); // case "input": // string type = tag["type"]; // if (!string.IsNullOrEmpty(type)) // { // switch (type.ToLower()) // { // case "button": return typeof(System.Web.UI.HtmlControls.HtmlInputButton); // case "checkbox": return typeof(System.Web.UI.HtmlControls.HtmlInputCheckBox); // case "file": return typeof(System.Web.UI.HtmlControls.HtmlInputFile); // case "hidden": return typeof(System.Web.UI.HtmlControls.HtmlInputHidden); // case "image": return typeof(System.Web.UI.HtmlControls.HtmlInputImage); // case "password": return typeof(System.Web.UI.HtmlControls.HtmlInputPassword); // case "radio": return typeof(System.Web.UI.HtmlControls.HtmlInputRadioButton); // case "reset": return typeof(System.Web.UI.HtmlControls.HtmlInputReset); // case "submit": return typeof(System.Web.UI.HtmlControls.HtmlInputSubmit); // case "text": return typeof(System.Web.UI.HtmlControls.HtmlInputText); // } // } // return typeof(System.Web.UI.HtmlControls.HtmlGenericControl); // default: // return typeof(System.Web.UI.HtmlControls.HtmlGenericControl); // } //} /// <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); }
/// <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 <%@ Register %> 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, ReferencedAssembliesContext 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 = GetHtmlTagRegistration(assemblies.SystemWebAssembly); ControlType = FindMatchingHtmlControlType(tag, assemblies); } 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. var enumerable = allowedTypes as Type[] ?? allowedTypes.ToArray(); if (!enumerable.Any(t => IsSameOrSubclass(t, 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, enumerable.Count()); foreach (Type allowedType in enumerable) { stringBuilder.AppendFormat("- {0}\r\n", allowedType.FullName); } throw new InvalidOperationException(stringBuilder.ToString()); } // Extract the [ParseChildren] attribute, if it has one. var custAtts = CustomAttributeData.GetCustomAttributes(ControlType).Where(a => a.AttributeType == typeof(System.Web.UI.ParseChildrenAttribute)); if (custAtts.Any()) { var parseAtt = custAtts.First(); if (!parseAtt.ConstructorArguments.Any()) { ParseChildrenAttribute = new ParseChildrenAttribute(); //public ParseChildrenAttribute(); } else if (parseAtt.ConstructorArguments.Count == 1) { //public ParseChildrenAttribute(bool childrenAsProperties); //public ParseChildrenAttribute(Type childControlType); if (parseAtt.ConstructorArguments[0].ArgumentType == typeof(bool)) { bool val = (bool)parseAtt.ConstructorArguments[0].Value; ParseChildrenAttribute = new ParseChildrenAttribute(val); } else if (parseAtt.ConstructorArguments[0].ArgumentType == typeof(Type)) { Type val = (Type)parseAtt.ConstructorArguments[0].Value; ParseChildrenAttribute = new ParseChildrenAttribute(val); } } else { //public ParseChildrenAttribute(bool childrenAsProperties, string defaultProperty); bool val = (bool)parseAtt.ConstructorArguments[0].Value; String defaultPropVal = (string)parseAtt.ConstructorArguments[1].Value; ParseChildrenAttribute = new ParseChildrenAttribute(val, defaultPropVal); } } //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. var controlBuilderAttsData = CustomAttributeData.GetCustomAttributes(ControlType).Where(a => a.AttributeType == typeof(System.Web.UI.ControlBuilderAttribute)); if (controlBuilderAttsData.Any()) { var controlBuilderAttData = custAtts.First(); if (controlBuilderAttData.ConstructorArguments.Count() == 1) { Type val = (Type)controlBuilderAttData.ConstructorArguments[0].Value; ControlBuilderAttribute = new System.Web.UI.ControlBuilderAttribute(val); } } //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 == GetHtmlTagRegistration(assemblies.SystemWebAssembly).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)); } } } }