using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Resources; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; namespace System.ComponentModel.DataAnnotations { /// /// Attribute to provide a hint to the presentation layer about what control it should use /// [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "ControlParameters is exposed, just with a different type")] [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")] public class UIHintAttribute : Attribute { private UIHintImplementation _implementation; /// /// Gets the name of the control that is most appropriate for this associated property or field /// public string UIHint { get { return this._implementation.UIHint; } } /// /// Gets the name of the presentation layer that supports the control type in /// public string PresentationLayer { get { return this._implementation.PresentationLayer; } } /// /// Gets the name-value pairs used as parameters to the control's constructor /// /// is thrown if the current attribute is ill-formed. public IDictionary ControlParameters { get { return this._implementation.ControlParameters; } } /// /// Constructor that accepts the name of the control, without specifying which presentation layer to use /// /// The name of the UI control. public UIHintAttribute(string uiHint) : this(uiHint, null, new object[0]) { } /// /// Constructor that accepts both the name of the control as well as the presentation layer /// /// The name of the control to use /// The name of the presentation layer that supports this control public UIHintAttribute(string uiHint, string presentationLayer) : this(uiHint, presentationLayer, new object[0]) { } /// /// Full constructor that accepts the name of the control, presentation layer, and optional parameters /// to use when constructing the control /// /// The name of the control /// The presentation layer /// The list of parameters for the control public UIHintAttribute(string uiHint, string presentationLayer, params object[] controlParameters) { this._implementation = new UIHintImplementation(uiHint, presentationLayer, controlParameters); } public override int GetHashCode() { return this._implementation.GetHashCode(); } public override bool Equals(object obj) { var otherAttribute = obj as UIHintAttribute; if (otherAttribute == null) { return false; } return this._implementation.Equals(otherAttribute._implementation); } internal class UIHintImplementation { private IDictionary _controlParameters; private object[] _inputControlParameters; /// /// Gets the name of the control that is most appropriate for this associated property or field /// public string UIHint { get; private set; } /// /// Gets the name of the presentation layer that supports the control type in /// public string PresentationLayer { get; private set; } public IDictionary ControlParameters { get { if (this._controlParameters == null) { // Lazy load the dictionary. It's fine if this method executes multiple times in stress scenarios. // If the method throws (indicating that the input params are invalid) this property will throw // every time it's accessed. this._controlParameters = this.BuildControlParametersDictionary(); } return this._controlParameters; } } public UIHintImplementation(string uiHint, string presentationLayer, params object[] controlParameters) { this.UIHint = uiHint; this.PresentationLayer = presentationLayer; if (controlParameters != null) { this._inputControlParameters = new object[controlParameters.Length]; Array.Copy(controlParameters, this._inputControlParameters, controlParameters.Length); } } /// /// Returns the hash code for this UIHintAttribute. /// /// A 32-bit signed integer hash code. public override int GetHashCode() { var a = this.UIHint ?? String.Empty; var b = this.PresentationLayer ?? String.Empty; return a.GetHashCode() ^ b.GetHashCode(); } /// /// Determines whether this instance of UIHintAttribute and a specified object, /// which must also be a UIHintAttribute object, have the same value. /// /// An System.Object. /// true if obj is a UIHintAttribute and its value is the same as this instance; otherwise, false. public override bool Equals(object obj) { // don't need to perform a type check on obj since this is an internal class var otherImplementation = (UIHintImplementation)obj; if (this.UIHint != otherImplementation.UIHint || this.PresentationLayer != otherImplementation.PresentationLayer) { return false; } IDictionary leftParams; IDictionary rightParams; try { leftParams = this.ControlParameters; rightParams = otherImplementation.ControlParameters; } catch (InvalidOperationException) { return false; } Debug.Assert(leftParams != null, "leftParams shouldn't be null"); Debug.Assert(rightParams != null, "rightParams shouldn't be null"); if (leftParams.Count != rightParams.Count) { return false; } else { return leftParams.OrderBy(p => p.Key).SequenceEqual(rightParams.OrderBy(p => p.Key)); } } /// /// Validates the input control parameters and throws InvalidOperationException if they are not correct. /// /// /// Dictionary of control parameters. /// private IDictionary BuildControlParametersDictionary() { IDictionary controlParameters = new Dictionary(); object[] inputControlParameters = this._inputControlParameters; if (inputControlParameters == null || inputControlParameters.Length == 0) { return controlParameters; } if (inputControlParameters.Length % 2 != 0) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DataAnnotationsResources.UIHintImplementation_NeedEvenNumberOfControlParameters)); } for (int i = 0; i < inputControlParameters.Length; i += 2) { object key = inputControlParameters[i]; object value = inputControlParameters[i + 1]; if (key == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, DataAnnotationsResources.UIHintImplementation_ControlParameterKeyIsNull, i)); } string keyString = key as string; if (keyString == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, DataAnnotationsResources.UIHintImplementation_ControlParameterKeyIsNotAString, i, inputControlParameters[i].ToString())); } if (controlParameters.ContainsKey(keyString)) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, DataAnnotationsResources.UIHintImplementation_ControlParameterKeyOccursMoreThanOnce, i, keyString)); } controlParameters[keyString] = value; } return controlParameters; } } } }