diff options
Diffstat (limited to 'src')
9 files changed, 206 insertions, 82 deletions
diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj index c46e6b8..447a3c5 100644 --- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj +++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj @@ -57,6 +57,7 @@ <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> <Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> + <Compile Include="Messaging\Reflection\DefaultEncoderAttribute.cs" /> <Compile Include="Messaging\StandardMessageFactory.cs" /> <Compile Include="Messaging\IDataBagFormatter.cs" /> <Compile Include="Messaging\UriStyleMessageFormatter.cs" /> diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs new file mode 100644 index 0000000..d827972 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="DefaultEncoderAttribute.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// Allows a custom class or struct to be serializable between itself and a string representation. + /// </summary> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] + internal sealed class DefaultEncoderAttribute : Attribute { + /// <summary> + /// Initializes a new instance of the <see cref="DefaultEncoderAttribute"/> class. + /// </summary> + /// <param name="converterType">The <see cref="IMessagePartEncoder"/> implementing type to use for serializing this type.</param> + public DefaultEncoderAttribute(Type converterType) { + Requires.NotNull(converterType, "converterType"); + Requires.True(typeof(IMessagePartEncoder).IsAssignableFrom(converterType), "Argument must be a type that implements {0}.", typeof(IMessagePartEncoder).Name); + this.Encoder = (IMessagePartEncoder)Activator.CreateInstance(converterType); + } + + /// <summary> + /// Gets the default encoder to use for the declaring class. + /// </summary> + public IMessagePartEncoder Encoder { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs index 8f40d6d..b2c4664 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs @@ -132,16 +132,10 @@ namespace DotNetOpenAuth.Messaging.Reflection { null, str => str != null ? underlyingMapping.StringToValue(str) : null); } else { - this.converter = new ValueMapping( - obj => obj != null ? obj.ToString() : null, - null, - str => str != null ? Convert.ChangeType(str, underlyingType, CultureInfo.InvariantCulture) : null); + this.converter = GetDefaultEncoder(underlyingType); } } else { - this.converter = new ValueMapping( - obj => obj != null ? obj.ToString() : null, - null, - str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); + this.converter = GetDefaultEncoder(this.memberDeclaredType); } } } else { @@ -212,28 +206,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> - /// Adds a pair of type conversion functions to the static conversion map. - /// </summary> - /// <typeparam name="T">The custom type to convert to and from strings.</typeparam> - /// <param name="toString">The function to convert the custom type to a string.</param> - /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> - /// <param name="toValue">The function to convert a string to the custom type.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toString", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toValue", Justification = "Code contracts")] - internal static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { - Requires.NotNull(toString, "toString"); - Requires.NotNull(toValue, "toValue"); - - if (toOriginalString == null) { - toOriginalString = toString; - } - - Func<object, string> safeToString = obj => obj != null ? toString((T)obj) : null; - Func<object, string> safeToOriginalString = obj => obj != null ? toOriginalString((T)obj) : null; - Func<string, object> safeToT = str => str != null ? toValue(str) : default(T); - converters.Add(typeof(T), new ValueMapping(safeToString, safeToOriginalString, safeToT)); - } - - /// <summary> /// Sets the member of a given message to some given value. /// Used in deserialization. /// </summary> @@ -308,6 +280,60 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> + /// Adds a pair of type conversion functions to the static conversion map. + /// </summary> + /// <typeparam name="T">The custom type to convert to and from strings.</typeparam> + /// <param name="toString">The function to convert the custom type to a string.</param> + /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> + /// <param name="toValue">The function to convert a string to the custom type.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toString", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toValue", Justification = "Code contracts")] + private static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { + Requires.NotNull(toString, "toString"); + Requires.NotNull(toValue, "toValue"); + + if (toOriginalString == null) { + toOriginalString = toString; + } + + Func<object, string> safeToString = obj => obj != null ? toString((T)obj) : null; + Func<object, string> safeToOriginalString = obj => obj != null ? toOriginalString((T)obj) : null; + Func<string, object> safeToT = str => str != null ? toValue(str) : default(T); + converters.Add(typeof(T), new ValueMapping(safeToString, safeToOriginalString, safeToT)); + } + + /// <summary> + /// Creates a <see cref="ValueMapping"/> that resorts to <see cref="object.ToString"/> and + /// <see cref="Convert.ChangeType(object, Type, IFormatProvider)"/> for the conversion. + /// </summary> + /// <param name="type">The type to create the mapping for.</param> + /// <returns>The value mapping.</returns> + private static ValueMapping CreateFallbackMapping(Type type) { + Requires.NotNull(type, "type"); + + return new ValueMapping( + obj => obj != null ? obj.ToString() : null, + null, + str => str != null ? Convert.ChangeType(str, type, CultureInfo.InvariantCulture) : null); + } + + /// <summary> + /// Creates the default encoder for a given type. + /// </summary> + /// <param name="type">The type to create a <see cref="ValueMapping"/> for.</param> + /// <returns>A <see cref="ValueMapping"/> struct.</returns> + private static ValueMapping GetDefaultEncoder(Type type) { + Requires.NotNull(type, "type"); + + var converterAttributes = (DefaultEncoderAttribute[])type.GetCustomAttributes(typeof(DefaultEncoderAttribute), false); + ErrorUtilities.VerifyInternal(converterAttributes.Length < 2, "Too many attributes applied."); + if (converterAttributes.Length == 1) { + return new ValueMapping(converterAttributes[0].Encoder); + } + + return CreateFallbackMapping(type); + } + + /// <summary> /// Figures out the CLR default value for a given type. /// </summary> /// <param name="type">The type whose default value is being sought.</param> @@ -347,11 +373,13 @@ namespace DotNetOpenAuth.Messaging.Reflection { Contract.Ensures(Contract.Result<IMessagePartEncoder>() != null); IMessagePartEncoder encoder; - if (!encoders.TryGetValue(messagePartEncoder, out encoder)) { - try { - encoder = encoders[messagePartEncoder] = (IMessagePartEncoder)Activator.CreateInstance(messagePartEncoder); - } catch (MissingMethodException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.EncoderInstantiationFailed, messagePartEncoder.FullName); + lock (encoders) { + if (!encoders.TryGetValue(messagePartEncoder, out encoder)) { + try { + encoder = encoders[messagePartEncoder] = (IMessagePartEncoder)Activator.CreateInstance(messagePartEncoder); + } catch (MissingMethodException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.EncoderInstantiationFailed, messagePartEncoder.FullName); + } } } diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs index 72fdc80..05a33bc 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs @@ -46,18 +46,6 @@ namespace DotNetOpenAuth.OpenId.Provider { private readonly IdentifierDiscoveryServices discoveryServices; /// <summary> - /// A type initializer that ensures that another type initializer runs in order to guarantee that - /// types are serializable. - /// </summary> - private static Identifier dummyIdentifierToInvokeStaticCtor = "http://localhost/"; - - /// <summary> - /// A type initializer that ensures that another type initializer runs in order to guarantee that - /// types are serializable. - /// </summary> - private static Realm dummyRealmToInvokeStaticCtor = "http://localhost/"; - - /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. /// </summary> private ProviderSecuritySettings securitySettings; diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs index 6e991d2..4dba624 100644 --- a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -59,18 +59,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private readonly IdentifierDiscoveryServices discoveryServices; /// <summary> - /// A type initializer that ensures that another type initializer runs in order to guarantee that - /// types are serializable. - /// </summary> - private static Identifier dummyIdentifierToInvokeStaticCtor = "http://localhost/"; - - /// <summary> - /// A type initializer that ensures that another type initializer runs in order to guarantee that - /// types are serializable. - /// </summary> - private static Realm dummyRealmToInvokeStaticCtor = "http://localhost/"; - - /// <summary> /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property. /// </summary> private OpenIdRelyingParty nonVerifyingRelyingParty; diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs index 03595d3..3cdd0f3 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs @@ -20,20 +20,9 @@ namespace DotNetOpenAuth.OpenId { [ContractVerification(true)] [Pure] [ContractClass(typeof(IdentifierContract))] + [DefaultEncoder(typeof(IdentifierEncoder))] public abstract class Identifier { /// <summary> - /// Initializes static members of the <see cref="Identifier"/> class. - /// </summary> - static Identifier() { - Func<string, Identifier> safeIdentifier = str => { - Contract.Assume(str != null); - ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); - return Identifier.Parse(str, true); - }; - MessagePart.Map<Identifier>(id => id.SerializedString, id => id.OriginalString, safeIdentifier); - } - - /// <summary> /// Initializes a new instance of the <see cref="Identifier"/> class. /// </summary> /// <param name="originalString">The original string before any normalization.</param> @@ -306,5 +295,42 @@ namespace DotNetOpenAuth.OpenId { /// False if the Identifier was originally created with an explicit HTTP scheme. /// </returns> internal abstract bool TryRequireSsl(out Identifier secureIdentifier); + + /// <summary> + /// Provides conversions to and from strings for messages that include members of this type. + /// </summary> + private class IdentifierEncoder : IMessagePartOriginalEncoder { + /// <summary> + /// Encodes the specified value as the original value that was formerly decoded. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + public string EncodeAsOriginalString(object value) { + Requires.NotNull(value, "value"); + return ((Identifier)value).OriginalString; + } + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + public string Encode(object value) { + Requires.NotNull(value, "value"); + return ((Identifier)value).SerializedString; + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns>The deserialized form of the given string.</returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + public object Decode(string value) { + Requires.NotNull(value, "value"); + ErrorUtilities.VerifyFormat(value.Length > 0, MessagingStrings.NonEmptyStringExpected); + return Identifier.Parse(value, true); + } + } } } diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs index d682542..28e4df0 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs @@ -29,6 +29,7 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> [Serializable] [Pure] + [DefaultEncoder(typeof(MessagePartRealmConverter))] public class Realm { /// <summary> /// A regex used to detect a wildcard that is being used in the realm. @@ -59,17 +60,6 @@ namespace DotNetOpenAuth.OpenId { private Uri uri; /// <summary> - /// Initializes static members of the <see cref="Realm"/> class. - /// </summary> - static Realm() { - Func<string, Realm> safeRealm = str => { - Contract.Assume(str != null); - return new Realm(str); - }; - MessagePart.Map<Realm>(realm => realm.ToString(), realm => realm.OriginalString, safeRealm); - } - - /// <summary> /// Initializes a new instance of the <see cref="Realm"/> class. /// </summary> /// <param name="realmUrl">The realm URL to use in the new instance.</param> @@ -508,5 +498,41 @@ namespace DotNetOpenAuth.OpenId { Contract.Invariant(this.uri.AbsoluteUri != null); } #endif + + /// <summary> + /// Provides conversions to and from strings for messages that include members of this type. + /// </summary> + private class MessagePartRealmConverter : IMessagePartOriginalEncoder { + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + public string Encode(object value) { + Requires.NotNull(value, "value"); + return value.ToString(); + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns>The deserialized form of the given string.</returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + public object Decode(string value) { + Requires.NotNull(value, "value"); + return new Realm(value); + } + + /// <summary> + /// Encodes the specified value as the original value that was formerly decoded. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + public string EncodeAsOriginalString(object value) { + Requires.NotNull(value, "value"); + return ((Realm)value).OriginalString; + } + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs index f44d23e..e83beb1 100644 --- a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -8,7 +8,10 @@ namespace DotNetOpenAuth.Test.OpenId { using System; using System.Collections.Generic; using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.Test.Mocks; using NUnit.Framework; [TestFixture] @@ -84,5 +87,19 @@ namespace DotNetOpenAuth.Test.OpenId { public void ParseEmpty() { Identifier.Parse(string.Empty); } + + [Test] + public void MessagePartConvertibility() { + var message = new MessageWithIdentifier(); + var messageDescription = new MessageDescription(message.GetType(), new Version(1, 0)); + var messageDictionary = new MessageDictionary(message, messageDescription, false); + messageDictionary["Identifier"] = OpenId.OpenIdTestBase.IdentifierSelect; + Assert.That(messageDictionary["Identifier"], Is.EqualTo(OpenId.OpenIdTestBase.IdentifierSelect)); + } + + private class MessageWithIdentifier : TestMessage { + [MessagePart] + internal Identifier Identifier { get; set; } + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs index 20f9fe2..764a795 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs @@ -6,7 +6,10 @@ namespace DotNetOpenAuth.Test { using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.Test.Mocks; using NUnit.Framework; [TestFixture] @@ -207,5 +210,19 @@ namespace DotNetOpenAuth.Test { Assert.AreNotEqual(testRealm1a, testRealm1a.ToString(), "Although the URLs are equal, different object types shouldn't be equal."); Assert.AreNotEqual(testRealm3, testRealm1a, "Wildcard difference ignored by Equals"); } + + [Test] + public void MessagePartConvertibility() { + var message = new MessageWithRealm(); + var messageDescription = new MessageDescription(message.GetType(), new Version(1, 0)); + var messageDictionary = new MessageDictionary(message, messageDescription, false); + messageDictionary["Realm"] = OpenId.OpenIdTestBase.RPRealmUri.AbsoluteUri; + Assert.That(messageDictionary["Realm"], Is.EqualTo(OpenId.OpenIdTestBase.RPRealmUri.AbsoluteUri)); + } + + private class MessageWithRealm : TestMessage { + [MessagePart] + internal Realm Realm { get; set; } + } } } |