1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\r
5 * This software is subject to the Microsoft Public License (Ms-PL).
\r
6 * A copy of the license can be found in the license.htm file included
\r
7 * in this distribution.
\r
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc {
\r
15 using System.Collections.Generic;
\r
16 using System.ComponentModel;
\r
17 using System.ComponentModel.DataAnnotations;
\r
18 using System.Globalization;
\r
20 using System.Reflection;
\r
21 using System.Web.Mvc.Resources;
\r
23 // TODO: Remove this class in MVC 3
\r
25 // We brought in a private copy of the AssociatedMetadataTypeTypeDescriptionProvider
\r
26 // from .NET 4, because it provides several bug fixes and perf improvements. If we're
\r
27 // running on .NET < 4, we'll use our private copy.
\r
29 internal static class TypeDescriptorHelper {
\r
31 private static Func<Type, ICustomTypeDescriptor> _typeDescriptorFactory = GetTypeDescriptorFactory();
\r
33 private static Func<Type, ICustomTypeDescriptor> GetTypeDescriptorFactory() {
\r
34 if (Environment.Version.Major < 4) {
\r
35 return type => new _AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
\r
38 return type => new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
\r
41 public static ICustomTypeDescriptor Get(Type type) {
\r
42 return _typeDescriptorFactory(type);
\r
45 // Private copies of the .NET 4 AssociatedMetadataType classes
\r
47 private class _AssociatedMetadataTypeTypeDescriptionProvider : TypeDescriptionProvider {
\r
48 public _AssociatedMetadataTypeTypeDescriptionProvider(Type type)
\r
49 : base(TypeDescriptor.GetProvider(type)) {
\r
52 public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
\r
53 ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);
\r
54 return new _AssociatedMetadataTypeTypeDescriptor(baseDescriptor, objectType);
\r
58 private class _AssociatedMetadataTypeTypeDescriptor : CustomTypeDescriptor {
\r
59 private Type AssociatedMetadataType {
\r
64 public _AssociatedMetadataTypeTypeDescriptor(ICustomTypeDescriptor parent, Type type)
\r
66 AssociatedMetadataType = TypeDescriptorCache.GetAssociatedMetadataType(type);
\r
67 if (AssociatedMetadataType != null) {
\r
68 TypeDescriptorCache.ValidateMetadataType(type, AssociatedMetadataType);
\r
72 public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
\r
73 return GetPropertiesWithMetadata(base.GetProperties(attributes));
\r
76 public override PropertyDescriptorCollection GetProperties() {
\r
77 return GetPropertiesWithMetadata(base.GetProperties());
\r
80 private PropertyDescriptorCollection GetPropertiesWithMetadata(PropertyDescriptorCollection originalCollection) {
\r
81 if (AssociatedMetadataType == null) {
\r
82 return originalCollection;
\r
85 bool customDescriptorsCreated = false;
\r
86 List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();
\r
87 foreach (PropertyDescriptor propDescriptor in originalCollection) {
\r
88 Attribute[] newMetadata = TypeDescriptorCache.GetAssociatedMetadata(AssociatedMetadataType, propDescriptor.Name);
\r
89 PropertyDescriptor descriptor = propDescriptor;
\r
90 if (newMetadata.Length > 0) {
\r
91 // Create a metadata descriptor that wraps the property descriptor
\r
92 descriptor = new _MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata);
\r
93 customDescriptorsCreated = true;
\r
96 tempPropertyDescriptors.Add(descriptor);
\r
99 if (customDescriptorsCreated) {
\r
100 return new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);
\r
102 return originalCollection;
\r
105 public override AttributeCollection GetAttributes() {
\r
106 // Since normal TD behavior is to return cached attribute instances on subsequent
\r
107 // calls to GetAttributes, we must be sure below to use the TD APIs to get both
\r
108 // the base and associated attributes
\r
109 AttributeCollection attributes = base.GetAttributes();
\r
110 if (AssociatedMetadataType != null) {
\r
111 // Note that the use of TypeDescriptor.GetAttributes here opens up the possibility of
\r
112 // infinite recursion, in the corner case of two Types referencing each other as
\r
113 // metadata types (or a longer cycle)
\r
114 Attribute[] newAttributes = TypeDescriptor.GetAttributes(AssociatedMetadataType).OfType<Attribute>().ToArray();
\r
115 attributes = AttributeCollection.FromExisting(attributes, newAttributes);
\r
120 private static class TypeDescriptorCache {
\r
121 private static readonly Attribute[] emptyAttributes = new Attribute[0];
\r
123 // Stores the associated metadata type for a type
\r
124 private static readonly Dictionary<Type, Type> _metadataTypeCache = new Dictionary<Type, Type>();
\r
126 // For a type and a property name stores the attributes for that property name.
\r
127 private static readonly Dictionary<Tuple<Type, string>, Attribute[]> _typeMemberCache = new Dictionary<Tuple<Type, string>, Attribute[]>();
\r
129 // Stores whether or not a type and associated metadata type has been checked for validity
\r
130 private static readonly Dictionary<Tuple<Type, Type>, bool> _validatedMetadataTypeCache = new Dictionary<Tuple<Type, Type>, bool>();
\r
132 public static void ValidateMetadataType(Type type, Type associatedType) {
\r
133 Tuple<Type, Type> typeTuple = new Tuple<Type, Type>(type, associatedType);
\r
135 lock (_validatedMetadataTypeCache) {
\r
136 if (!_validatedMetadataTypeCache.ContainsKey(typeTuple)) {
\r
137 CheckAssociatedMetadataType(type, associatedType);
\r
138 _validatedMetadataTypeCache.Add(typeTuple, true);
\r
143 public static Type GetAssociatedMetadataType(Type type) {
\r
144 Type associatedMetadataType = null;
\r
145 lock (_metadataTypeCache) {
\r
146 if (_metadataTypeCache.TryGetValue(type, out associatedMetadataType)) {
\r
147 return associatedMetadataType;
\r
151 // Try association attribute
\r
152 MetadataTypeAttribute attribute = (MetadataTypeAttribute)Attribute.GetCustomAttribute(type, typeof(MetadataTypeAttribute));
\r
153 if (attribute != null) {
\r
154 associatedMetadataType = attribute.MetadataClassType;
\r
157 lock (_metadataTypeCache) {
\r
158 _metadataTypeCache[type] = associatedMetadataType;
\r
161 return associatedMetadataType;
\r
164 private static void CheckAssociatedMetadataType(Type mainType, Type associatedMetadataType) {
\r
165 // Only properties from main type
\r
166 HashSet<string> mainTypeMemberNames = new HashSet<string>(mainType.GetProperties().Select(p => p.Name));
\r
168 // Properties and fields from buddy type
\r
169 var buddyFields = associatedMetadataType.GetFields().Select(f => f.Name);
\r
170 var buddyProperties = associatedMetadataType.GetProperties().Select(p => p.Name);
\r
171 HashSet<string> buddyTypeMembers = new HashSet<string>(buddyFields.Concat(buddyProperties), StringComparer.Ordinal);
\r
173 // Buddy members should be a subset of the main type's members
\r
174 if (!buddyTypeMembers.IsSubsetOf(mainTypeMemberNames)) {
\r
175 // Reduce the buddy members to the set not contained in the main members
\r
176 buddyTypeMembers.ExceptWith(mainTypeMemberNames);
\r
178 throw new InvalidOperationException(String.Format(
\r
179 CultureInfo.CurrentCulture,
\r
180 MvcResources.PrivateAssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties,
\r
182 String.Join(", ", buddyTypeMembers.ToArray())));
\r
186 public static Attribute[] GetAssociatedMetadata(Type type, string memberName) {
\r
187 var memberTuple = new Tuple<Type, string>(type, memberName);
\r
188 Attribute[] attributes;
\r
189 lock (_typeMemberCache) {
\r
190 if (_typeMemberCache.TryGetValue(memberTuple, out attributes)) {
\r
195 // Allow fields and properties
\r
196 MemberTypes allowedMemberTypes = MemberTypes.Property | MemberTypes.Field;
\r
197 // Only public static/instance members
\r
198 BindingFlags searchFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
\r
199 // Try to find a matching member on type
\r
200 MemberInfo matchingMember = type.GetMember(memberName, allowedMemberTypes, searchFlags).FirstOrDefault();
\r
201 if (matchingMember != null) {
\r
202 attributes = Attribute.GetCustomAttributes(matchingMember, true /* inherit */);
\r
205 attributes = emptyAttributes;
\r
208 lock (_typeMemberCache) {
\r
209 _typeMemberCache[memberTuple] = attributes;
\r
214 private class Tuple<T1, T2> {
\r
215 public T1 Item1 { get; set; }
\r
216 public T2 Item2 { get; set; }
\r
218 public Tuple(T1 item1, T2 item2) {
\r
223 public override int GetHashCode() {
\r
224 int h1 = Item1.GetHashCode();
\r
225 int h2 = Item2.GetHashCode();
\r
226 return ((h1 << 5) + h1) ^ h2;
\r
229 public override bool Equals(object obj) {
\r
230 var other = obj as Tuple<T1, T2>;
\r
231 if (other != null) {
\r
232 return other.Item1.Equals(Item1) && other.Item2.Equals(Item2);
\r
240 private class _MetadataPropertyDescriptorWrapper : PropertyDescriptor {
\r
241 private PropertyDescriptor _descriptor;
\r
242 private bool _isReadOnly;
\r
244 public _MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] newAttributes)
\r
245 : base(descriptor, newAttributes) {
\r
246 _descriptor = descriptor;
\r
247 var readOnlyAttribute = newAttributes.OfType<ReadOnlyAttribute>().FirstOrDefault();
\r
248 _isReadOnly = (readOnlyAttribute != null ? readOnlyAttribute.IsReadOnly : false);
\r
251 public override void AddValueChanged(object component, EventHandler handler) { _descriptor.AddValueChanged(component, handler); }
\r
253 public override bool CanResetValue(object component) { return _descriptor.CanResetValue(component); }
\r
255 public override Type ComponentType { get { return _descriptor.ComponentType; } }
\r
257 public override object GetValue(object component) { return _descriptor.GetValue(component); }
\r
259 public override bool IsReadOnly {
\r
261 // Dev10 Bug 594083
\r
262 // It's not enough to call the wrapped _descriptor because it does not know anything about
\r
263 // new attributes passed into the constructor of this class.
\r
264 return _isReadOnly || _descriptor.IsReadOnly;
\r
268 public override Type PropertyType { get { return _descriptor.PropertyType; } }
\r
270 public override void RemoveValueChanged(object component, EventHandler handler) { _descriptor.RemoveValueChanged(component, handler); }
\r
272 public override void ResetValue(object component) { _descriptor.ResetValue(component); }
\r
274 public override void SetValue(object component, object value) { _descriptor.SetValue(component, value); }
\r
276 public override bool ShouldSerializeValue(object component) { return _descriptor.ShouldSerializeValue(component); }
\r
278 public override bool SupportsChangeEvents { get { return _descriptor.SupportsChangeEvents; } }
\r