PageRenderTime 24ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs

https://github.com/oyvindkinsey/dotnetopenid
C# | 409 lines | 212 code | 50 blank | 147 comment | 35 complexity | 2afa92882f6dfd18f01d616a2b05c603 MD5 | raw file
  1. //-----------------------------------------------------------------------
  2. // <copyright file="MessageDictionary.cs" company="Andrew Arnott">
  3. // Copyright (c) Andrew Arnott. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. namespace DotNetOpenAuth.Messaging.Reflection {
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Diagnostics.CodeAnalysis;
  12. using System.Diagnostics.Contracts;
  13. /// <summary>
  14. /// Wraps an <see cref="IMessage"/> instance in a dictionary that
  15. /// provides access to both well-defined message properties and "extra"
  16. /// name/value pairs that have no properties associated with them.
  17. /// </summary>
  18. [ContractVerification(false)]
  19. internal class MessageDictionary : IDictionary<string, string> {
  20. /// <summary>
  21. /// The <see cref="IMessage"/> instance manipulated by this dictionary.
  22. /// </summary>
  23. private readonly IMessage message;
  24. /// <summary>
  25. /// The <see cref="MessageDescription"/> instance that describes the message type.
  26. /// </summary>
  27. private readonly MessageDescription description;
  28. /// <summary>
  29. /// Whether original string values should be retrieved instead of normalized ones.
  30. /// </summary>
  31. private readonly bool getOriginalValues;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="MessageDictionary"/> class.
  34. /// </summary>
  35. /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param>
  36. /// <param name="description">The message description.</param>
  37. /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param>
  38. [Pure]
  39. internal MessageDictionary(IMessage message, MessageDescription description, bool getOriginalValues) {
  40. Contract.Requires<ArgumentNullException>(message != null);
  41. Contract.Requires<ArgumentNullException>(description != null);
  42. this.message = message;
  43. this.description = description;
  44. this.getOriginalValues = getOriginalValues;
  45. }
  46. /// <summary>
  47. /// Gets the message this dictionary provides access to.
  48. /// </summary>
  49. public IMessage Message {
  50. get {
  51. Contract.Ensures(Contract.Result<IMessage>() != null);
  52. return this.message;
  53. }
  54. }
  55. /// <summary>
  56. /// Gets the description of the type of message this dictionary provides access to.
  57. /// </summary>
  58. public MessageDescription Description {
  59. get {
  60. Contract.Ensures(Contract.Result<MessageDescription>() != null);
  61. return this.description;
  62. }
  63. }
  64. #region ICollection<KeyValuePair<string,string>> Properties
  65. /// <summary>
  66. /// Gets the number of explicitly set values in the message.
  67. /// </summary>
  68. public int Count {
  69. get { return this.Keys.Count; }
  70. }
  71. /// <summary>
  72. /// Gets a value indicating whether this message is read only.
  73. /// </summary>
  74. bool ICollection<KeyValuePair<string, string>>.IsReadOnly {
  75. get { return false; }
  76. }
  77. #endregion
  78. #region IDictionary<string,string> Properties
  79. /// <summary>
  80. /// Gets all the keys that have values associated with them.
  81. /// </summary>
  82. public ICollection<string> Keys {
  83. get {
  84. List<string> keys = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count);
  85. keys.AddRange(this.DeclaredKeys);
  86. keys.AddRange(this.AdditionalKeys);
  87. return keys.AsReadOnly();
  88. }
  89. }
  90. /// <summary>
  91. /// Gets the set of official message part names that have non-null values associated with them.
  92. /// </summary>
  93. public ICollection<string> DeclaredKeys {
  94. get {
  95. List<string> keys = new List<string>(this.description.Mapping.Count);
  96. foreach (var pair in this.description.Mapping) {
  97. // Don't include keys with null values, but default values for structs is ok
  98. if (pair.Value.GetValue(this.message, this.getOriginalValues) != null) {
  99. keys.Add(pair.Key);
  100. }
  101. }
  102. return keys.AsReadOnly();
  103. }
  104. }
  105. /// <summary>
  106. /// Gets the keys that are in the message but not declared as official OAuth properties.
  107. /// </summary>
  108. public ICollection<string> AdditionalKeys {
  109. get { return this.message.ExtraData.Keys; }
  110. }
  111. /// <summary>
  112. /// Gets all the values.
  113. /// </summary>
  114. public ICollection<string> Values {
  115. get {
  116. List<string> values = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count);
  117. foreach (MessagePart part in this.description.Mapping.Values) {
  118. if (part.GetValue(this.message, this.getOriginalValues) != null) {
  119. values.Add(part.GetValue(this.message, this.getOriginalValues));
  120. }
  121. }
  122. foreach (string value in this.message.ExtraData.Values) {
  123. Debug.Assert(value != null, "Null values should never be allowed in the extra data dictionary.");
  124. values.Add(value);
  125. }
  126. return values.AsReadOnly();
  127. }
  128. }
  129. #endregion
  130. /// <summary>
  131. /// Gets the serializer for the message this dictionary provides access to.
  132. /// </summary>
  133. private MessageSerializer Serializer {
  134. get { return MessageSerializer.Get(this.Message.GetType()); }
  135. }
  136. #region IDictionary<string,string> Indexers
  137. /// <summary>
  138. /// Gets or sets a value for some named value.
  139. /// </summary>
  140. /// <param name="key">The serialized form of a name for the value to read or write.</param>
  141. /// <returns>The named value.</returns>
  142. /// <remarks>
  143. /// If the key matches a declared property or field on the message type,
  144. /// that type member is set. Otherwise the key/value is stored in a
  145. /// dictionary for extra (weakly typed) strings.
  146. /// </remarks>
  147. /// <exception cref="ArgumentException">Thrown when setting a value that is not allowed for a given <paramref name="key"/>.</exception>
  148. public string this[string key] {
  149. get {
  150. MessagePart part;
  151. if (this.description.Mapping.TryGetValue(key, out part)) {
  152. // Never throw KeyNotFoundException for declared properties.
  153. return part.GetValue(this.message, this.getOriginalValues);
  154. } else {
  155. return this.message.ExtraData[key];
  156. }
  157. }
  158. set {
  159. MessagePart part;
  160. if (this.description.Mapping.TryGetValue(key, out part)) {
  161. part.SetValue(this.message, value);
  162. } else {
  163. if (value == null) {
  164. this.message.ExtraData.Remove(key);
  165. } else {
  166. this.message.ExtraData[key] = value;
  167. }
  168. }
  169. }
  170. }
  171. #endregion
  172. #region IDictionary<string,string> Methods
  173. /// <summary>
  174. /// Adds a named value to the message.
  175. /// </summary>
  176. /// <param name="key">The serialized form of the name whose value is being set.</param>
  177. /// <param name="value">The serialized form of the value.</param>
  178. /// <exception cref="ArgumentException">
  179. /// Thrown if <paramref name="key"/> already has a set value in this message.
  180. /// </exception>
  181. /// <exception cref="ArgumentNullException">
  182. /// Thrown if <paramref name="value"/> is null.
  183. /// </exception>
  184. public void Add(string key, string value) {
  185. ErrorUtilities.VerifyArgumentNotNull(value, "value");
  186. MessagePart part;
  187. if (this.description.Mapping.TryGetValue(key, out part)) {
  188. if (part.IsNondefaultValueSet(this.message)) {
  189. throw new ArgumentException(MessagingStrings.KeyAlreadyExists);
  190. }
  191. part.SetValue(this.message, value);
  192. } else {
  193. this.message.ExtraData.Add(key, value);
  194. }
  195. }
  196. /// <summary>
  197. /// Checks whether some named parameter has a value set in the message.
  198. /// </summary>
  199. /// <param name="key">The serialized form of the message part's name.</param>
  200. /// <returns>True if the parameter by the given name has a set value. False otherwise.</returns>
  201. public bool ContainsKey(string key) {
  202. return this.message.ExtraData.ContainsKey(key) ||
  203. (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message, this.getOriginalValues) != null);
  204. }
  205. /// <summary>
  206. /// Removes a name and value from the message given its name.
  207. /// </summary>
  208. /// <param name="key">The serialized form of the name to remove.</param>
  209. /// <returns>True if a message part by the given name was found and removed. False otherwise.</returns>
  210. public bool Remove(string key) {
  211. if (this.message.ExtraData.Remove(key)) {
  212. return true;
  213. } else {
  214. MessagePart part;
  215. if (this.description.Mapping.TryGetValue(key, out part)) {
  216. if (part.GetValue(this.message, this.getOriginalValues) != null) {
  217. part.SetValue(this.message, null);
  218. return true;
  219. }
  220. }
  221. return false;
  222. }
  223. }
  224. /// <summary>
  225. /// Gets some named value if the key has a value.
  226. /// </summary>
  227. /// <param name="key">The name (in serialized form) of the value being sought.</param>
  228. /// <param name="value">The variable where the value will be set.</param>
  229. /// <returns>True if the key was found and <paramref name="value"/> was set. False otherwise.</returns>
  230. public bool TryGetValue(string key, out string value) {
  231. MessagePart part;
  232. if (this.description.Mapping.TryGetValue(key, out part)) {
  233. value = part.GetValue(this.message, this.getOriginalValues);
  234. return value != null;
  235. }
  236. return this.message.ExtraData.TryGetValue(key, out value);
  237. }
  238. #endregion
  239. #region ICollection<KeyValuePair<string,string>> Methods
  240. /// <summary>
  241. /// Sets a named value in the message.
  242. /// </summary>
  243. /// <param name="item">The name-value pair to add. The name is the serialized form of the key.</param>
  244. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts ccrewrite does this.")]
  245. public void Add(KeyValuePair<string, string> item) {
  246. this.Add(item.Key, item.Value);
  247. }
  248. /// <summary>
  249. /// Removes all values in the message.
  250. /// </summary>
  251. public void ClearValues() {
  252. foreach (string key in this.Keys) {
  253. this.Remove(key);
  254. }
  255. }
  256. /// <summary>
  257. /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
  258. /// </summary>
  259. /// <exception cref="T:System.NotSupportedException">
  260. /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
  261. /// </exception>
  262. /// <remarks>
  263. /// This method cannot be implemented because keys are not guaranteed to be removed
  264. /// since some are inherent to the type of message that this dictionary provides
  265. /// access to.
  266. /// </remarks>
  267. public void Clear() {
  268. throw new NotSupportedException();
  269. }
  270. /// <summary>
  271. /// Checks whether a named value has been set on the message.
  272. /// </summary>
  273. /// <param name="item">The name/value pair.</param>
  274. /// <returns>True if the key exists and has the given value. False otherwise.</returns>
  275. public bool Contains(KeyValuePair<string, string> item) {
  276. MessagePart part;
  277. if (this.description.Mapping.TryGetValue(item.Key, out part)) {
  278. return string.Equals(part.GetValue(this.message, this.getOriginalValues), item.Value, StringComparison.Ordinal);
  279. } else {
  280. return this.message.ExtraData.Contains(item);
  281. }
  282. }
  283. /// <summary>
  284. /// Copies all the serializable data from the message to a key/value array.
  285. /// </summary>
  286. /// <param name="array">The array to copy to.</param>
  287. /// <param name="arrayIndex">The index in the <paramref name="array"/> to begin copying to.</param>
  288. void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) {
  289. foreach (var pair in (IDictionary<string, string>)this) {
  290. array[arrayIndex++] = pair;
  291. }
  292. }
  293. /// <summary>
  294. /// Removes a named value from the message if it exists.
  295. /// </summary>
  296. /// <param name="item">The serialized form of the name and value to remove.</param>
  297. /// <returns>True if the name/value was found and removed. False otherwise.</returns>
  298. public bool Remove(KeyValuePair<string, string> item) {
  299. // We use contains because that checks that the value is equal as well.
  300. if (((ICollection<KeyValuePair<string, string>>)this).Contains(item)) {
  301. ((IDictionary<string, string>)this).Remove(item.Key);
  302. return true;
  303. }
  304. return false;
  305. }
  306. #endregion
  307. #region IEnumerable<KeyValuePair<string,string>> Members
  308. /// <summary>
  309. /// Gets an enumerator that generates KeyValuePair&lt;string, string&gt; instances
  310. /// for all the key/value pairs that are set in the message.
  311. /// </summary>
  312. /// <returns>The enumerator that can generate the name/value pairs.</returns>
  313. public IEnumerator<KeyValuePair<string, string>> GetEnumerator() {
  314. foreach (string key in this.Keys) {
  315. yield return new KeyValuePair<string, string>(key, this[key]);
  316. }
  317. }
  318. #endregion
  319. #region IEnumerable Members
  320. /// <summary>
  321. /// Gets an enumerator that generates KeyValuePair&lt;string, string&gt; instances
  322. /// for all the key/value pairs that are set in the message.
  323. /// </summary>
  324. /// <returns>The enumerator that can generate the name/value pairs.</returns>
  325. IEnumerator System.Collections.IEnumerable.GetEnumerator() {
  326. return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator();
  327. }
  328. #endregion
  329. /// <summary>
  330. /// Saves the data in a message to a standard dictionary.
  331. /// </summary>
  332. /// <returns>The generated dictionary.</returns>
  333. [Pure]
  334. public IDictionary<string, string> Serialize() {
  335. Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null);
  336. return this.Serializer.Serialize(this);
  337. }
  338. /// <summary>
  339. /// Loads data from a dictionary into the message.
  340. /// </summary>
  341. /// <param name="fields">The data to load into the message.</param>
  342. public void Deserialize(IDictionary<string, string> fields) {
  343. Contract.Requires<ArgumentNullException>(fields != null);
  344. this.Serializer.Deserialize(fields, this);
  345. }
  346. #if CONTRACTS_FULL
  347. /// <summary>
  348. /// Verifies conditions that should be true for any valid state of this object.
  349. /// </summary>
  350. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
  351. [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
  352. [ContractInvariantMethod]
  353. private void ObjectInvariant() {
  354. Contract.Invariant(this.Message != null);
  355. Contract.Invariant(this.Description != null);
  356. }
  357. #endif
  358. }
  359. }