aspnetcore/src/Microsoft.AspNetCore.Mvc.Fo.../XmlDataContractSerializerOu...

289 lines
11 KiB
C#

// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
/// <summary>
/// This class handles serialization of objects
/// to XML using <see cref="DataContractSerializer"/>
/// </summary>
public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter
{
private readonly ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
private readonly ILogger _logger;
private DataContractSerializerSettings _serializerSettings;
/// <summary>
/// Initializes a new instance of <see cref="XmlDataContractSerializerOutputFormatter"/>
/// with default <see cref="XmlWriterSettings"/>.
/// </summary>
public XmlDataContractSerializerOutputFormatter()
: this(FormattingUtilities.GetDefaultXmlWriterSettings())
{
}
/// <summary>
/// Initializes a new instance of <see cref="XmlDataContractSerializerOutputFormatter"/>
/// with default <see cref="XmlWriterSettings"/>.
/// </summary>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public XmlDataContractSerializerOutputFormatter(ILoggerFactory loggerFactory)
: this(FormattingUtilities.GetDefaultXmlWriterSettings(), loggerFactory)
{
}
/// <summary>
/// Initializes a new instance of <see cref="XmlDataContractSerializerOutputFormatter"/>.
/// </summary>
/// <param name="writerSettings">The settings to be used by the <see cref="DataContractSerializer"/>.</param>
public XmlDataContractSerializerOutputFormatter(XmlWriterSettings writerSettings)
: this(writerSettings, loggerFactory: null)
{
}
/// <summary>
/// Initializes a new instance of <see cref="XmlDataContractSerializerOutputFormatter"/>.
/// </summary>
/// <param name="writerSettings">The settings to be used by the <see cref="DataContractSerializer"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public XmlDataContractSerializerOutputFormatter(XmlWriterSettings writerSettings, ILoggerFactory loggerFactory)
{
if (writerSettings == null)
{
throw new ArgumentNullException(nameof(writerSettings));
}
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
WriterSettings = writerSettings;
_serializerSettings = new DataContractSerializerSettings();
WrapperProviderFactories = new List<IWrapperProviderFactory>();
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
_logger = loggerFactory?.CreateLogger(GetType());
}
/// <summary>
/// Gets the list of <see cref="IWrapperProviderFactory"/> to
/// provide the wrapping type for serialization.
/// </summary>
public IList<IWrapperProviderFactory> WrapperProviderFactories { get; }
/// <summary>
/// Gets the settings to be used by the XmlWriter.
/// </summary>
public XmlWriterSettings WriterSettings { get; }
/// <summary>
/// Gets or sets the <see cref="DataContractSerializerSettings"/> used to configure the
/// <see cref="DataContractSerializer"/>.
/// </summary>
public DataContractSerializerSettings SerializerSettings
{
get => _serializerSettings;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_serializerSettings = value;
}
}
/// <summary>
/// Gets the type to be serialized.
/// </summary>
/// <param name="type">The original type to be serialized</param>
/// <returns>The original or wrapped type provided by any <see cref="IWrapperProvider"/>s.</returns>
protected virtual Type GetSerializableType(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext(
type,
isSerialization: true));
return wrapperProvider?.WrappingType ?? type;
}
/// <inheritdoc />
protected override bool CanWriteType(Type type)
{
if (type == null)
{
return false;
}
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
/// Create a new instance of <see cref="DataContractSerializer"/> for the given object type.
/// </summary>
/// <param name="type">The type of object for which the serializer should be created.</param>
/// <returns>A new instance of <see cref="DataContractSerializer"/></returns>
protected virtual DataContractSerializer CreateSerializer(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
try
{
// Verify that type is a valid data contract by forcing the serializer to try to create a data contract
FormattingUtilities.XsdDataContractExporter.GetRootElementName(type);
// If the serializer does not support this type it will throw an exception.
return new DataContractSerializer(type, _serializerSettings);
}
catch (Exception ex)
{
_logger?.FailedToCreateDataContractSerializer(type.FullName, ex);
// We do not surface the caught exception because if CanWriteResult returns
// false, then this Formatter is not picked up at all.
return null;
}
}
/// <summary>
/// Creates a new instance of <see cref="XmlWriter"/> using the given <see cref="TextWriter"/> and
/// <see cref="XmlWriterSettings"/>.
/// </summary>
/// <param name="writer">
/// The underlying <see cref="TextWriter"/> which the <see cref="XmlWriter"/> should write to.
/// </param>
/// <param name="xmlWriterSettings">
/// The <see cref="XmlWriterSettings"/>.
/// </param>
/// <returns>A new instance of <see cref="XmlWriter"/></returns>
public virtual XmlWriter CreateXmlWriter(
TextWriter writer,
XmlWriterSettings xmlWriterSettings)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (xmlWriterSettings == null)
{
throw new ArgumentNullException(nameof(xmlWriterSettings));
}
// We always close the TextWriter, so the XmlWriter shouldn't.
xmlWriterSettings.CloseOutput = false;
return XmlWriter.Create(writer, xmlWriterSettings);
}
/// <summary>
/// Creates a new instance of <see cref="XmlWriter"/> using the given <see cref="TextWriter"/> and
/// <see cref="XmlWriterSettings"/>.
/// </summary>
/// <param name="context">The formatter context associated with the call.</param>
/// <param name="writer">
/// The underlying <see cref="TextWriter"/> which the <see cref="XmlWriter"/> should write to.
/// </param>
/// <param name="xmlWriterSettings">
/// The <see cref="XmlWriterSettings"/>.
/// </param>
/// <returns>A new instance of <see cref="XmlWriter"/>.</returns>
public virtual XmlWriter CreateXmlWriter(
OutputFormatterWriteContext context,
TextWriter writer,
XmlWriterSettings xmlWriterSettings)
{
return CreateXmlWriter(writer, xmlWriterSettings);
}
/// <inheritdoc />
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (selectedEncoding == null)
{
throw new ArgumentNullException(nameof(selectedEncoding));
}
var writerSettings = WriterSettings.Clone();
writerSettings.Encoding = selectedEncoding;
// Wrap the object only if there is a wrapping type.
var value = context.Object;
var wrappingType = GetSerializableType(context.ObjectType);
if (wrappingType != null && wrappingType != context.ObjectType)
{
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext(
declaredType: context.ObjectType,
isSerialization: true));
value = wrapperProvider.Wrap(value);
}
var dataContractSerializer = GetCachedSerializer(wrappingType);
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
{
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))
{
dataContractSerializer.WriteObject(xmlWriter, value);
}
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
// buffers. This is better than just letting dispose handle it (which would result in a synchronous
// write).
await textWriter.FlushAsync();
}
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="DataContractSerializer"/> instance.</returns>
protected virtual DataContractSerializer GetCachedSerializer(Type type)
{
if (!_serializerCache.TryGetValue(type, out var serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (DataContractSerializer)serializer;
}
}
}