diff --git a/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerable.cs b/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerable.cs new file mode 100644 index 0000000000..3536863afc --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerable.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Serializes types by delegating them through a concrete implementation. + /// + /// The wrapping or original type of the + /// to proxy. + /// The type parameter of the original + /// to proxy. + public class DelegatingEnumerable : IEnumerable + { + private readonly IEnumerable _source; + private readonly IWrapperProvider _wrapperProvider; + + /// + /// Initializes a . + /// + /// + /// This constructor is necessary for + /// to serialize. + /// + public DelegatingEnumerable() + { + _source = Enumerable.Empty(); + } + + /// + /// Initializes a with the original + /// and the wrapper provider for wrapping individual elements. + /// + /// The instance to get the enumerator from. + /// The wrapper provider for wrapping individual elements. + public DelegatingEnumerable([NotNull] IEnumerable source, IWrapperProvider elementWrapperProvider) + { + _source = source; + _wrapperProvider = elementWrapperProvider; + } + + /// + /// Gets a delegating enumerator of the original source which is being + /// wrapped. + /// + /// The delegating enumerator of the original source. + public IEnumerator GetEnumerator() + { + return new DelegatingEnumerator(_source.GetEnumerator(), _wrapperProvider); + } + + /// + /// The serializer requires every type it encounters can be serialized and deserialized. + /// This type will never be used for deserialization, but we are required to implement the add + /// method so that the type can be serialized. This will never be called. + /// + /// The item to add. Unused. + public void Add(object item) + { + throw new NotImplementedException(); + } + + /// + /// Gets a delegating enumerator of the original source which is being + /// wrapped. + /// + /// The delegating enumerator of the original source. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerator.cs b/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerator.cs new file mode 100644 index 0000000000..937679a643 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/DelegatingEnumerator.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Delegates enumeration of elements to the original enumerator and wraps the items + /// with the supplied . + /// + /// The type to which the individual elements need to be wrapped to. + /// The original type of the element being wrapped. + public class DelegatingEnumerator : IEnumerator + { + private readonly IEnumerator _inner; + private readonly IWrapperProvider _wrapperProvider; + + /// + /// Initializes a which enumerates + /// over the elements of the original enumerator and wraps them using the supplied + /// . + /// + /// The original enumerator. + /// The wrapper provider to wrap individual elements. + public DelegatingEnumerator([NotNull] IEnumerator inner, IWrapperProvider wrapperProvider) + { + _inner = inner; + _wrapperProvider = wrapperProvider; + } + + /// + public TWrapped Current + { + get + { + object obj = _inner.Current; + if (_wrapperProvider == null) + { + // if there is no wrapper, then this cast should not fail + return (TWrapped)obj; + } + + return (TWrapped)_wrapperProvider.Wrap(obj); + } + } + + /// + object IEnumerator.Current + { + get + { + return Current; + } + } + + /// + public void Dispose() + { + _inner.Dispose(); + } + + /// + public bool MoveNext() + { + return _inner.MoveNext(); + } + + /// + public void Reset() + { + _inner.Reset(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProvider.cs b/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProvider.cs new file mode 100644 index 0000000000..a86ba11939 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProvider.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Provides a for interface types which implement + /// . + /// + public class EnumerableWrapperProvider : IWrapperProvider + { + private readonly IWrapperProvider _wrapperProvider; + private readonly ConstructorInfo _wrappingTypeConstructor; + + /// + /// Initializes an instance of . + /// + /// Type of the original + /// that is being wrapped. + /// The for the element type. + /// Can be null. + public EnumerableWrapperProvider( + [NotNull] Type sourceEnumerableOfT, + IWrapperProvider elementWrapperProvider) + { + var enumerableOfT = sourceEnumerableOfT.ExtractGenericInterface(typeof(IEnumerable<>)); + if (!sourceEnumerableOfT.IsInterface() || enumerableOfT == null) + { + throw new ArgumentException( + Resources.FormatEnumerableWrapperProvider_InvalidSourceEnumerableOfT(typeof(IEnumerable<>).Name), + nameof(sourceEnumerableOfT)); + } + + _wrapperProvider = elementWrapperProvider; + + var declaredElementType = enumerableOfT.GetGenericArguments()[0]; + var wrappedElementType = elementWrapperProvider?.WrappingType ?? declaredElementType; + WrappingType = typeof(DelegatingEnumerable<,>).MakeGenericType(wrappedElementType, declaredElementType); + + _wrappingTypeConstructor = WrappingType.GetConstructor(new[] + { + sourceEnumerableOfT, + typeof(IWrapperProvider) + }); + } + + /// + public Type WrappingType + { + get; + } + + /// + public object Wrap(object original) + { + if (original == null) + { + return null; + } + + return _wrappingTypeConstructor.Invoke(new object[] { original, _wrapperProvider }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProviderFactory.cs b/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProviderFactory.cs new file mode 100644 index 0000000000..09a720b853 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/EnumerableWrapperProviderFactory.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Creates an for interface types implementing the + /// type. + /// + public class EnumerableWrapperProviderFactory : IWrapperProviderFactory + { + private readonly IEnumerable _wrapperProviderFactories; + + /// + /// Initializes an with a list + /// . + /// + /// List of . + public EnumerableWrapperProviderFactory([NotNull] IEnumerable wrapperProviderFactories) + { + _wrapperProviderFactories = wrapperProviderFactories; + } + + /// + /// Gets an for the provided context. + /// + /// The . + /// An instance of if the declared type is + /// an interface and implements . + public IWrapperProvider GetProvider([NotNull] WrapperProviderContext context) + { + if (context.IsSerialization) + { + // Example: IEnumerable + var declaredType = context.DeclaredType; + + // We only wrap interfaces types(ex: IEnumerable, IQueryable, IList etc.) and not + // concrete types like List, Collection which implement IEnumerable. + if (declaredType != null && declaredType.IsInterface() && declaredType.IsGenericType()) + { + var enumerableOfT = declaredType.ExtractGenericInterface(typeof(IEnumerable<>)); + if (enumerableOfT != null) + { + var elementType = enumerableOfT.GetGenericArguments()[0]; + + var wrapperProviderContext = new WrapperProviderContext( + elementType, + context.IsSerialization); + + var elementWrapperProvider = _wrapperProviderFactories.GetWrapperProvider(wrapperProviderContext); + + return new EnumerableWrapperProvider(enumerableOfT, elementWrapperProvider); + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.Xml/FormattingUtilities.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc.Xml/Formatters/FormattingUtilities.cs rename to src/Microsoft.AspNet.Mvc.Xml/FormattingUtilities.cs diff --git a/src/Microsoft.AspNet.Mvc.Xml/IUnwrappable.cs b/src/Microsoft.AspNet.Mvc.Xml/IUnwrappable.cs new file mode 100644 index 0000000000..fb0cdc9ce8 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/IUnwrappable.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Defines an interface for objects to be un-wrappable after deserialization. + /// + public interface IUnwrappable + { + /// + /// Unwraps an object. + /// + /// The type to which the object should be un-wrapped to. + /// The un-wrapped object. + object Unwrap(Type declaredType); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/IWrapperProvider.cs b/src/Microsoft.AspNet.Mvc.Xml/IWrapperProvider.cs new file mode 100644 index 0000000000..73ed4b63a3 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/IWrapperProvider.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Defines an interface for wrapping objects for serialization or deserialization into xml. + /// + public interface IWrapperProvider + { + /// + /// Gets the wrapping type. + /// + Type WrappingType { get; } + + /// + /// Wraps the given object to the wrapping type provided by . + /// + /// The original non-wrapped object. + /// Returns a wrapped object. + object Wrap(object original); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/IWrapperProviderFactory.cs b/src/Microsoft.AspNet.Mvc.Xml/IWrapperProviderFactory.cs new file mode 100644 index 0000000000..6a3c6fdcc2 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/IWrapperProviderFactory.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Create a given a . + /// + public interface IWrapperProviderFactory + { + /// + /// Gets the for the provided context. + /// + /// The . + /// A wrapping provider if the factory decides to wrap the type, else null. + IWrapperProvider GetProvider(WrapperProviderContext context); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Xml/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Xml/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..fb22746534 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +// +namespace Microsoft.AspNet.Mvc.Xml +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNet.Mvc.Xml.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The type must be an interface and must be or derive from '{0}'. + /// + internal static string EnumerableWrapperProvider_InvalidSourceEnumerableOfT + { + get { return GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"); } + } + + /// + /// The type must be an interface and must be or derive from '{0}'. + /// + internal static string FormatEnumerableWrapperProvider_InvalidSourceEnumerableOfT(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0); + } + + /// + /// The object to be wrapped must be of type '{0}' but was of type '{1}'. + /// + internal static string WrapperProvider_MismatchType + { + get { return GetString("WrapperProvider_MismatchType"); } + } + + /// + /// The object to be wrapped must be of type '{0}' but was of type '{1}'. + /// + internal static string FormatWrapperProvider_MismatchType(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("WrapperProvider_MismatchType"), p0, p1); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Xml/Resources.resx b/src/Microsoft.AspNet.Mvc.Xml/Resources.resx new file mode 100644 index 0000000000..d2c7be467a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The type must be an interface and must be or derive from '{0}'. + + + The object to be wrapped must be of type '{0}' but was of type '{1}'. + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapper.cs b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapper.cs index f25addfc21..c374cb26fd 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapper.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; @@ -9,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.Xml /// Wrapper class for to enable it to be serialized by the xml formatters. /// [XmlRoot("Error")] - public sealed class SerializableErrorWrapper : IXmlSerializable + public sealed class SerializableErrorWrapper : IXmlSerializable, IUnwrappable { // Note: XmlSerializer requires to have default constructor public SerializableErrorWrapper() @@ -83,61 +86,10 @@ namespace Microsoft.AspNet.Mvc.Xml } } - /// - /// Gets the - /// - /// - /// - /// - public static object UnwrapSerializableErrorObject([NotNull] Type modelType, object deserializedObject) + /// + public object Unwrap([NotNull] Type declaredType) { - // Since we expect users to typically bind with SerializableError type, - // we should try to unwrap and get the actual SerializableError. - if (modelType == typeof(SerializableError)) - { - var serializableErrorWrapper = deserializedObject as SerializableErrorWrapper; - if (serializableErrorWrapper != null) - { - deserializedObject = serializableErrorWrapper.SerializableError; - } - } - - return deserializedObject; - } - - /// - /// Checks if an object is an instance of type and if yes, - /// gets and returns the wrapped object in it. - /// - /// An - /// - public static object WrapSerializableErrorObject(object obj) - { - var serializableError = obj as SerializableError; - if (serializableError == null) - { - return obj; - } - - return new SerializableErrorWrapper(serializableError); - } - - /// - /// Checks if the given type is of type and if yes, returns - /// the wrapper type . - /// - /// The type to be checked - /// type, else the original type. - public static Type CreateSerializableType([NotNull] Type type) - { - // Since the type "SerializableError" is not compatible - // with the xml serializers, we create a compatible wrapper type for serialization. - if (type == typeof(SerializableError)) - { - type = typeof(SerializableErrorWrapper); - } - - return type; + return SerializableError; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProvider.cs b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProvider.cs new file mode 100644 index 0000000000..208d372f3e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Wraps the object of type . + /// + public class SerializableErrorWrapperProvider : IWrapperProvider + { + /// + public Type WrappingType + { + get + { + return typeof(SerializableErrorWrapper); + } + } + + /// + public object Wrap([NotNull] object original) + { + var error = original as SerializableError; + if (error == null) + { + throw new ArgumentException( + Resources.FormatWrapperProvider_MismatchType( + typeof(SerializableErrorWrapper).Name, + original.GetType().Name), + nameof(original)); + } + + return new SerializableErrorWrapper(error); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProviderFactory.cs b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProviderFactory.cs new file mode 100644 index 0000000000..e779971223 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/SerializableErrorWrapperProviderFactory.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Creates an for the type . + /// + public class SerializableErrorWrapperProviderFactory : IWrapperProviderFactory + { + /// + /// Creates an instance of if the provided + /// declared type is . + /// + /// + /// An instance of if the provided + /// declared type is , else null. + public IWrapperProvider GetProvider([NotNull] WrapperProviderContext context) + { + if (context.DeclaredType == typeof(SerializableError)) + { + return new SerializableErrorWrapperProvider(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderContext.cs b/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderContext.cs new file mode 100644 index 0000000000..b2b9a744ff --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderContext.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// The context used by an to wrap or un-wrap types. + /// + public class WrapperProviderContext + { + /// + /// Initializes a . + /// + /// The declared type of the object that needs to be wrapped. + /// if the wrapper provider is invoked during + /// serialization, otherwise . + public WrapperProviderContext([NotNull] Type declaredType, bool isSerialization) + { + DeclaredType = declaredType; + IsSerialization = isSerialization; + } + + /// + /// The declared type which could be wrapped/un-wrapped by a different type + /// during serialization or de-serializatoin. + /// + public Type DeclaredType { get; } + + /// + /// if a wrapper provider is invoked during serialization, + /// otherwise. + /// + public bool IsSerialization { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderFactoriesExtensions.cs b/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderFactoriesExtensions.cs new file mode 100644 index 0000000000..6a2efd5c50 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Xml/WrapperProviderFactoriesExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.Xml +{ + /// + /// Extension methods for . + /// + public static class WrapperProviderFactoriesExtensions + { + /// + /// Gets an instance of for the supplied + /// type. + /// + /// A list of . + /// The . + /// An instance of if there is a wrapping provider for the + /// supplied type, else null. + public static IWrapperProvider GetWrapperProvider( + [NotNull] this IEnumerable wrapperProviderFactories, + [NotNull] WrapperProviderContext wrapperProviderContext) + { + foreach (var wrapperProviderFactory in wrapperProviderFactories) + { + var wrapperProvider = wrapperProviderFactory.GetProvider(wrapperProviderContext); + if (wrapperProvider != null) + { + return wrapperProvider; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs similarity index 82% rename from src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs index cec1a3c38d..f9ae5007fa 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs @@ -31,17 +31,28 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedEncodings = new List(); SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); + SupportedMediaTypes = new List(); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); + _serializerSettings = new DataContractSerializerSettings(); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } - /// - public IList SupportedMediaTypes { get; private set; } + /// + /// Gets the list of to + /// provide the wrapping type for de-serialization. + /// + public IList WrapperProviderFactories { get; } /// - public IList SupportedEncodings { get; private set; } + public IList SupportedMediaTypes { get; } + + /// + public IList SupportedEncodings { get; } /// /// Indicates the acceptable input XML depth. @@ -127,7 +138,10 @@ namespace Microsoft.AspNet.Mvc.Xml /// The type to which the XML will be deserialized. protected virtual Type GetSerializableType([NotNull] Type declaredType) { - return SerializableErrorWrapper.CreateSerializableType(declaredType); + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(declaredType, isSerialization: false)); + + return wrapperProvider?.WrappingType ?? declaredType; } /// @@ -157,9 +171,21 @@ namespace Microsoft.AspNet.Mvc.Xml using (var xmlReader = CreateXmlReader(new DelegatingStream(request.Body))) { var type = GetSerializableType(context.ModelType); - var dataContractSerializer = CreateSerializer(type); - var deserializedObject = dataContractSerializer.ReadObject(xmlReader); - deserializedObject = SerializableErrorWrapper.UnwrapSerializableErrorObject(context.ModelType, deserializedObject); + + var serializer = CreateSerializer(type); + + var deserializedObject = serializer.ReadObject(xmlReader); + + // Unwrap only if the original type was wrapped. + if (type != context.ModelType) + { + var unwrappable = deserializedObject as IUnwrappable; + if (unwrappable != null) + { + deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); + } + } + return Task.FromResult(deserializedObject); } } diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs similarity index 69% rename from src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs index a751f202f9..1b5da1437a 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Threading.Tasks; @@ -40,9 +41,20 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); WriterSettings = writerSettings; + _serializerSettings = new DataContractSerializerSettings(); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } + /// + /// Gets the list of to + /// provide the wrapping type for serialization. + /// + public IList WrapperProviderFactories { get; } + /// /// Gets the settings to be used by the XmlWriter. /// @@ -72,24 +84,38 @@ namespace Microsoft.AspNet.Mvc.Xml /// The declared type. /// The runtime type. /// The type of the object to be serialized. - protected virtual Type GetSerializableType(Type declaredType, Type runtimeType) + protected virtual Type ResolveType(Type declaredType, Type runtimeType) { - Type type = declaredType; if (declaredType == null || declaredType == typeof(object)) { if (runtimeType != null) { - type = runtimeType; + return runtimeType; } } - return SerializableErrorWrapper.CreateSerializableType(type); + return declaredType; + } + + /// + /// Gets the type to be serialized. + /// + /// The original type to be serialized + /// The original or wrapped type provided by any s. + protected virtual Type GetSerializableType(Type type) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(type, isSerialization: true)); + + return wrapperProvider?.WrappingType ?? type; } /// protected override bool CanWriteType(Type declaredType, Type runtimeType) { - return CreateSerializer(GetSerializableType(declaredType, runtimeType)) != null; + var type = ResolveType(declaredType, runtimeType); + + return CreateSerializer(GetSerializableType(type)) != null; } /// @@ -138,12 +164,26 @@ namespace Microsoft.AspNet.Mvc.Xml using (var outputStream = new DelegatingStream(innerStream)) using (var xmlWriter = CreateXmlWriter(outputStream, tempWriterSettings)) { - var runtimeType = context.Object == null ? null : context.Object.GetType(); + var obj = context.Object; + var runtimeType = obj?.GetType(); - var type = GetSerializableType(context.DeclaredType, runtimeType); - var dataContractSerializer = CreateSerializer(type); - var responseObject = SerializableErrorWrapper.WrapSerializableErrorObject(context.Object); - dataContractSerializer.WriteObject(xmlWriter, responseObject); + var resolvedType = ResolveType(context.DeclaredType, runtimeType); + + var wrappingType = GetSerializableType(resolvedType); + + // Wrap the object only if there is a wrapping type. + if (wrappingType != null && wrappingType != resolvedType) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext( + declaredType: resolvedType, + isSerialization: true)); + + obj = wrapperProvider.Wrap(obj); + } + + var dataContractSerializer = CreateSerializer(wrappingType); + dataContractSerializer.WriteObject(xmlWriter, obj); } return Task.FromResult(true); diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs similarity index 78% rename from src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerInputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs index 6b9611c71a..d553064849 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs @@ -30,16 +30,26 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedEncodings = new List(); SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); + SupportedMediaTypes = new List(); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } - /// - public IList SupportedMediaTypes { get; private set; } + /// + /// Gets the list of to + /// provide the wrapping type for de-serialization. + /// + public IList WrapperProviderFactories { get; } /// - public IList SupportedEncodings { get; private set; } + public IList SupportedMediaTypes { get; } + + /// + public IList SupportedEncodings { get; } /// /// Indicates the acceptable input XML depth. @@ -96,7 +106,10 @@ namespace Microsoft.AspNet.Mvc.Xml /// The type to which the XML will be deserialized. protected virtual Type GetSerializableType([NotNull] Type declaredType) { - return SerializableErrorWrapper.CreateSerializableType(declaredType); + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(declaredType, isSerialization: false)); + + return wrapperProvider?.WrappingType ?? declaredType; } /// @@ -114,7 +127,7 @@ namespace Microsoft.AspNet.Mvc.Xml /// Called during deserialization to get the . /// /// The used during deserialization. - protected virtual XmlSerializer CreateXmlSerializer(Type type) + protected virtual XmlSerializer CreateSerializer(Type type) { return new XmlSerializer(type); } @@ -136,9 +149,21 @@ namespace Microsoft.AspNet.Mvc.Xml using (var xmlReader = CreateXmlReader(new DelegatingStream(request.Body))) { var type = GetSerializableType(context.ModelType); - var xmlSerializer = CreateXmlSerializer(type); - var deserializedObject = xmlSerializer.Deserialize(xmlReader); - deserializedObject = SerializableErrorWrapper.UnwrapSerializableErrorObject(context.ModelType, deserializedObject); + + var serializer = CreateSerializer(type); + + var deserializedObject = serializer.Deserialize(xmlReader); + + // Unwrap only if the original type was wrapped. + if (type != context.ModelType) + { + var unwrappable = deserializedObject as IUnwrappable; + if (unwrappable != null) + { + deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); + } + } + return Task.FromResult(deserializedObject); } } diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs similarity index 61% rename from src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerOutputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs index b96dff87e3..cde4a7ac53 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using System.Xml; @@ -38,8 +39,18 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); WriterSettings = writerSettings; + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } + /// + /// Gets the list of to + /// provide the wrapping type for serialization. + /// + public IList WrapperProviderFactories { get; } + /// /// Gets the settings to be used by the XmlWriter. /// @@ -48,27 +59,41 @@ namespace Microsoft.AspNet.Mvc.Xml /// /// Gets the type of the object to be serialized. /// - /// The declared type. - /// The runtime type. - /// The type of the object to be serialized. - protected virtual Type GetSerializableType(Type declaredType, Type runtimeType) + /// The declared type of the object. + /// The runtime type of the object + /// A type that needs to be serialized. + protected virtual Type ResolveType(Type declaredType, Type runtimeType) { - var type = declaredType; if (declaredType == null || declaredType == typeof(object)) { if (runtimeType != null) { - type = runtimeType; + return runtimeType; } } - return SerializableErrorWrapper.CreateSerializableType(type); + return declaredType; + } + + /// + /// Gets the type to be serialized. + /// + /// The original type to be serialized + /// The original or wrapped type provided by any . + protected virtual Type GetSerializableType(Type type) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(type, isSerialization: true)); + + return wrapperProvider?.WrappingType ?? type; } /// protected override bool CanWriteType(Type declaredType, Type runtimeType) { - return CreateSerializer(GetSerializableType(declaredType, runtimeType)) != null; + var type = ResolveType(declaredType, runtimeType); + + return CreateSerializer(GetSerializableType(type)) != null; } /// @@ -115,12 +140,26 @@ namespace Microsoft.AspNet.Mvc.Xml using (var outputStream = new DelegatingStream(innerStream)) using (var xmlWriter = CreateXmlWriter(outputStream, tempWriterSettings)) { - var runtimeType = context.Object == null ? null : context.Object.GetType(); + var obj = context.Object; + var runtimeType = obj?.GetType(); - var type = GetSerializableType(context.DeclaredType, runtimeType); - var xmlSerializer = CreateSerializer(type); - var responseObject = SerializableErrorWrapper.WrapSerializableErrorObject(context.Object); - xmlSerializer.Serialize(xmlWriter, responseObject); + var resolvedType = ResolveType(context.DeclaredType, runtimeType); + + var wrappingType = GetSerializableType(resolvedType); + + // Wrap the object only if there is a wrapping type. + if (wrappingType != null && wrappingType != resolvedType) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext( + declaredType: resolvedType, + isSerialization: true)); + + obj = wrapperProvider.Wrap(obj); + } + + var xmlSerializer = CreateSerializer(wrappingType); + xmlSerializer.Serialize(xmlWriter, obj); } return Task.FromResult(true); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj index 1b773ab251..1225141df4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj @@ -14,4 +14,9 @@ 2.0 + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs index 588f1d043c..2e926dc14b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs @@ -20,9 +20,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { private readonly IServiceProvider _provider = TestHelper.CreateServices("ActionResultsWebSite"); private readonly Action _app = new Startup().Configure; - private const string sampleIntError = "The field SampleInt must be between 10 and 100."; - private const string sampleStringError = - "The field SampleString must be a string or array type with a minimum length of '15'."; [Fact] public async Task BadRequestResult_CanBeReturned() @@ -188,43 +185,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); } - [Theory] - [InlineData("http://localhost/Home/Index", - "application/json;charset=utf-8", - "{\"test.SampleInt\":[\"" + sampleIntError + "\"]," + - "\"test.SampleString\":" + - "[\"" + sampleStringError + "\"]}")] - [InlineData("http://localhost/Home/Index", - "application/xml;charset=utf-8", - "" + sampleIntError + "" + - "" + sampleStringError + - "")] - [InlineData("http://localhost/XmlSerializer/GetSerializableError", - "application/xml;charset=utf-8", - "" + sampleIntError + "" + - "" + sampleStringError + - "")] - public async Task SerializableErrorIsReturnedInExpectedFormat(string url, string outputFormat, string output) - { - // Arrange - var server = TestServer.Create(_provider, _app); - var client = server.CreateClient(); - - var input = "" + - "" + - "2foo"; - var request = new HttpRequestMessage(HttpMethod.Post, url); - request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(outputFormat)); - request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); - - // Act - var response = await client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Equal(output, await response.Content.ReadAsStringAsync()); - } - [Fact] public async Task SerializableError_CanSerializeNormalObjects() { diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs index 0dff6199ee..b1ef1e218b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs @@ -2,6 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; @@ -42,5 +46,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var actualBody = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedBody, actualBody); } + + [Fact] + public async Task SerializableErrorIsReturnedInExpectedFormat() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var input = "" + + "" + + "2foo"; + + var expectedOutput = "{\"employee.Id\":[\"The field Id must be between 10 and 100." + + "\"],\"employee.Name\":[\"The field Name must be a string or array type with" + + " a minimum length of '15'.\"]}"; + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/SerializableError/CreateEmployee"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal(expectedOutput, await response.Content.ReadAsStringAsync()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj b/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj index f97db678df..be9e29520d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj @@ -10,6 +10,12 @@ ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin\$(MSBuildProjectName)\ + + Microsoft.AspNet.Mvc.FunctionalTests + + + Microsoft.AspNet.Mvc.FunctionalTests + 2.0 diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/SerializableErrorTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/SerializableErrorTests.cs index 0111208e7f..a4cd4d740f 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/SerializableErrorTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/SerializableErrorTests.cs @@ -64,5 +64,32 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var responseData = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedXml, responseData); } + + [Theory] + [InlineData("application/xml-xmlser")] + [InlineData("application/xml-dcs")] + public async Task IsReturnedInExpectedFormat(string acceptHeader) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var input = "" + + "" + + "2foo"; + var expected = "The field Id must be between 10 and 100." + + "The field Name must be a string or array type with a minimum " + + "length of '15'."; + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/SerializableError/CreateEmployee"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(acceptHeader)); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml-dcs"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var responseData = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, responseData); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs new file mode 100644 index 0000000000..a36aa61aee --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class XmlDataContractSerializerFormattersWrappingTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(XmlFormattersWebSite)); + private readonly Action _app = new XmlFormattersWebSite.Startup().Configure; + + [Theory] + [InlineData("http://localhost/IEnumerable/ValueTypes")] + [InlineData("http://localhost/IQueryable/ValueTypes")] + public async Task CanWrite_ValueTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("" + + "1020", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes")] + public async Task CanWrite_NonWrappedTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("" + + "value1value2", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes_Empty")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes_Empty")] + public async Task CanWrite_NonWrappedTypes_Empty(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes_NullInstance")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes_NullInstance")] + public async Task CanWrite_NonWrappedTypes_NullInstance(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes")] + [InlineData("http://localhost/IQueryable/WrappedTypes")] + public async Task CanWrite_WrappedTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("" + + "3510Mike35" + + "11Jimmy", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes_Empty")] + [InlineData("http://localhost/IQueryable/WrappedTypes_Empty")] + public async Task CanWrite_WrappedTypes_Empty(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes_NullInstance")] + [InlineData("http://localhost/IQueryable/WrappedTypes_NullInstance")] + public async Task CanWrite_WrappedTypes_NullInstance(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Fact] + public async Task CanWrite_IEnumerableOf_SerializableErrors() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/IEnumerable/SerializableErrors"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-dcs")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("" + + "key1-errorkey2-error" + + "key1-errorkey2-error" + + "", + result); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs new file mode 100644 index 0000000000..b1d7968343 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class XmlSerializerFormattersWrappingTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(XmlFormattersWebSite)); + private readonly Action _app = new XmlFormattersWebSite.Startup().Configure; + + [Theory] + [InlineData("http://localhost/IEnumerable/ValueTypes")] + [InlineData("http://localhost/IQueryable/ValueTypes")] + public async Task CanWrite_ValueTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("10" + + "20", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes")] + public async Task CanWrite_NonWrappedTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("value1" + + "value2", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes_NullInstance")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes_NullInstance")] + public async Task CanWrite_NonWrappedTypes_NullInstance(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/NonWrappedTypes_Empty")] + [InlineData("http://localhost/IQueryable/NonWrappedTypes_Empty")] + public async Task CanWrite_NonWrappedTypes_Empty(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes")] + [InlineData("http://localhost/IQueryable/WrappedTypes")] + public async Task CanWrite_WrappedTypes(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("10" + + "Mike3511" + + "Jimmy35", + result); + } + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes_Empty")] + [InlineData("http://localhost/IQueryable/WrappedTypes_Empty")] + public async Task CanWrite_WrappedTypes_Empty(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + + [Theory] + [InlineData("http://localhost/IEnumerable/WrappedTypes_NullInstance")] + [InlineData("http://localhost/IQueryable/WrappedTypes_NullInstance")] + public async Task CanWrite_WrappedTypes_NullInstance(string url) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("", + result); + } + + [Fact] + public async Task CanWrite_IEnumerableOf_SerializableErrors() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/IEnumerable/SerializableErrors"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml-xmlser")); + + // Act + var response = await client.SendAsync(request); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal("key1-error" + + "key2-errorkey1-error" + + "key2-error", + result); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumerableTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumerableTest.cs new file mode 100644 index 0000000000..0f9ec100e8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumerableTest.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Collections.Generic; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class DelegatingEnumerableTest + { + [Fact] + public void CanEnumerateOn_NonWrappableElementTypes() + { + // Arrange + var numbers = new[] { 10, 20 }; + var delegatingEnumerable = new DelegatingEnumerable(numbers, elementWrapperProvider: null); + + // Act and Assert + Assert.Equal(numbers, delegatingEnumerable); + } + + [Fact] + public void DoesNotThrowOn_EmptyCollections_NonWrappableElementTypes() + { + // Arrange + var numbers = new int[] { }; + var delegatingEnumerable = new DelegatingEnumerable(numbers, elementWrapperProvider: null); + + // Act and Assert + Assert.Empty(delegatingEnumerable); + } + + [Fact] + public void CanEnumerateOn_WrappableElementTypes() + { + // Arrange + var error1 = new SerializableError(); + error1.Add("key1", "key1-error"); + var error2 = new SerializableError(); + error2.Add("key1", "key1-error"); + var errors = new[] { error1, error2 }; + var delegatingEnumerable = new DelegatingEnumerable( + errors, + new SerializableErrorWrapperProvider()); + + // Act and Assert + Assert.Equal(errors.Length, delegatingEnumerable.Count()); + + for (var i = 0; i < errors.Length; i++) + { + var errorWrapper = delegatingEnumerable.ElementAt(i); + + Assert.IsType(errorWrapper); + Assert.NotNull(errorWrapper); + Assert.Same(errors[i], errorWrapper.SerializableError); + } + } + + [Fact] + public void DoesNotThrowOn_EmptyCollections_WrappableElementTypes() + { + // Arrange + var errors = new SerializableError[] { }; + var delegatingEnumerable = new DelegatingEnumerable( + errors, new SerializableErrorWrapperProvider()); + + // Act and Assert + Assert.Empty(delegatingEnumerable); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumeratorTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumeratorTest.cs new file mode 100644 index 0000000000..a432dbc9b9 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/DelegatingEnumeratorTest.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class DelegatingEnumeratorTest + { + [Fact] + public void DisposeCalled_OnInnerEnumerator() + { + // Arrange + var innerEnumerator = new Mock>(); + innerEnumerator.Setup(innerEnum => innerEnum.Dispose()) + .Verifiable(); + var delegatingEnumerator = new DelegatingEnumerator( + innerEnumerator.Object, + wrapperProvider: null); + + // Act + delegatingEnumerator.Dispose(); + + // Assert + innerEnumerator.Verify(); + } + + [Fact] + public void MoveNextCalled_OnInnerEnumerator() + { + // Arrange + var innerEnumerator = new Mock>(); + innerEnumerator.Setup(innerEnum => innerEnum.MoveNext()) + .Verifiable(); + var delegatingEnumerator = new DelegatingEnumerator( + innerEnumerator.Object, + wrapperProvider: null); + + // Act + var available = delegatingEnumerator.MoveNext(); + + // Assert + innerEnumerator.Verify(); + } + + [Fact] + public void ResetCalled_OnInnerEnumerator() + { + // Arrange + var innerEnumerator = new Mock>(); + innerEnumerator.Setup(innerEnum => innerEnum.Reset()) + .Verifiable(); + var delegatingEnumerator = new DelegatingEnumerator( + innerEnumerator.Object, + wrapperProvider: null); + + // Act + delegatingEnumerator.Reset(); + + // Assert + innerEnumerator.Verify(); + } + + [Fact] + public void CurrentCalled_OnInnerEnumerator() + { + // Arrange + var innerEnumerator = new Mock>(); + innerEnumerator.SetupGet(innerEnum => innerEnum.Current) + .Verifiable(); + var delegatingEnumerator = new DelegatingEnumerator( + innerEnumerator.Object, + wrapperProvider: null); + + // Act + var obj = delegatingEnumerator.Current; + + // Assert + innerEnumerator.Verify(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderFactoryTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderFactoryTest.cs new file mode 100644 index 0000000000..7d6770fa31 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderFactoryTest.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Collections.Generic; +using Microsoft.AspNet.Mvc; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class EnumerableWrapperProviderFactoryTest + { + public static TheoryData EnumerableOfTInterfaceData + { + get + { + var serializableError = new SerializableError(); + serializableError.Add("key1", "key1-error"); + + return new TheoryData + { + { + typeof(IEnumerable), + new [] { "value1", "value2" }, + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + new [] { 10, 20 }, + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + new [] { new Person() { Id =10, Name = "John" } }, + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + new [] { serializableError }, + typeof(DelegatingEnumerable) + }, + }; + } + } + + [Theory] + [MemberData(nameof(EnumerableOfTInterfaceData))] + public void Creates_WrapperProvider_EnumerableOfTInterface( + Type declaredType, + object objectToBeWrapped, + Type expectedWrappingType) + { + // Arrange + var wrapperProviderFactories = GetWrapperProviderFactories(); + var enumerableWrapperProviderFactory = new EnumerableWrapperProviderFactory(wrapperProviderFactories); + var wrapperProviderContext = new WrapperProviderContext(declaredType, isSerialization: true); + + // Act + var wrapperProvider = enumerableWrapperProviderFactory.GetProvider(wrapperProviderContext); + + // Assert + Assert.NotNull(wrapperProvider); + Assert.Equal(expectedWrappingType, wrapperProvider.WrappingType); + } + + public static TheoryData QueryableOfTInterfaceData + { + get + { + var serializableError = new SerializableError(); + serializableError.Add("key1", "key1-error"); + + return new TheoryData + { + { + typeof(IEnumerable), + (new [] { "value1", "value2" }).AsQueryable(), + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + (new [] { 10, 20 }).AsQueryable(), + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + (new [] { new Person() { Id =10, Name = "John" } }).AsQueryable(), + typeof(DelegatingEnumerable) + }, + { + typeof(IEnumerable), + (new [] { serializableError }).AsQueryable(), + typeof(DelegatingEnumerable) + }, + }; + } + } + + [Theory] + [MemberData(nameof(QueryableOfTInterfaceData))] + public void Creates_WrapperProvider_QueryableOfTInterface( + Type declaredType, + object objectToBeWrapped, + Type expectedWrappingType) + { + // Arrange + var wrapperProviderFactories = GetWrapperProviderFactories(); + var enumerableWrapperProviderFactory = new EnumerableWrapperProviderFactory(wrapperProviderFactories); + var wrapperProviderContext = new WrapperProviderContext(declaredType, isSerialization: true); + + // Act + var wrapperProvider = enumerableWrapperProviderFactory.GetProvider(wrapperProviderContext); + + // Assert + Assert.NotNull(wrapperProvider); + Assert.Equal(expectedWrappingType, wrapperProvider.WrappingType); + } + + public static TheoryData ConcreteEnumerableOfTData + { + get + { + var serializableError = new SerializableError(); + serializableError.Add("key1", "key1-error"); + + return new TheoryData + { + { + typeof(string), // 'string' implements IEnumerable + "value" + }, + { + typeof(List), + (new [] { 10, 20 }).ToList() + }, + { + typeof(List), + (new [] { new Person() { Id =10, Name = "John" } }).ToList() + }, + { + typeof(List), + (new [] { serializableError }).ToList() + }, + { + typeof(PersonList), + new PersonList() + }, + }; + } + } + + [Theory] + [MemberData(nameof(ConcreteEnumerableOfTData))] + public void DoesNot_CreateWrapperProvider_ForConcrete_EnumerableOfTImplementations( + Type declaredType, + object objectToBeWrapped) + { + // Arrange + var wrapperProviderFactories = GetWrapperProviderFactories(); + var enumerableWrapperProviderFactory = new EnumerableWrapperProviderFactory(wrapperProviderFactories); + var wrapperProviderContext = new WrapperProviderContext(declaredType, isSerialization: true); + + // Act + var wrapperProvider = enumerableWrapperProviderFactory.GetProvider(wrapperProviderContext); + + // Assert + Assert.Null(wrapperProvider); + } + + private IEnumerable GetWrapperProviderFactories() + { + var wrapperProviderFactories = new List(); + wrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(wrapperProviderFactories)); + wrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + + return wrapperProviderFactories; + } + + internal class Person + { + public int Id { get; set; } + + public string Name { get; set; } + } + + internal class PersonList : List + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderTest.cs new file mode 100644 index 0000000000..c6b23f071a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/EnumerableWrapperProviderTest.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNet.Mvc; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class EnumerableWrapperProviderTest + { + [Theory] + [InlineData(typeof(IEnumerable), + typeof(DelegatingEnumerable))] + [InlineData(typeof(IQueryable), + typeof(DelegatingEnumerable))] + [InlineData(typeof(ICollection), + typeof(DelegatingEnumerable))] + [InlineData(typeof(IList), + typeof(DelegatingEnumerable))] + public void Gets_DelegatingWrappingType(Type declaredEnumerableOfT, Type expectedType) + { + // Arrange + var wrapperProvider = new EnumerableWrapperProvider( + declaredEnumerableOfT, + new SerializableErrorWrapperProvider()); + + // Act + var wrappingType = wrapperProvider.WrappingType; + + // Assert + Assert.NotNull(wrappingType); + Assert.Equal(expectedType, wrappingType); + } + + [Fact] + public void Wraps_EmptyCollections() + { + // Arrange + var declaredEnumerableOfT = typeof(IEnumerable); + var wrapperProvider = new EnumerableWrapperProvider( + declaredEnumerableOfT, + elementWrapperProvider: null); + + // Act + var wrapped = wrapperProvider.Wrap(new int[] { }); + + // Assert + Assert.Equal(typeof(DelegatingEnumerable), wrapperProvider.WrappingType); + Assert.NotNull(wrapped); + var delegatingEnumerable = wrapped as DelegatingEnumerable; + Assert.NotNull(delegatingEnumerable); + Assert.Equal(0, delegatingEnumerable.Count()); + } + + [Fact] + public void Ignores_NullInstances() + { + // Arrange + var declaredEnumerableOfT = typeof(IEnumerable); + var wrapperProvider = new EnumerableWrapperProvider( + declaredEnumerableOfT, + elementWrapperProvider: null); + + // Act + var wrapped = wrapperProvider.Wrap(null); + + // Assert + Assert.Equal(typeof(DelegatingEnumerable), wrapperProvider.WrappingType); + Assert.Null(wrapped); + } + + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(List))] + [InlineData(typeof(List))] + [InlineData(typeof(List))] + [InlineData(typeof(PersonList))] + public void ThrowsArugmentExceptionFor_ConcreteEnumerableOfT(Type declaredType) + { + // Arrange + var expectedMessage = "The type must be an interface and must be or derive from 'IEnumerable`1'." + + "\r\nParameter name: sourceEnumerableOfT"; + + // Act and Assert + var ex = Assert.Throws(() => new EnumerableWrapperProvider( + declaredType, + elementWrapperProvider: null)); + + Assert.Equal(expectedMessage, ex.Message); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/Models/Person.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/Models/Person.cs new file mode 100644 index 0000000000..5314dfd257 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/Models/Person.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class Person + { + public int Id { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/Models/PersonList.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/Models/PersonList.cs new file mode 100644 index 0000000000..52f97a0b04 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/Models/PersonList.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class PersonList : List + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapper.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapper.cs new file mode 100644 index 0000000000..06690c1895 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapper.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class PersonWrapper : IUnwrappable + { + public PersonWrapper() { } + + public PersonWrapper(Person person) + { + Id = person.Id; + Name = person.Name; + Age = 35; + } + + public int Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } + + public override string ToString() + { + return string.Format("{0}, {1}, {2}", Id, Name, Age); + } + + public object Unwrap(Type declaredType) + { + return new Person() { Id = this.Id, Name = this.Name }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProvider.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProvider.cs new file mode 100644 index 0000000000..dfdd36ad39 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class PersonWrapperProvider : IWrapperProvider + { + public object Wrap(object obj) + { + var person = obj as Person; + + if (person == null) + { + return obj; + } + + return new PersonWrapper(person); + } + + public Type WrappingType + { + get + { + return typeof(PersonWrapper); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProviderFactory.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProviderFactory.cs new file mode 100644 index 0000000000..d341a2794f --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/PersonWrapperProviderFactory.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class PersonWrapperProviderFactory : IWrapperProviderFactory + { + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context.DeclaredType == typeof(Person)) + { + return new PersonWrapperProvider(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableErrorWrapperProviderTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableErrorWrapperProviderTest.cs new file mode 100644 index 0000000000..d7acd20e60 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableErrorWrapperProviderTest.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Microsoft.AspNet.Mvc; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class SerializableErrorWrapperProviderTest + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Gets_SerializableErrorWrapper_AsWrappingType(bool isSerialization) + { + // Arrange + var wrapperProvider = new SerializableErrorWrapperProvider(); + + // Act and Assert + Assert.Equal(typeof(SerializableErrorWrapper), wrapperProvider.WrappingType); + } + + [Fact] + public void Wraps_SerializableErrorInstance() + { + // Arrange + var wrapperProvider = new SerializableErrorWrapperProvider(); + var serializableError = new SerializableError(); + + // Act + var wrapped = wrapperProvider.Wrap(serializableError); + + // Assert + Assert.NotNull(wrapped); + var errorWrapper = wrapped as SerializableErrorWrapper; + Assert.NotNull(errorWrapper); + Assert.Same(serializableError, errorWrapper.SerializableError); + } + + [Fact] + public void ThrowsExceptionOn_NonSerializableErrorInstances() + { + // Arrange + var wrapperProvider = new SerializableErrorWrapperProvider(); + var person = new Person() { Id = 10, Name = "John" }; + var expectedMessage = string.Format("The object to be wrapped must be of type '{0}'" + + " but was of type 'Person'.\r\nParameter name: original", + typeof(SerializableErrorWrapper).Name); + + // Act and Assert + var exception = Assert.Throws(() => wrapperProvider.Wrap(person)); + Assert.Equal(expectedMessage, exception.Message); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableWrapperProviderFactoryTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableWrapperProviderFactoryTest.cs new file mode 100644 index 0000000000..785fb61247 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/SerializableWrapperProviderFactoryTest.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Xml +{ + public class SerializableWrapperProviderFactoryTest + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Creates_WrapperProvider_ForSerializableErrorType(bool isSerialization) + { + // Arrange + var serializableErroWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); + + // Act + var wrapperProvider = serializableErroWrapperProviderFactory.GetProvider( + new WrapperProviderContext(typeof(SerializableError), isSerialization)); + + // Assert + Assert.NotNull(wrapperProvider); + Assert.Equal(typeof(SerializableErrorWrapper), wrapperProvider.WrappingType); + } + + [Fact] + public void ReturnsNullFor_NonSerializableErrorTypes() + { + // Arrange + var serializableErroWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); + + // Act + var wrapperProvider = serializableErroWrapperProviderFactory.GetProvider( + new WrapperProviderContext(typeof(Person), isSerialization: true)); + + // Assert + Assert.Null(wrapperProvider); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 52b2d20ef7..0785d58fe4 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -15,7 +15,7 @@ using Microsoft.AspNet.Testing; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.Xml { public class XmlDataContractSerializerInputFormatterTest { @@ -345,26 +345,6 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(expectedString, levelOneModel.sampleString); } - [Fact] - public async Task ReadAsync_ReadsSerializableErrorXml() - { - // Arrange - var serializableErrorXml = "" + - "Test Error 1 Test Error 2Test Error 3"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(serializableErrorXml); - var context = GetInputFormatterContext(contentBytes, typeof(SerializableError)); - - // Act - var model = await formatter.ReadAsync(context); - - // Assert - var serializableError = model as SerializableError; - Assert.NotNull(serializableError); - Assert.Equal("Test Error 1 Test Error 2", serializableError["key1"]); - Assert.Equal("Test Error 3", serializableError["key2"]); - } - [Fact] public async Task ReadAsync_ThrowsWhenNotConfiguredWithRootName() { diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs index 9a526b6ea6..31d97a6946 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs @@ -15,7 +15,7 @@ using Microsoft.Net.Http.Headers; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.Xml { public class XmlDataContractSerializerOutputFormatterTest { @@ -134,7 +134,7 @@ namespace Microsoft.AspNet.Mvc var sampleInput = new DummyClass { SampleInt = 10 }; var formatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); var formatter = new XmlDataContractSerializerOutputFormatter(writerSettings); - var expectedOutput = ""+ + var expectedOutput = "" + "" + "10"; @@ -222,7 +222,7 @@ namespace Microsoft.AspNet.Mvc Assert.Equal("" + "" + "10", - new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, + new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8).ReadToEnd()); } @@ -348,7 +348,7 @@ namespace Microsoft.AspNet.Mvc [Theory] [MemberData(nameof(TypesForGetSupportedContentTypes))] - public void GetSupportedContentTypes_ReturnsSupportedTypes(Type declaredType, + public void GetSupportedContentTypes_ReturnsSupportedTypes(Type declaredType, Type runtimeType, object expectedOutput) { // Arrange @@ -378,7 +378,7 @@ namespace Microsoft.AspNet.Mvc var outputFormatterContext = GetOutputFormatterContext(sampleInput, typeof(DummyClass)); // Act & Assert - await Assert.ThrowsAsync(typeof(SerializationException), + await Assert.ThrowsAsync(typeof(SerializationException), async () => await formatter.WriteAsync(outputFormatterContext)); } @@ -394,7 +394,7 @@ namespace Microsoft.AspNet.Mvc var outputFormatterContext = GetOutputFormatterContext(parent, parent.GetType()); // Act & Assert - await Assert.ThrowsAsync(typeof(SerializationException), + await Assert.ThrowsAsync(typeof(SerializationException), async () => await formatter.WriteAsync(outputFormatterContext)); } diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs index cfadaba9a0..4f1dab7dc2 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs @@ -15,7 +15,7 @@ using Microsoft.AspNet.Testing; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.Xml { public class XmlSerializerInputFormatterTest { @@ -346,26 +346,6 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), levelOneModel.SampleDate); } - [Fact] - public async Task ReadAsync_ReadsSerializableErrorXml() - { - // Arrange - var serializableErrorXml = "" + - "Test Error 1 Test Error 2Test Error 3"; - var formatter = new XmlSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(serializableErrorXml); - var context = GetInputFormatterContext(contentBytes, typeof(SerializableError)); - - // Act - var model = await formatter.ReadAsync(context); - - // Assert - var serializableError = model as SerializableError; - Assert.NotNull(serializableError); - Assert.Equal("Test Error 1 Test Error 2", serializableError["key1"]); - Assert.Equal("Test Error 3", serializableError["key2"]); - } - private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType) { var actionContext = GetActionContext(contentBytes); diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs index cf54c35344..0a94a3b23c 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; @@ -13,7 +14,7 @@ using Microsoft.AspNet.Mvc.Xml; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.Xml { public class XmlSerializerOutputFormatterTest { @@ -243,7 +244,7 @@ namespace Microsoft.AspNet.Mvc Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead); } - + public static IEnumerable TypesForCanWriteResult { get @@ -257,6 +258,10 @@ namespace Microsoft.AspNet.Mvc new Dictionary { { "Hello", "world" } }, typeof(object), false }; yield return new object[] { new Dictionary { { "Hello", "world" } }, typeof(Dictionary), false }; + yield return new object[] { + new[] {"value1", "value2"}, typeof(IEnumerable), true }; + yield return new object[] { + Enumerable.Range(1, 2).Select(i => "value" + i).AsQueryable(), typeof(IQueryable), true }; } } diff --git a/test/WebSites/ActionConstraintsWebSite/ActionConstraintsWebSite.kproj b/test/WebSites/ActionConstraintsWebSite/ActionConstraintsWebSite.kproj index 6dc9eaf4ae..cb32117971 100644 --- a/test/WebSites/ActionConstraintsWebSite/ActionConstraintsWebSite.kproj +++ b/test/WebSites/ActionConstraintsWebSite/ActionConstraintsWebSite.kproj @@ -15,4 +15,9 @@ 41642 + + + + + \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs b/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs new file mode 100644 index 0000000000..aa719893d0 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc; + +namespace FormatterWebSite.Controllers +{ + public class SerializableErrorController : Controller + { + [HttpPost] + public IActionResult CreateEmployee([FromBody] Employee employee) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Content("Hello World!"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/Employee.cs b/test/WebSites/FormatterWebSite/Models/Employee.cs new file mode 100644 index 0000000000..71ea460ab2 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/Employee.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; + +namespace FormatterWebSite +{ + public class Employee + { + [Range(10, 100)] + public int Id { get; set; } + + [MinLength(15)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/PrecompilationWebSite/Views/Home/Index.cshtml b/test/WebSites/PrecompilationWebSite/Views/Home/Index.cshtml index 77a9e96d2e..679f69ca75 100644 --- a/test/WebSites/PrecompilationWebSite/Views/Home/Index.cshtml +++ b/test/WebSites/PrecompilationWebSite/Views/Home/Index.cshtml @@ -1 +1 @@ -index:@GetType().GetTypeInfo().Assembly.GetName() \ No newline at end of file +index:@GetType().GetTypeInfo().Assembly.GetName() \ No newline at end of file diff --git a/test/WebSites/PrecompilationWebSite/Views/Home/Layout.cshtml b/test/WebSites/PrecompilationWebSite/Views/Home/Layout.cshtml index 75fe916035..1a03bb0226 100644 --- a/test/WebSites/PrecompilationWebSite/Views/Home/Layout.cshtml +++ b/test/WebSites/PrecompilationWebSite/Views/Home/Layout.cshtml @@ -1,2 +1,2 @@ Layout:@GetType().GetTypeInfo().Assembly.FullName -@RenderBody() \ No newline at end of file +@RenderBody() \ No newline at end of file diff --git a/test/WebSites/PrecompilationWebSite/Views/Home/_ViewStart.cshtml b/test/WebSites/PrecompilationWebSite/Views/Home/_ViewStart.cshtml index 70e372c10e..71a6696b86 100644 --- a/test/WebSites/PrecompilationWebSite/Views/Home/_ViewStart.cshtml +++ b/test/WebSites/PrecompilationWebSite/Views/Home/_ViewStart.cshtml @@ -1,4 +1,4 @@ @using System.Reflection @{ Layout = "/views/Home/Layout.cshtml";} _viewstart:@GetType().GetTypeInfo().Assembly.FullName - \ No newline at end of file + \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs index 1652e561cb..0e02811166 100644 --- a/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs +++ b/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Mvc; namespace XmlFormattersWebSite diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs new file mode 100644 index 0000000000..eea36e1596 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.Mvc; +using XmlFormattersWebSite.Models; + +namespace XmlFormattersWebSite.Controllers +{ + public class IEnumerableController : Controller + { + public IEnumerable ValueTypes() + { + return new[] { 10, 20 }; + } + + public IEnumerable NonWrappedTypes() + { + return new[] { "value1", "value2" }; + } + + public IEnumerable NonWrappedTypes_Empty() + { + return new string[] { }; + } + + public IEnumerable WrappedTypes() + { + return new[] { + new Person() { Id = 10, Name = "Mike" }, + new Person() { Id = 11, Name = "Jimmy" } + }; + } + + public IEnumerable WrappedTypes_Empty() + { + return new Person[] { }; + } + + public IEnumerable NonWrappedTypes_NullInstance() + { + return null; + } + + public IEnumerable WrappedTypes_NullInstance() + { + return null; + } + + public IEnumerable SerializableErrors() + { + List errors = new List(); + var error1 = new SerializableError(); + error1.Add("key1", "key1-error"); + error1.Add("key2", "key2-error"); + + var error2 = new SerializableError(); + error2.Add("key3", "key1-error"); + error2.Add("key4", "key2-error"); + errors.Add(error1); + errors.Add(error2); + return errors; + } + } +} diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs new file mode 100644 index 0000000000..41ed4f052b --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.Mvc; +using XmlFormattersWebSite.Models; + +namespace XmlFormattersWebSite.Controllers +{ + public class IQueryableController : Controller + { + public IQueryable ValueTypes() + { + return Enumerable.Range(1, 2).Select(i => i * 10).AsQueryable(); + } + + public IQueryable NonWrappedTypes() + { + return Enumerable.Range(1, 2).Select(i => "value" + i).AsQueryable(); + } + + public IQueryable WrappedTypes() + { + return new[] { + new Person() { Id = 10, Name = "Mike" }, + new Person() { Id = 11, Name = "Jimmy" } + }.AsQueryable(); + } + + public IQueryable WrappedTypes_Empty() + { + return (new Person[] { }).AsQueryable(); + } + + public IQueryable NonWrappedTypes_Empty() + { + return (new string[] { }).AsQueryable(); + } + + public IQueryable NonWrappedTypes_NullInstance() + { + return null; + } + + public IQueryable WrappedTypes_NullInstance() + { + return null; + } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs index 7cc7f24a31..1d1a0a7f5f 100644 --- a/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs +++ b/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNet.Mvc; +using XmlFormattersWebSite.Models; namespace XmlFormattersWebSite.Controllers { @@ -33,5 +34,16 @@ namespace XmlFormattersWebSite.Controllers { return serializableError; } + + [HttpPost] + public IActionResult CreateEmployee([FromBody] Employee employee) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Content("Hello World!"); + } } } diff --git a/test/WebSites/XmlFormattersWebSite/Models/Employee.cs b/test/WebSites/XmlFormattersWebSite/Models/Employee.cs new file mode 100644 index 0000000000..c055a5fa73 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Models/Employee.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; + +namespace XmlFormattersWebSite.Models +{ + public class Employee + { + [Range(10, 100)] + public int Id { get; set; } + + [MinLength(15)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/Models/Person.cs b/test/WebSites/XmlFormattersWebSite/Models/Person.cs new file mode 100644 index 0000000000..d6ed553502 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Models/Person.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace XmlFormattersWebSite.Models +{ + public class Person + { + public int Id { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs b/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs new file mode 100644 index 0000000000..407f45b545 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using XmlFormattersWebSite.Models; +using Microsoft.AspNet.Mvc.Xml; + +namespace XmlFormattersWebSite +{ + public class PersonWrapper : IUnwrappable + { + public PersonWrapper() { } + + public PersonWrapper(Person person) + { + Id = person.Id; + Name = person.Name; + Age = 35; + } + + public int Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } + + public override string ToString() + { + return string.Format("{0}, {1}, {2}", Id, Name, Age); + } + + public object Unwrap(Type declaredType) + { + return new Person() { Id = this.Id, Name = this.Name }; + } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs b/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs new file mode 100644 index 0000000000..4a0959af10 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc.Xml; +using XmlFormattersWebSite.Models; + +namespace XmlFormattersWebSite +{ + public class PersonWrapperProvider : IWrapperProvider + { + public object Wrap(object obj) + { + var person = obj as Person; + + if (person == null) + { + return obj; + } + + return new PersonWrapper(person); + } + + public Type WrappingType + { + get + { + return typeof(PersonWrapper); + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs b/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs new file mode 100644 index 0000000000..47e3525801 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc.Xml; +using XmlFormattersWebSite.Models; + +namespace XmlFormattersWebSite +{ + public class PersonWrapperProviderFactory : IWrapperProviderFactory + { + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context.DeclaredType == typeof(Person)) + { + return new PersonWrapperProvider(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/Startup.cs b/test/WebSites/XmlFormattersWebSite/Startup.cs index 1f37168028..655594bac4 100644 --- a/test/WebSites/XmlFormattersWebSite/Startup.cs +++ b/test/WebSites/XmlFormattersWebSite/Startup.cs @@ -61,6 +61,11 @@ namespace XmlFormattersWebSite options.InputFormatters.Add(xmlSerializerInputFormatter); options.OutputFormatters.Add(dcsOutputFormatter); options.OutputFormatters.Add(xmlSerializerOutputFormatter); + + xmlSerializerInputFormatter.WrapperProviderFactories.Add(new PersonWrapperProviderFactory()); + xmlSerializerOutputFormatter.WrapperProviderFactories.Add(new PersonWrapperProviderFactory()); + dcsInputFormatter.WrapperProviderFactories.Add(new PersonWrapperProviderFactory()); + dcsOutputFormatter.WrapperProviderFactories.Add(new PersonWrapperProviderFactory()); }); }); @@ -71,7 +76,6 @@ namespace XmlFormattersWebSite { routes.MapRoute("ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); - }); } } diff --git a/test/WebSites/XmlFormattersWebSite/project.json b/test/WebSites/XmlFormattersWebSite/project.json index 1066442200..e9c67fee0a 100644 --- a/test/WebSites/XmlFormattersWebSite/project.json +++ b/test/WebSites/XmlFormattersWebSite/project.json @@ -14,7 +14,11 @@ }, "frameworks": { "aspnet50": { }, - "aspnetcore50": { } + "aspnetcore50": { + "dependencies": { + "System.Linq.Queryable": "4.0.0-beta-*" + } + } }, "webroot": "wwwroot" }