1
//-----------------------------------------------------------------------
2 // <copyright file="MessageDescription.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.Messaging
.Reflection
{
9 using System
.Collections
.Generic
;
10 using System
.Diagnostics
;
11 using System
.Globalization
;
13 using System
.Reflection
;
16 /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing
17 /// those key/values pairs.
19 internal class MessageDescription
{
21 /// A dictionary of reflected message types and the generated reflection information.
23 private static Dictionary
<MessageTypeAndVersion
, MessageDescription
> reflectedMessageTypes
= new Dictionary
<MessageTypeAndVersion
, MessageDescription
>();
26 /// The type of message this instance was generated from.
28 private MessageTypeAndVersion messageTypeAndVersion
;
31 /// A mapping between the serialized key names and their
32 /// describing <see cref="MessagePart"/> instances.
34 private Dictionary
<string, MessagePart
> mapping
;
37 /// Initializes a new instance of the <see cref="MessageDescription"/> class.
39 /// <param name="messageTypeAndVersion">The type and protocol version of the message to reflect over.</param>
40 private MessageDescription(MessageTypeAndVersion messageTypeAndVersion
) {
41 ErrorUtilities
.VerifyArgumentNotNull(messageTypeAndVersion
, "messageTypeAndVersion");
43 if (!typeof(IProtocolMessage
).IsAssignableFrom(messageTypeAndVersion
.Type
)) {
44 throw new ArgumentException(string.Format(
45 CultureInfo
.CurrentCulture
,
46 MessagingStrings
.UnexpectedType
,
47 typeof(IProtocolMessage
),
48 messageTypeAndVersion
.Type
));
51 this.messageTypeAndVersion
= messageTypeAndVersion
;
52 this.ReflectMessageType();
56 /// Gets the mapping between the serialized key names and their describing
57 /// <see cref="MessagePart"/> instances.
59 internal IDictionary
<string, MessagePart
> Mapping
{
60 get { return this.mapping; }
64 /// Gets a <see cref="MessageDescription"/> instance prepared for the
65 /// given message type.
67 /// <param name="messageType">A type that implements <see cref="IProtocolMessage"/>.</param>
68 /// <param name="messageVersion">The protocol version of the message.</param>
69 /// <returns>A <see cref="MessageDescription"/> instance.</returns>
70 internal static MessageDescription
Get(Type messageType
, Version messageVersion
) {
71 ErrorUtilities
.VerifyArgumentNotNull(messageType
, "messageType");
72 ErrorUtilities
.VerifyArgumentNotNull(messageVersion
, "messageVersion");
74 MessageTypeAndVersion key
= new MessageTypeAndVersion(messageType
, messageVersion
);
76 MessageDescription result
;
77 if (!reflectedMessageTypes
.TryGetValue(key
, out result
)) {
78 lock (reflectedMessageTypes
) {
79 if (!reflectedMessageTypes
.TryGetValue(key
, out result
)) {
80 reflectedMessageTypes
[key
] = result
= new MessageDescription(key
);
89 /// Reflects over some <see cref="IProtocolMessage"/>-implementing type
90 /// and prepares to serialize/deserialize instances of that type.
92 internal void ReflectMessageType() {
93 this.mapping
= new Dictionary
<string, MessagePart
>();
95 Type currentType
= this.messageTypeAndVersion
.Type
;
97 foreach (MemberInfo member
in currentType
.GetMembers(BindingFlags
.Instance
| BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.DeclaredOnly
)) {
98 if (member
is PropertyInfo
|| member
is FieldInfo
) {
99 MessagePartAttribute partAttribute
=
100 (from a
in member
.GetCustomAttributes(typeof(MessagePartAttribute
), true).OfType
<MessagePartAttribute
>()
101 orderby a
.MinVersionValue
descending
102 where a
.MinVersionValue
<= this.messageTypeAndVersion
.Version
103 select a
).FirstOrDefault();
104 if (partAttribute
!= null) {
105 MessagePart part
= new MessagePart(member
, partAttribute
);
106 this.mapping
.Add(part
.Name
, part
);
110 currentType
= currentType
.BaseType
;
111 } while (currentType
!= null);
115 /// Ensures the message parts pass basic validation.
117 /// <param name="parts">The key/value pairs of the serialzied message.</param>
118 internal void EnsureMessagePartsPassBasicValidation(IDictionary
<string, string> parts
) {
119 this.EnsureRequiredMessagePartsArePresent(parts
.Keys
);
120 this.EnsureRequiredProtocolMessagePartsAreNotEmpty(parts
);
124 /// Verifies that a given set of keys include all the required parameters
125 /// for this message type or throws an exception.
127 /// <param name="keys">The names of all parameters included in a message.</param>
128 /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception>
129 private void EnsureRequiredMessagePartsArePresent(IEnumerable
<string> keys
) {
130 var missingKeys
= (from part
in Mapping
.Values
131 where part
.IsRequired
&& !keys
.Contains(part
.Name
)
132 select part
.Name
).ToArray();
133 if (missingKeys
.Length
> 0) {
134 throw new ProtocolException(
136 CultureInfo
.CurrentCulture
,
137 MessagingStrings
.RequiredParametersMissing
,
138 this.messageTypeAndVersion
.Type
.FullName
,
139 string.Join(", ", missingKeys
)));
144 /// Ensures the protocol message parts that must not be empty are in fact not empty.
146 /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param>
147 private void EnsureRequiredProtocolMessagePartsAreNotEmpty(IDictionary
<string, string> partValues
) {
149 var emptyValuedKeys
= (from part
in Mapping
.Values
150 where
!part
.AllowEmpty
&& partValues
.TryGetValue(part
.Name
, out value) && value.Length
== 0
151 select part
.Name
).ToArray();
152 if (emptyValuedKeys
.Length
> 0) {
153 throw new ProtocolException(
155 CultureInfo
.CurrentCulture
,
156 MessagingStrings
.RequiredNonEmptyParameterWasEmpty
,
157 this.messageTypeAndVersion
.Type
.FullName
,
158 string.Join(", ", emptyValuedKeys
)));
163 /// A struct used as the key to bundle message type and version.
165 private struct MessageTypeAndVersion
{
167 /// Backing store for the <see cref="Type"/> property.
169 private readonly Type type
;
172 /// Backing store for the <see cref="Version"/> property.
174 private readonly Version version
;
177 /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct.
179 /// <param name="messageType">Type of the message.</param>
180 /// <param name="messageVersion">The message version.</param>
181 internal MessageTypeAndVersion(Type messageType
, Version messageVersion
) {
182 ErrorUtilities
.VerifyArgumentNotNull(messageType
, "messageType");
183 ErrorUtilities
.VerifyArgumentNotNull(messageVersion
, "messageVersion");
185 this.type
= messageType
;
186 this.version
= messageVersion
;
190 /// Gets the message type.
192 internal Type Type { get { return this.type; }
}
195 /// Gets the message version.
197 internal Version Version { get { return this.version; }
}
200 /// Implements the operator ==.
202 /// <param name="first">The first object to compare.</param>
203 /// <param name="second">The second object to compare.</param>
204 /// <returns>The result of the operator.</returns>
205 public static bool operator ==(MessageTypeAndVersion first
, MessageTypeAndVersion second
) {
206 // structs cannot be null, so this is safe
207 return first
.Equals(second
);
211 /// Implements the operator !=.
213 /// <param name="first">The first object to compare.</param>
214 /// <param name="second">The second object to compare.</param>
215 /// <returns>The result of the operator.</returns>
216 public static bool operator !=(MessageTypeAndVersion first
, MessageTypeAndVersion second
) {
217 // structs cannot be null, so this is safe
218 return !first
.Equals(second
);
222 /// Indicates whether this instance and a specified object are equal.
224 /// <param name="obj">Another object to compare to.</param>
226 /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
228 public override bool Equals(object obj
) {
229 if (obj
is MessageTypeAndVersion
) {
230 MessageTypeAndVersion other
= (MessageTypeAndVersion
)obj
;
231 return this.type
== other
.type
&& this.version
== other
.version
;
238 /// Returns the hash code for this instance.
241 /// A 32-bit signed integer that is the hash code for this instance.
243 public override int GetHashCode() {
244 return this.type
.GetHashCode();