//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOAuth.Messaging.Reflection { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net.Security; using System.Reflection; using System.Xml; /// /// Describes an individual member of a message and assists in its serialization. /// internal class MessagePart { /// /// A map of converters that help serialize custom objects to string values and back again. /// private static readonly Dictionary converters = new Dictionary(); /// /// The string-object conversion routines to use for this individual message part. /// private ValueMapping converter; /// /// The property that this message part is associated with, if aplicable. /// private PropertyInfo property; /// /// The field that this message part is associated with, if aplicable. /// private FieldInfo field; /// /// The type of the message part. (Not the type of the message itself). /// private Type memberDeclaredType; /// /// The default (uninitialized) value of the member inherent in its type. /// private object defaultMemberValue; /// /// Initializes static members of the class. /// [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] static MessagePart() { Map(uri => uri.AbsoluteUri, str => new Uri(str)); Map(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); } /// /// Initializes a new instance of the class. /// /// /// A property or field of an implementing type /// that has a attached to it. /// /// /// The attribute discovered on that describes the /// serialization requirements of the message part. /// internal MessagePart(MemberInfo member, MessagePartAttribute attribute) { if (member == null) { throw new ArgumentNullException("member"); } this.field = member as FieldInfo; this.property = member as PropertyInfo; if (this.field == null && this.property == null) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, MessagingStrings.UnexpectedType, typeof(FieldInfo).Name + ", " + typeof(PropertyInfo).Name, member.GetType().Name), "member"); } if (attribute == null) { throw new ArgumentNullException("attribute"); } this.Name = attribute.Name ?? member.Name; this.RequiredProtection = attribute.RequiredProtection; this.IsRequired = attribute.IsRequired; this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { this.converter = new ValueMapping( obj => obj != null ? obj.ToString() : null, str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); } // Validate a sane combination of settings this.ValidateSettings(); } /// /// Gets or sets the name to use when serializing or deserializing this parameter in a message. /// internal string Name { get; set; } /// /// Gets or sets whether this message part must be signed. /// internal ProtectionLevel RequiredProtection { get; set; } /// /// Gets or sets a value indicating whether this message part is required for the /// containing message to be valid. /// internal bool IsRequired { get; set; } /// /// Sets the member of a given message to some given value. /// Used in deserialization. /// /// The message instance containing the member whose value should be set. /// The string representation of the value to set. internal void SetValue(IProtocolMessage message, string value) { if (this.property != null) { this.property.SetValue(message, this.ToValue(value), null); } else { this.field.SetValue(message, this.ToValue(value)); } } /// /// Gets the value of a member of a given message. /// Used in serialization. /// /// The message instance to read the value from. /// The string representation of the member's value. internal string GetValue(IProtocolMessage message) { return this.ToString(this.GetValueAsObject(message)); } /// /// Gets whether the value has been set to something other than its CLR type default value. /// /// The message instance to check the value on. /// True if the value is not the CLR default value. internal bool IsNondefaultValueSet(IProtocolMessage message) { if (this.memberDeclaredType.IsValueType) { return !this.GetValueAsObject(message).Equals(this.defaultMemberValue); } else { return this.defaultMemberValue != this.GetValueAsObject(message); } } /// /// Figures out the CLR default value for a given type. /// /// The type whose default value is being sought. /// Either null, or some default value like 0 or 0.0. private static object DeriveDefaultValue(Type type) { if (type.IsValueType) { return Activator.CreateInstance(type); } else { return null; } } /// /// Adds a pair of type conversion functions to the static converstion map. /// /// The custom type to convert to and from strings. /// The function to convert the custom type to a string. /// The function to convert a string to the custom type. private static void Map(Func toString, Func toValue) { Func safeToString = obj => obj != null ? toString((T)obj) : null; Func safeToT = str => str != null ? toValue(str) : default(T); converters.Add(typeof(T), new ValueMapping(safeToString, safeToT)); } /// /// Checks whether a type is a nullable value type (i.e. int?) /// /// The type in question. /// True if this is a nullable value type. private static bool IsNonNullableValueType(Type type) { if (!type.IsValueType) { return false; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { return false; } return true; } /// /// Converts a string representation of the member's value to the appropriate type. /// /// The string representation of the member's value. /// An instance of the appropriate type for setting the member. private object ToValue(string value) { return this.converter.StringToValue(value); } /// /// Converts the member's value to its string representation. /// /// The value of the member. /// The string representation of the member's value. private string ToString(object value) { return this.converter.ValueToString(value); } /// /// Gets the value of the message part, without converting it to/from a string. /// /// The message instance to read from. /// The value of the member. private object GetValueAsObject(IProtocolMessage message) { if (this.property != null) { return this.property.GetValue(message, null); } else { return this.field.GetValue(message); } } /// /// Validates that the message part and its attribute have agreeable settings. /// /// /// Thrown when a non-nullable value type is set as optional. /// private void ValidateSettings() { if (!this.IsRequired && IsNonNullableValueType(this.memberDeclaredType)) { MemberInfo member = (MemberInfo)this.field ?? this.property; throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, "Invalid combination: {0} on message type {1} is a non-nullable value type but is marked as optional.", member.Name, member.DeclaringType)); } } } }