diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
commit | 5fec515095ee10b522f414a03e78f282aaf520dc (patch) | |
tree | 204c75486639c23cdda2ef38b34d7e5050a1a2e3 /src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs | |
parent | f1a4155398635a4fd9f485eec817152627682704 (diff) | |
parent | 8f4165ee515728aca3faaa26e8354a40612e85e4 (diff) | |
download | DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.zip DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.gz DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.bz2 |
Merge branch 'splitDlls'.
DNOA now builds and (in some cases) ships as many distinct assemblies.
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs')
-rw-r--r-- | src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs new file mode 100644 index 0000000..957ea41 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageSerializer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Reflection; + using System.Xml; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// Serializes/deserializes OAuth messages for/from transit. + /// </summary> + [ContractVerification(true)] + internal class MessageSerializer { + /// <summary> + /// The specific <see cref="IMessage"/>-derived type + /// that will be serialized and deserialized using this class. + /// </summary> + private readonly Type messageType; + + /// <summary> + /// Initializes a new instance of the MessageSerializer class. + /// </summary> + /// <param name="messageType">The specific <see cref="IMessage"/>-derived type + /// that will be serialized and deserialized using this class.</param> + [ContractVerification(false)] // bugs/limitations in CC static analysis + private MessageSerializer(Type messageType) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + Contract.Ensures(this.messageType != null); + this.messageType = messageType; + } + + /// <summary> + /// Creates or reuses a message serializer for a given message type. + /// </summary> + /// <param name="messageType">The type of message that will be serialized/deserialized.</param> + /// <returns>A message serializer for the given message type.</returns> + [ContractVerification(false)] // bugs/limitations in CC static analysis + internal static MessageSerializer Get(Type messageType) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + + return new MessageSerializer(messageType); + } + + /// <summary> + /// Reads JSON as a flat dictionary into a message. + /// </summary> + /// <param name="messageDictionary">The message dictionary to fill with the JSON-deserialized data.</param> + /// <param name="reader">The JSON reader.</param> + internal static void DeserializeJsonAsFlatDictionary(IDictionary<string, string> messageDictionary, XmlDictionaryReader reader) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(reader, "reader"); + + reader.Read(); // one extra one to skip the root node. + while (reader.Read()) { + if (reader.NodeType == XmlNodeType.EndElement) { + // This is likely the closing </root> tag. + continue; + } + + string key = reader.Name; + reader.Read(); + string value = reader.ReadContentAsString(); + messageDictionary[key] = value; + } + } + + /// <summary> + /// Reads the data from a message instance and writes a XML/JSON encoding of it. + /// </summary> + /// <param name="messageDictionary">The message to be serialized.</param> + /// <param name="writer">The writer to use for the serialized form.</param> + /// <remarks> + /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonWriter(System.IO.Stream)"/> + /// to create the <see cref="XmlDictionaryWriter"/> instance capable of emitting JSON. + /// </remarks> + [Pure] + internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(writer, "writer"); + + writer.WriteStartElement("root"); + writer.WriteAttributeString("type", "object"); + foreach (var pair in messageDictionary) { + bool include = false; + string type = "string"; + MessagePart partDescription; + if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { + Contract.Assume(partDescription != null); + if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { + include = true; + if (IsNumeric(partDescription.MemberDeclaredType)) { + type = "number"; + } else if (partDescription.MemberDeclaredType.IsAssignableFrom(typeof(bool))) { + type = "boolean"; + } + } + } else { + // This is extra data. We always write it out. + include = true; + } + + if (include) { + writer.WriteStartElement(pair.Key); + writer.WriteAttributeString("type", type); + writer.WriteString(pair.Value); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); + } + + /// <summary> + /// Reads XML/JSON into a message dictionary. + /// </summary> + /// <param name="messageDictionary">The message to deserialize into.</param> + /// <param name="reader">The XML/JSON to read into the message.</param> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> + /// <remarks> + /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(System.IO.Stream, System.Xml.XmlDictionaryReaderQuotas)"/> + /// to create the <see cref="XmlDictionaryReader"/> instance capable of reading JSON. + /// </remarks> + internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(reader, "reader"); + + DeserializeJsonAsFlatDictionary(messageDictionary, reader); + + // Make sure all the required parts are present and valid. + messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); + messageDictionary.Message.EnsureValidMessage(); + } + + /// <summary> + /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. + /// </summary> + /// <param name="messageDictionary">The message to be serialized.</param> + /// <returns>The dictionary of values to send for the message.</returns> + [Pure] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] + internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + + // Rather than hand back the whole message dictionary (which + // includes keys with blank values), create a new dictionary + // that only has required keys, and optional keys whose + // values are not empty (or default). + var result = new Dictionary<string, string>(); + foreach (var pair in messageDictionary) { + MessagePart partDescription; + if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { + Contract.Assume(partDescription != null); + if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { + result.Add(pair.Key, pair.Value); + } + } else { + // This is extra data. We always write it out. + result.Add(pair.Key, pair.Value); + } + } + + return result; + } + + /// <summary> + /// Reads name=value pairs into a message. + /// </summary> + /// <param name="fields">The name=value pairs that were read in from the transport.</param> + /// <param name="messageDictionary">The message to deserialize into.</param> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> + internal void Deserialize(IDictionary<string, string> fields, MessageDictionary messageDictionary) { + Requires.NotNull(fields, "fields"); + Requires.NotNull(messageDictionary, "messageDictionary"); + + var messageDescription = messageDictionary.Description; + + // Before we deserialize the message, make sure all the required parts are present. + messageDescription.EnsureMessagePartsPassBasicValidation(fields); + + try { + foreach (var pair in fields) { + messageDictionary[pair.Key] = pair.Value; + } + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name); + } + + messageDictionary.Message.EnsureValidMessage(); + + var originalPayloadMessage = messageDictionary.Message as IMessageOriginalPayload; + if (originalPayloadMessage != null) { + originalPayloadMessage.OriginalPayload = fields; + } + } + + /// <summary> + /// Determines whether the specified type is numeric. + /// </summary> + /// <param name="type">The type to test.</param> + /// <returns> + /// <c>true</c> if the specified type is numeric; otherwise, <c>false</c>. + /// </returns> + private static bool IsNumeric(Type type) { + return type.IsAssignableFrom(typeof(double)) + || type.IsAssignableFrom(typeof(float)) + || type.IsAssignableFrom(typeof(short)) + || type.IsAssignableFrom(typeof(int)) + || type.IsAssignableFrom(typeof(long)) + || type.IsAssignableFrom(typeof(ushort)) + || type.IsAssignableFrom(typeof(uint)) + || type.IsAssignableFrom(typeof(ulong)); + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.messageType != null); + } +#endif + } +} |