Use compat flag to drive XML ProblemDetails formatting

This commit is contained in:
Pranav K 2018-10-11 10:46:29 -07:00
parent 164d14064c
commit a40c1f2d02
33 changed files with 1505 additions and 318 deletions

View File

@ -3,7 +3,7 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -14,6 +14,29 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public static class MvcXmlMvcBuilderExtensions
{
/// <summary>
/// Adds configuration of <see cref="MvcXmlOptions"/> for the application.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
public static IMvcBuilder AddXmlOptions(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
@ -30,6 +53,31 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddXmlDataContractSerializerFormatters(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
AddXmlDataContractSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
@ -46,18 +94,44 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddXmlSerializerFormatters(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
AddXmlSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
// Internal for testing.
internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlDataContractSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlDataContractSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
// Internal for testing.
internal static void AddXmlSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
}
}

View File

@ -3,7 +3,7 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -14,6 +14,30 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public static class MvcXmlMvcCoreBuilderExtensions
{
/// <summary>
/// Adds configuration of <see cref="MvcXmlOptions"/> for the application.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlOptions(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
@ -30,6 +54,31 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlDataContractSerializerFormatters(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
AddXmlDataContractSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
@ -46,18 +95,44 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// /// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlSerializerFormatters(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
AddXmlSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
// Internal for testing.
internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlDataContractSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlDataContractSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
// Internal for testing.
internal static void AddXmlSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Provides configuration for XML formatters.
/// </summary>
public class MvcXmlOptions : IEnumerable<ICompatibilitySwitch>
{
private readonly CompatibilitySwitch<bool> _allowRfc7807CompliantProblemDetailsFormat;
private readonly IReadOnlyList<ICompatibilitySwitch> _switches;
/// <summary>
/// Creates a new instance of <see cref="MvcXmlOptions"/>.
/// </summary>
public MvcXmlOptions()
{
_allowRfc7807CompliantProblemDetailsFormat = new CompatibilitySwitch<bool>(nameof(AllowRfc7807CompliantProblemDetailsFormat));
_switches = new ICompatibilitySwitch[]
{
_allowRfc7807CompliantProblemDetailsFormat,
};
}
/// <summary>
/// Gets or sets a value inidicating whether <see cref="ProblemDetails"/> and <see cref="ValidationProblemDetails"/>
/// are serialized in a format compliant with the RFC 7807 specification (https://tools.ietf.org/html/rfc7807).
/// </summary>
/// <value>
/// The default value is <see langword="true"/> if the version is
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="false"/> otherwise.
/// </value>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// </remarks>
public bool AllowRfc7807CompliantProblemDetailsFormat
{
get => _allowRfc7807CompliantProblemDetailsFormat.Value;
set => _allowRfc7807CompliantProblemDetailsFormat.Value = value;
}
public IEnumerator<ICompatibilitySwitch> GetEnumerator() => _switches.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc
{
internal sealed class MvcXmlOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions<MvcXmlOptions>
{
public MvcXmlOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
var values = new Dictionary<string, object>();
if (Version >= CompatibilityVersion.Version_2_2)
{
values[nameof(MvcXmlOptions.AllowRfc7807CompliantProblemDetailsFormat)] = true;
}
return values;
}
}
}
}

View File

@ -0,0 +1,179 @@
// Copyright (c) .NET Foundation. 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.Globalization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Wrapper class for <see cref="Mvc.ProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ProblemDetails))]
[Obsolete("This type is deprecated and will be removed in a future version")]
public class ProblemDetails21Wrapper : IXmlSerializable, IUnwrappable
{
protected static readonly string EmptyKey = SerializableErrorWrapper.EmptyKey;
public ProblemDetails21Wrapper()
: this(new ProblemDetails())
{
}
public ProblemDetails21Wrapper(ProblemDetails problemDetails)
{
ProblemDetails = problemDetails;
}
internal ProblemDetails ProblemDetails { get; }
/// <inheritdoc />
public XmlSchema GetSchema() => null;
/// <inheritdoc />
public virtual void ReadXml(XmlReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
var key = XmlConvert.DecodeName(reader.LocalName);
ReadValue(reader, key);
reader.MoveToContent();
}
reader.ReadEndElement();
}
/// <summary>
/// Reads the value for the specified <paramref name="name"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="XmlReader"/>.</param>
/// <param name="name">The name of the node.</param>
protected virtual void ReadValue(XmlReader reader, string name)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
var value = reader.ReadInnerXml();
switch (name)
{
case "Detail":
ProblemDetails.Detail = value;
break;
case "Instance":
ProblemDetails.Instance = value;
break;
case "Status":
ProblemDetails.Status = string.IsNullOrEmpty(value) ?
(int?)null :
int.Parse(value, CultureInfo.InvariantCulture);
break;
case "Title":
ProblemDetails.Title = value;
break;
case "Type":
ProblemDetails.Type = value;
break;
default:
if (string.Equals(name, EmptyKey, StringComparison.Ordinal))
{
name = string.Empty;
}
ProblemDetails.Extensions.Add(name, value);
break;
}
}
/// <inheritdoc />
public virtual void WriteXml(XmlWriter writer)
{
if (!string.IsNullOrEmpty(ProblemDetails.Detail))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Detail"),
ProblemDetails.Detail);
}
if (!string.IsNullOrEmpty(ProblemDetails.Instance))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Instance"),
ProblemDetails.Instance);
}
if (ProblemDetails.Status.HasValue)
{
writer.WriteStartElement(XmlConvert.EncodeLocalName("Status"));
writer.WriteValue(ProblemDetails.Status.Value);
writer.WriteEndElement();
}
if (!string.IsNullOrEmpty(ProblemDetails.Title))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Title"),
ProblemDetails.Title);
}
if (!string.IsNullOrEmpty(ProblemDetails.Type))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Type"),
ProblemDetails.Type);
}
foreach (var keyValuePair in ProblemDetails.Extensions)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (string.IsNullOrEmpty(key))
{
key = EmptyKey;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
writer.WriteValue(value);
}
writer.WriteEndElement();
}
}
object IUnwrappable.Unwrap(Type declaredType)
{
if (declaredType == null)
{
throw new ArgumentNullException(nameof(declaredType));
}
return ProblemDetails;
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. 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.AspNetCore.Mvc.Formatters.Xml
{
internal class ProblemDetailsWrapperProviderFactory : IWrapperProviderFactory
{
private readonly MvcXmlOptions _options;
public ProblemDetailsWrapperProviderFactory(MvcXmlOptions options)
{
_options = options;
}
public IWrapperProvider GetProvider(WrapperProviderContext context)
{
if (context.DeclaredType == typeof(ProblemDetails))
{
if (_options.AllowRfc7807CompliantProblemDetailsFormat)
{
return new WrapperProvider(typeof(ProblemDetailsWrapper), p => new ProblemDetailsWrapper((ProblemDetails)p));
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
return new WrapperProvider(typeof(ProblemDetails21Wrapper), p => new ProblemDetails21Wrapper((ProblemDetails)p));
#pragma warning restore CS0618 // Type or member is obsolete
}
}
if (context.DeclaredType == typeof(ValidationProblemDetails))
{
if (_options.AllowRfc7807CompliantProblemDetailsFormat)
{
return new WrapperProvider(typeof(ValidationProblemDetailsWrapper), p => new ValidationProblemDetailsWrapper((ValidationProblemDetails)p));
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
return new WrapperProvider(typeof(ValidationProblemDetails21Wrapper), p => new ValidationProblemDetails21Wrapper((ValidationProblemDetails)p));
#pragma warning restore CS0618 // Type or member is obsolete
}
}
return null;
}
private class WrapperProvider : IWrapperProvider
{
public WrapperProvider(Type wrappingType, Func<object, object> wrapDelegate)
{
WrappingType = wrappingType;
WrapDelegate = wrapDelegate;
}
public Type WrappingType { get; }
public Func<object, object> WrapDelegate { get; }
public object Wrap(object original) => WrapDelegate(original);
}
}
}

View File

@ -0,0 +1,127 @@
// Copyright (c) .NET Foundation. 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.Serialization;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Wrapper class for <see cref="ValidationProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ValidationProblemDetails))]
[Obsolete("This type is deprecated and will be removed in a future version")]
public class ValidationProblemDetails21Wrapper : ProblemDetails21Wrapper, IUnwrappable
{
private static readonly string ErrorKey = "MVC-Errors";
/// <summary>
/// Initializes a new instance of <see cref="ValidationProblemDetailsWrapper"/>.
/// </summary>
public ValidationProblemDetails21Wrapper()
: this(new ValidationProblemDetails())
{
}
/// <summary>
/// Initializes a new instance of <see cref="ValidationProblemDetailsWrapper"/> for the specified
/// <paramref name="problemDetails"/>.
/// </summary>
/// <param name="problemDetails">The <see cref="ProblemDetails"/>.</param>
public ValidationProblemDetails21Wrapper(ValidationProblemDetails problemDetails)
: base(problemDetails)
{
ProblemDetails = problemDetails;
}
internal new ValidationProblemDetails ProblemDetails { get; }
/// <inheritdoc />
protected override void ReadValue(XmlReader reader, string name)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (string.Equals(name, ErrorKey, StringComparison.Ordinal))
{
reader.Read();
ReadErrorProperty(reader);
}
else
{
base.ReadValue(reader, name);
}
}
private void ReadErrorProperty(XmlReader reader)
{
if (reader.IsEmptyElement)
{
return;
}
while (reader.NodeType != XmlNodeType.EndElement)
{
var key = XmlConvert.DecodeName(reader.LocalName);
var value = reader.ReadInnerXml();
if (string.Equals(EmptyKey, key, StringComparison.Ordinal))
{
key = string.Empty;
}
ProblemDetails.Errors.Add(key, new[] { value });
reader.MoveToContent();
}
}
/// <inheritdoc />
public override void WriteXml(XmlWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
base.WriteXml(writer);
if (ProblemDetails.Errors.Count == 0)
{
return;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(ErrorKey));
foreach (var keyValuePair in ProblemDetails.Errors)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (string.IsNullOrEmpty(key))
{
key = EmptyKey;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
writer.WriteValue(value);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
object IUnwrappable.Unwrap(Type declaredType)
{
if (declaredType == null)
{
throw new ArgumentNullException(nameof(declaredType));
}
return ProblemDetails;
}
}
}

View File

@ -44,24 +44,5 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
return null;
}
internal static IList<IWrapperProviderFactory> GetDefaultProviderFactories()
{
var wrapperProviderFactories = new List<IWrapperProviderFactory>();
wrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
wrapperProviderFactories.Add(new WrapperProviderFactory(
typeof(ProblemDetails),
typeof(ProblemDetailsWrapper),
value => new ProblemDetailsWrapper((ProblemDetails)value)));
wrapperProviderFactories.Add(new WrapperProviderFactory(
typeof(ValidationProblemDetails),
typeof(ValidationProblemDetailsWrapper),
value => new ValidationProblemDetailsWrapper((ValidationProblemDetails)value)));
return wrapperProviderFactories;
}
}
}

View File

@ -1,50 +0,0 @@
// Copyright (c) .NET Foundation. 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.AspNetCore.Mvc.Formatters.Xml
{
internal class WrapperProviderFactory : IWrapperProviderFactory
{
public WrapperProviderFactory(Type declaredType, Type wrappingType, Func<object, object> wrapper)
{
DeclaredType = declaredType;
WrappingType = wrappingType;
Wrapper = wrapper;
}
public Type DeclaredType { get; }
public Type WrappingType { get; }
public Func<object, object> Wrapper { get; }
public IWrapperProvider GetProvider(WrapperProviderContext context)
{
if (context.DeclaredType == DeclaredType)
{
return new WrapperProvider(this);
}
return null;
}
private class WrapperProvider : IWrapperProvider
{
private readonly WrapperProviderFactory _wrapperFactory;
public WrapperProvider(WrapperProviderFactory wrapperFactory)
{
_wrapperFactory = wrapperFactory;
}
public Type WrappingType => _wrapperFactory.WrappingType;
public object Wrap(object original)
{
return _wrapperFactory.Wrapper(original);
}
}
}
}

View File

@ -46,7 +46,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_serializerSettings = new DataContractSerializerSettings();
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
}
/// <summary>

View File

@ -2,34 +2,36 @@
// 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.Linq;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// A <see cref="IConfigureOptions{TOptions}"/> implementation which will add the
/// data contract serializer formatters to <see cref="MvcOptions"/>.
/// </summary>
public class MvcXmlDataContractSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
internal sealed class XmlDataContractSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly MvcXmlOptions _xmlOptions;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Initializes a new instance of <see cref="MvcXmlDataContractSerializerMvcOptionsSetup"/>.
/// Initializes a new instance of <see cref="XmlDataContractSerializerMvcOptionsSetup"/>.
/// </summary>
/// <param name="xmlOptions"><see cref="MvcXmlOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public MvcXmlDataContractSerializerMvcOptionsSetup(ILoggerFactory loggerFactory)
public XmlDataContractSerializerMvcOptionsSetup(
IOptions<MvcXmlOptions> xmlOptions,
ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_loggerFactory = loggerFactory;
_xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <summary>
@ -40,8 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider());
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options));
var inputFormatter = new XmlDataContractSerializerInputFormatter(options);
inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.InputFormatters.Add(inputFormatter);
var outputFormatter = new XmlDataContractSerializerOutputFormatter(_loggerFactory);
outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.OutputFormatters.Add(outputFormatter);
// Do not override any user mapping
var key = "xml";

View File

@ -76,7 +76,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_serializerSettings = new DataContractSerializerSettings();
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>()
{
new SerializableErrorWrapperProviderFactory(),
};
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
_logger = loggerFactory?.CreateLogger(GetType());

View File

@ -43,7 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
}
/// <summary>

View File

@ -2,32 +2,32 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// A <see cref="IConfigureOptions{TOptions}"/> implementation which will add the
/// XML serializer formatters to <see cref="MvcOptions"/>.
/// </summary>
public class MvcXmlSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
internal sealed class XmlSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly MvcXmlOptions _xmlOptions;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Initializes a new instance of <see cref="MvcXmlSerializerMvcOptionsSetup"/>.
/// Initializes a new instance of <see cref="XmlSerializerMvcOptionsSetup"/>.
/// </summary>
/// <param name="xmlOptions"><see cref="MvcXmlOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public MvcXmlSerializerMvcOptionsSetup(ILoggerFactory loggerFactory)
public XmlSerializerMvcOptionsSetup(
IOptions<MvcXmlOptions> xmlOptions,
ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_loggerFactory = loggerFactory;
_xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <summary>
@ -46,8 +46,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
MediaTypeHeaderValues.ApplicationXml);
}
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlSerializerInputFormatter(options));
var inputFormatter = new XmlSerializerInputFormatter(options);
inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.InputFormatters.Add(inputFormatter);
var outputFormatter = new XmlSerializerOutputFormatter(_loggerFactory);
outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.OutputFormatters.Add(outputFormatter);
}
}
}

View File

@ -73,7 +73,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
WriterSettings = writerSettings;
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
_logger = loggerFactory?.CreateLogger(GetType());

View File

@ -1,42 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
public class MvcXmlDataContractSerializerMvcOptionsSetupTest
{
[Fact]
public void AddsFormatterMapping()
{
// Arrange
var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("application/xml", mappedContentType);
}
[Fact]
public void DoesNotOverrideExistingMapping()
{
// Arrange
var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("text/xml", mappedContentType);
}
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
public class MvcXmlSerializerMvcOptionsSetupTest
{
[Fact]
public void AddsFormatterMapping()
{
// Arrange
var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("application/xml", mappedContentType);
}
[Fact]
public void DoesNotOverrideExistingMapping()
{
// Arrange
var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("text/xml", mappedContentType);
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
#pragma warning disable CS0618 // Type or member is obsolete
public class ProblemDetails21WrapperTest
{
[Fact]
public void ReadXml_ReadsProblemDetailsXml()
{
// Arrange
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ProblemDetails>" +
"<Title>Some title</Title>" +
"<Status>403</Status>" +
"<Instance>Some instance</Instance>" +
"<key1>Test Value 1</key1>" +
"<_x005B_key2_x005D_>Test Value 2</_x005B_key2_x005D_>" +
"<MVC-Empty>Test Value 3</MVC-Empty>" +
"</ProblemDetails>";
var serializer = new DataContractSerializer(typeof(ProblemDetails21Wrapper));
// Act
var value = serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));
// Assert
var problemDetails = Assert.IsType<ProblemDetails21Wrapper>(value).ProblemDetails;
Assert.Equal("Some title", problemDetails.Title);
Assert.Equal("Some instance", problemDetails.Instance);
Assert.Equal(403, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Empty(kvp.Key);
Assert.Equal("Test Value 3", kvp.Value);
},
kvp =>
{
Assert.Equal("[key2]", kvp.Key);
Assert.Equal("Test Value 2", kvp.Value);
},
kvp =>
{
Assert.Equal("key1", kvp.Key);
Assert.Equal("Test Value 1", kvp.Value);
});
}
[Fact]
public void WriteXml_WritesValidXml()
{
// Arrange
var problemDetails = new ProblemDetails
{
Title = "Some title",
Detail = "Some detail",
Extensions =
{
["key1"] = "Test Value 1",
["[Key2]"] = "Test Value 2",
[""] = "Test Value 3",
},
};
var wrapper = new ProblemDetails21Wrapper(problemDetails);
var outputStream = new MemoryStream();
var expectedContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ProblemDetails>" +
"<Detail>Some detail</Detail>" +
"<Title>Some title</Title>" +
"<key1>Test Value 1</key1>" +
"<_x005B_Key2_x005D_>Test Value 2</_x005B_Key2_x005D_>" +
"<MVC-Empty>Test Value 3</MVC-Empty>" +
"</ProblemDetails>";
// Act
using (var xmlWriter = XmlWriter.Create(outputStream))
{
var dataContractSerializer = new DataContractSerializer(wrapper.GetType());
dataContractSerializer.WriteObject(xmlWriter, wrapper);
}
outputStream.Position = 0;
var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd();
// Assert
Assert.Equal(expectedContent, res);
}
}
#pragma warning restore CS0618 // Type or member is obsolete
}

View File

@ -0,0 +1,119 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
public class ProblemDetailsWrapperProviderFactoryTest
{
[Fact]
public void GetProvider_ReturnsNull_IfTypeDoesNotMatch()
{
// Arrange
var xmlOptions = new MvcXmlOptions();
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var context = new WrapperProviderContext(typeof(SerializableError), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
Assert.Null(provider);
}
[Fact]
public void GetProvider_ReturnsWrapper_ForProblemDetails()
{
// Arrange
var xmlOptions = new MvcXmlOptions { AllowRfc7807CompliantProblemDetailsFormat = true };
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var instance = new ProblemDetails();
var context = new WrapperProviderContext(instance.GetType(), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
var result = provider.Wrap(instance);
var wrapper = Assert.IsType<ProblemDetailsWrapper>(result);
Assert.Same(instance, wrapper.ProblemDetails);
}
[Fact]
public void GetProvider_Returns21CompatibleWrapper_ForProblemDetails()
{
// Arrange
var xmlOptions = new MvcXmlOptions();
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var instance = new ProblemDetails();
var context = new WrapperProviderContext(instance.GetType(), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
var result = provider.Wrap(instance);
#pragma warning disable CS0618 // Type or member is obsolete
var wrapper = Assert.IsType<ProblemDetails21Wrapper>(result);
#pragma warning restore CS0618 // Type or member is obsolete
Assert.Same(instance, wrapper.ProblemDetails);
}
[Fact]
public void GetProvider_ReturnsWrapper_ForValidationProblemDetails()
{
// Arrange
var xmlOptions = new MvcXmlOptions { AllowRfc7807CompliantProblemDetailsFormat = true };
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var instance = new ValidationProblemDetails();
var context = new WrapperProviderContext(instance.GetType(), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
var result = provider.Wrap(instance);
var wrapper = Assert.IsType<ValidationProblemDetailsWrapper>(result);
Assert.Same(instance, wrapper.ProblemDetails);
}
[Fact]
public void GetProvider_Returns21CompatibleWrapper_ForValidationProblemDetails()
{
// Arrange
var xmlOptions = new MvcXmlOptions();
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var instance = new ValidationProblemDetails();
var context = new WrapperProviderContext(instance.GetType(), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
var result = provider.Wrap(instance);
#pragma warning disable CS0618 // Type or member is obsolete
var wrapper = Assert.IsType<ValidationProblemDetails21Wrapper>(result);
#pragma warning restore CS0618 // Type or member is obsolete
Assert.Same(instance, wrapper.ProblemDetails);
}
[Fact]
public void GetProvider_ReturnsNull_ForCustomProblemDetails()
{
// Arrange
var xmlOptions = new MvcXmlOptions();
var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions);
var instance = new CustomProblemDetails();
var context = new WrapperProviderContext(instance.GetType(), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
// Assert
Assert.Null(provider);
}
private class CustomProblemDetails : ProblemDetails { }
}
}

View File

@ -56,7 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
});
}
[Fact]
public void WriteXml_WritesValidXml()
{

View File

@ -0,0 +1,228 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
#pragma warning disable CS0618 // Type or member is obsolete
public class ValidationProblemDetails21WrapperTest
{
[Fact]
public void ReadXml_ReadsValidationProblemDetailsXml()
{
// Arrange
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ValidationProblemDetails>" +
"<Title>Some title</Title>" +
"<Status>400</Status>" +
"<Instance>Some instance</Instance>" +
"<key1>Test Value 1</key1>" +
"<_x005B_key2_x005D_>Test Value 2</_x005B_key2_x005D_>" +
"<MVC-Errors>" +
"<error1>Test error 1 Test error 2</error1>" +
"<_x005B_error2_x005D_>Test error 3</_x005B_error2_x005D_>" +
"<MVC-Empty>Test error 4</MVC-Empty>" +
"</MVC-Errors>" +
"</ValidationProblemDetails>";
var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper));
// Act
var value = serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));
// Assert
var problemDetails = Assert.IsType<ValidationProblemDetails21Wrapper>(value).ProblemDetails;
Assert.Equal("Some title", problemDetails.Title);
Assert.Equal("Some instance", problemDetails.Instance);
Assert.Equal(400, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("[key2]", kvp.Key);
Assert.Equal("Test Value 2", kvp.Value);
},
kvp =>
{
Assert.Equal("key1", kvp.Key);
Assert.Equal("Test Value 1", kvp.Value);
});
Assert.Collection(
problemDetails.Errors.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Empty(kvp.Key);
Assert.Equal(new[] { "Test error 4" }, kvp.Value);
},
kvp =>
{
Assert.Equal("[error2]", kvp.Key);
Assert.Equal(new[] { "Test error 3" }, kvp.Value);
},
kvp =>
{
Assert.Equal("error1", kvp.Key);
Assert.Equal(new[] { "Test error 1 Test error 2" }, kvp.Value);
});
}
[Fact]
public void ReadXml_ReadsValidationProblemDetailsXml_WithNoErrors()
{
// Arrange
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ValidationProblemDetails>" +
"<Title>Some title</Title>" +
"<Status>400</Status>" +
"<Instance>Some instance</Instance>" +
"<key1>Test Value 1</key1>" +
"<_x005B_key2_x005D_>Test Value 2</_x005B_key2_x005D_>" +
"</ValidationProblemDetails>";
var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper));
// Act
var value = serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));
// Assert
var problemDetails = Assert.IsType<ValidationProblemDetails21Wrapper>(value).ProblemDetails;
Assert.Equal("Some title", problemDetails.Title);
Assert.Equal("Some instance", problemDetails.Instance);
Assert.Equal(400, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("key1", kvp.Key);
Assert.Equal("Test Value 1", kvp.Value);
},
kvp =>
{
Assert.Equal("[key2]", kvp.Key);
Assert.Equal("Test Value 2", kvp.Value);
});
Assert.Empty(problemDetails.Errors);
}
[Fact]
public void ReadXml_ReadsValidationProblemDetailsXml_WithEmptyErrorsElement()
{
// Arrange
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ValidationProblemDetails>" +
"<Title>Some title</Title>" +
"<Status>400</Status>" +
"<MVC-Errors />" +
"</ValidationProblemDetails>";
var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper));
// Act
var value = serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));
// Assert
var problemDetails = Assert.IsType<ValidationProblemDetails21Wrapper>(value).ProblemDetails;
Assert.Equal("Some title", problemDetails.Title);
Assert.Equal(400, problemDetails.Status);
Assert.Empty(problemDetails.Errors);
}
[Fact]
public void WriteXml_WritesValidXml()
{
// Arrange
var problemDetails = new ValidationProblemDetails
{
Title = "Some title",
Detail = "Some detail",
Extensions =
{
["key1"] = "Test Value 1",
["[Key2]"] = "Test Value 2"
},
Errors =
{
{ "error1", new[] {"Test error 1", "Test error 2" } },
{ "[error2]", new[] {"Test error 3" } },
{ "", new[] { "Test error 4" } },
}
};
var wrapper = new ValidationProblemDetails21Wrapper(problemDetails);
var outputStream = new MemoryStream();
var expectedContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ValidationProblemDetails>" +
"<Detail>Some detail</Detail>" +
"<Title>Some title</Title>" +
"<key1>Test Value 1</key1>" +
"<_x005B_Key2_x005D_>Test Value 2</_x005B_Key2_x005D_>" +
"<MVC-Errors>" +
"<error1>Test error 1 Test error 2</error1>" +
"<_x005B_error2_x005D_>Test error 3</_x005B_error2_x005D_>" +
"<MVC-Empty>Test error 4</MVC-Empty>" +
"</MVC-Errors>" +
"</ValidationProblemDetails>";
// Act
using (var xmlWriter = XmlWriter.Create(outputStream))
{
var dataContractSerializer = new DataContractSerializer(wrapper.GetType());
dataContractSerializer.WriteObject(xmlWriter, wrapper);
}
outputStream.Position = 0;
var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd();
// Assert
Assert.Equal(expectedContent, res);
}
[Fact]
public void WriteXml_WithNoValidationErrors()
{
// Arrange
var problemDetails = new ValidationProblemDetails
{
Title = "Some title",
Detail = "Some detail",
Extensions =
{
["key1"] = "Test Value 1",
["[Key2]"] = "Test Value 2"
},
};
var wrapper = new ValidationProblemDetails21Wrapper(problemDetails);
var outputStream = new MemoryStream();
var expectedContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<ValidationProblemDetails>" +
"<Detail>Some detail</Detail>" +
"<Title>Some title</Title>" +
"<key1>Test Value 1</key1>" +
"<_x005B_Key2_x005D_>Test Value 2</_x005B_Key2_x005D_>" +
"</ValidationProblemDetails>";
// Act
using (var xmlWriter = XmlWriter.Create(outputStream))
{
var dataContractSerializer = new DataContractSerializer(wrapper.GetType());
dataContractSerializer.WriteObject(xmlWriter, wrapper);
}
outputStream.Position = 0;
var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd();
// Assert
Assert.Equal(expectedContent, res);
}
}
#pragma warning restore CS0618 // Type or member is obsolete
}

View File

@ -1,34 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
public class WrapperProviderFactoryExtensionsTest
{
[Fact]
public void GetDefaultProviderFactories_GetsFactoriesUsedByInputAndOutputFormatters()
{
// Act
var factoryProviders = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
// Assert
Assert.Collection(
factoryProviders,
factory => Assert.IsType<SerializableErrorWrapperProviderFactory>(factory),
factory =>
{
var wrapperProviderFactory = Assert.IsType<WrapperProviderFactory>(factory);
Assert.Equal(typeof(ProblemDetails), wrapperProviderFactory.DeclaredType);
Assert.Equal(typeof(ProblemDetailsWrapper), wrapperProviderFactory.WrappingType);
},
factory =>
{
var wrapperProviderFactory = Assert.IsType<WrapperProviderFactory>(factory);
Assert.Equal(typeof(ValidationProblemDetails), wrapperProviderFactory.DeclaredType);
Assert.Equal(typeof(ValidationProblemDetailsWrapper), wrapperProviderFactory.WrappingType);
});
}
}
}

View File

@ -1,63 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
public class WrapperProviderFactoryTest
{
[Fact]
public void GetProvider_ReturnsNull_IfTypeDoesNotMatch()
{
// Arrange
var provider = new WrapperProviderFactory(
typeof(ProblemDetails),
typeof(ProblemDetailsWrapper),
_ => null);
var context = new WrapperProviderContext(typeof(SerializableError), isSerialization: true);
// Act
var result = provider.GetProvider(context);
// Assert
Assert.Null(result);
}
[Fact]
public void GetProvider_ReturnsNull_IfTypeIsSubtype()
{
// Arrange
var provider = new WrapperProviderFactory(
typeof(ProblemDetails),
typeof(ProblemDetailsWrapper),
_ => null);
var context = new WrapperProviderContext(typeof(ValidationProblemDetails), isSerialization: true);
// Act
var result = provider.GetProvider(context);
// Assert
Assert.Null(result);
}
[Fact]
public void GetProvider_ReturnsValue_IfTypeMatches()
{
// Arrange
var expected = new object();
var providerFactory = new WrapperProviderFactory(
typeof(ProblemDetails),
typeof(ProblemDetailsWrapper),
_ => expected);
var context = new WrapperProviderContext(typeof(ProblemDetails), isSerialization: true);
// Act
var provider = providerFactory.GetProvider(context);
var result = provider.Wrap(new ProblemDetails());
// Assert
Assert.Same(expected, result);
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
public class XmlDataContractSerializerMvcOptionsSetupTest
{
[Fact]
public void AddsFormatterMapping()
{
// Arrange
var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("application/xml", mappedContentType);
}
[Fact]
public void DoesNotOverrideExistingMapping()
{
// Arrange
var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("text/xml", mappedContentType);
}
[Fact]
public void AddsInputFormatter()
{
// Arrange
var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
Assert.IsType<XmlDataContractSerializerInputFormatter>(Assert.Single(options.InputFormatters));
}
[Fact]
public void AddsOutputFormatter()
{
// Arrange
var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
Assert.IsType<XmlDataContractSerializerOutputFormatter>(Assert.Single(options.OutputFormatters));
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
public class XmlSerializerMvcOptionsSetupTest
{
[Fact]
public void AddsFormatterMapping()
{
// Arrange
var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("application/xml", mappedContentType);
}
[Fact]
public void DoesNotOverrideExistingMapping()
{
// Arrange
var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("text/xml", mappedContentType);
}
[Fact]
public void AddsInputFormatter()
{
// Arrange
var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
Assert.IsType<XmlSerializerInputFormatter>(Assert.Single(options.InputFormatters));
}
[Fact]
public void AddsOutputFormatter()
{
// Arrange
var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
Assert.IsType<XmlSerializerOutputFormatter>(Assert.Single(options.OutputFormatters));
}
}
}

View File

@ -2,12 +2,16 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using XmlFormattersWebSite;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@ -16,10 +20,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public XmlDataContractSerializerFormattersWrappingTest(MvcTestFixture<XmlFormattersWebSite.Startup> fixture)
{
Client = fixture.CreateDefaultClient();
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(builder => builder.UseStartup<Startup>());
Client = Factory.CreateDefaultClient();
}
public HttpClient Client { get; }
public WebApplicationFactory<Startup> Factory { get; }
[ConditionalTheory]
// Mono issue - https://github.com/aspnet/External/issues/18
@ -254,6 +260,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
XmlAssert.Equal(expected, content);
}
[Fact]
public async Task ProblemDetails_With21Behavior()
{
// Arrange
var expected = "<ProblemDetails>" +
"<Instance>instance</Instance>" +
"<Status>404</Status>" +
"<Title>title</Title>" +
"<Correlation>correlation</Correlation>" +
"<Accounts>Account1 Account2</Accounts>" +
"</ProblemDetails>";
var client = Factory
.WithWebHostBuilder(builder => builder.UseStartup<StartupWith21Compat>())
.CreateDefaultClient();
// Act
var response = await client.GetAsync("/api/XmlDataContractApi/ActionReturningProblemDetails");
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.NotFound);
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
[Fact]
public async Task ValidationProblemDetails_IsSerialized()
{
@ -302,5 +333,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
[Fact]
public async Task ValidationProblemDetails_With21Behavior()
{
// Arrange
var expected = "<ValidationProblemDetails>" +
"<Detail>some detail</Detail>" +
"<Status>400</Status>" +
"<Title>One or more validation errors occurred.</Title>" +
"<Type>some type</Type>" +
"<CorrelationId>correlation</CorrelationId>" +
"<MVC-Errors>" +
"<Error1>ErrorValue</Error1>" +
"</MVC-Errors>" +
"</ValidationProblemDetails>";
var client = Factory
.WithWebHostBuilder(builder => builder.UseStartup<StartupWith21Compat>())
.CreateDefaultClient();
// Act
var response = await client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationDetailsWithMetadata");
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
}
}

View File

@ -2,11 +2,15 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Testing;
using XmlFormattersWebSite;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@ -15,9 +19,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public XmlSerializerFormattersWrappingTest(MvcTestFixture<XmlFormattersWebSite.Startup> fixture)
{
Client = fixture.CreateDefaultClient();
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(builder => builder.UseStartup<Startup>());
Client = Factory.CreateDefaultClient();
}
public WebApplicationFactory<Startup> Factory { get; }
public HttpClient Client { get; }
[Theory]
@ -208,6 +214,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
}
}
[Fact]
public async Task ProblemDetails_With21Behavior()
{
// Arrange
var expected = "<ProblemDetails>" +
"<Instance>instance</Instance>" +
"<Status>404</Status>" +
"<Title>title</Title>" +
"<Correlation>correlation</Correlation>" +
"<Accounts>Account1 Account2</Accounts>" +
"</ProblemDetails>";
var client = Factory
.WithWebHostBuilder(builder => builder.UseStartup<StartupWith21Compat>())
.CreateDefaultClient();
// Act
var response = await client.GetAsync("/api/XmlSerializerApi/ActionReturningProblemDetails");
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.NotFound);
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
[Fact]
public async Task ProblemDetails_WithExtensionMembers_IsSerialized()
{
@ -277,5 +308,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
[Fact]
public async Task ValidationProblemDetails_With21Behavior()
{
// Arrange
var expected = "<ValidationProblemDetails>" +
"<Detail>some detail</Detail>" +
"<Status>400</Status>" +
"<Title>One or more validation errors occurred.</Title>" +
"<Type>some type</Type>" +
"<CorrelationId>correlation</CorrelationId>" +
"<MVC-Errors>" +
"<Error1>ErrorValue</Error1>" +
"</MVC-Errors>" +
"</ValidationProblemDetails>";
var client = Factory
.WithWebHostBuilder(builder => builder.UseStartup<StartupWith21Compat>())
.CreateDefaultClient();
// Act
var response = await client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationDetailsWithMetadata");
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
var content = await response.Content.ReadAsStringAsync();
XmlAssert.Equal(expected, content);
}
}
}

View File

@ -5,12 +5,8 @@ using System;
using System.Buffers;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
@ -26,7 +27,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Arrange
var serviceCollection = new ServiceCollection();
AddHostingServices(serviceCollection);
serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_0);
serviceCollection
.AddMvc()
.AddXmlDataContractSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_0);
var services = serviceCollection.BuildServiceProvider();
@ -36,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
@ -50,6 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
}
[Fact]
@ -58,7 +64,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Arrange
var serviceCollection = new ServiceCollection();
AddHostingServices(serviceCollection);
serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
serviceCollection
.AddMvc()
.AddXmlDataContractSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var services = serviceCollection.BuildServiceProvider();
@ -68,6 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
@ -82,6 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
}
[Fact]
@ -90,7 +101,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Arrange
var serviceCollection = new ServiceCollection();
AddHostingServices(serviceCollection);
serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
serviceCollection
.AddMvc()
.AddXmlDataContractSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var services = serviceCollection.BuildServiceProvider();
@ -100,6 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
@ -114,6 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
}
[Fact]
@ -122,7 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Arrange
var serviceCollection = new ServiceCollection();
AddHostingServices(serviceCollection);
serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
serviceCollection
.AddMvc()
.AddXmlDataContractSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
var services = serviceCollection.BuildServiceProvider();
@ -132,6 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
@ -146,6 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
}
// This just does the minimum needed to be able to resolve these options.

View File

@ -1,9 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace XmlFormattersWebSite
{
@ -22,7 +25,10 @@ namespace XmlFormattersWebSite
// Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter
// selection. The globally configured formatters rely on custom content-type to perform conneg which does not play
// well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller.
objectResult.Formatters.Add(new XmlDataContractSerializerOutputFormatter());
var mvcOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
var xmlFormatter = mvcOptions.Value.OutputFormatters.OfType<XmlDataContractSerializerOutputFormatter>().First();
objectResult.Formatters.Add(xmlFormatter);
}
}
}

View File

@ -1,9 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace XmlFormattersWebSite
{
@ -22,7 +25,10 @@ namespace XmlFormattersWebSite
// Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter
// selection. The globally configured formatters rely on custom content-type to perform conneg which does not play
// well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller.
objectResult.Formatters.Add(new XmlSerializerOutputFormatter());
var mvcOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
var xmlFormatter = mvcOptions.Value.OutputFormatters.OfType<XmlSerializerOutputFormatter>().First();
objectResult.Formatters.Add(xmlFormatter);
}
}
}

View File

@ -13,47 +13,85 @@ namespace XmlFormattersWebSite
{
public class Startup
{
public virtual CompatibilityVersion CompatibilityVersion => CompatibilityVersion.Latest;
// Set up application services
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
.AddXmlDataContractSerializerFormatters()
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion);
services.Configure<MvcOptions>(options =>
{
options.InputFormatters.Clear();
options.OutputFormatters.Clear();
// Since both XmlSerializer and DataContractSerializer based formatters
// have supported media types of 'application/xml' and 'text/xml', it
// would be difficult for a test to choose a particular formatter based on
// request information (Ex: Accept header).
// So here we instead clear out the default supported media types and create new
// ones which are distinguishable between formatters.
var xmlSerializerInputFormatter = new XmlSerializerInputFormatter(new MvcOptions());
// request information (Ex: Accept header).
// We'll configure the ones on MvcOptions to use a distinct set of content types.
XmlSerializerInputFormatter xmlSerializerInputFormatter = null;
XmlSerializerOutputFormatter xmlSerializerOutputFormatter = null;
XmlDataContractSerializerInputFormatter dcsInputFormatter = null;
XmlDataContractSerializerOutputFormatter dcsOutputFormatter = null;
for (var i = options.InputFormatters.Count - 1; i >= 0; i--)
{
switch (options.InputFormatters[i])
{
case XmlSerializerInputFormatter formatter:
xmlSerializerInputFormatter = formatter;
break;
case XmlDataContractSerializerInputFormatter formatter:
dcsInputFormatter = formatter;
break;
default:
options.InputFormatters.RemoveAt(i);
break;
}
}
for (var i = options.OutputFormatters.Count - 1; i >= 0; i--)
{
switch (options.OutputFormatters[i])
{
case XmlSerializerOutputFormatter formatter:
xmlSerializerOutputFormatter = formatter;
break;
case XmlDataContractSerializerOutputFormatter formatter:
dcsOutputFormatter = formatter;
break;
default:
options.OutputFormatters.RemoveAt(i);
break;
}
}
xmlSerializerInputFormatter.SupportedMediaTypes.Clear();
xmlSerializerInputFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("application/xml-xmlser"));
xmlSerializerInputFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("text/xml-xmlser"));
xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-xmlser"));
xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-xmlser"));
xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml"));
var xmlSerializerOutputFormatter = new XmlSerializerOutputFormatter();
xmlSerializerOutputFormatter.SupportedMediaTypes.Clear();
xmlSerializerOutputFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("application/xml-xmlser"));
xmlSerializerOutputFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("text/xml-xmlser"));
xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-xmlser"));
xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-xmlser"));
xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml"));
var dcsInputFormatter = new XmlDataContractSerializerInputFormatter(new MvcOptions());
dcsInputFormatter.SupportedMediaTypes.Clear();
dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-dcs"));
dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-dcs"));
dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml"));
var dcsOutputFormatter = new XmlDataContractSerializerOutputFormatter();
dcsOutputFormatter.SupportedMediaTypes.Clear();
dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-dcs"));
dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-dcs"));
dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml"));
options.InputFormatters.Add(dcsInputFormatter);
options.InputFormatters.Add(xmlSerializerInputFormatter);

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc;
namespace XmlFormattersWebSite
{
public class StartupWith21Compat : Startup
{
public override CompatibilityVersion CompatibilityVersion => CompatibilityVersion.Version_2_1;
}
}