aspnetcore/src/Microsoft.AspNet.Mvc.Format.../XmlSerializerOutputFormatte...

197 lines
7.7 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.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.AspNet.Mvc.Formatters.Xml;
using Microsoft.AspNet.Mvc.Formatters.Xml.Internal;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// This class handles serialization of objects
/// to XML using <see cref="XmlSerializer"/>
/// </summary>
public class XmlSerializerOutputFormatter : OutputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
/// <summary>
/// Initializes a new instance of <see cref="XmlSerializerOutputFormatter"/>
/// with default XmlWriterSettings.
/// </summary>
public XmlSerializerOutputFormatter() :
this(FormattingUtilities.GetDefaultXmlWriterSettings())
{
}
/// <summary>
/// Initializes a new instance of <see cref="XmlSerializerOutputFormatter"/>
/// </summary>
/// <param name="writerSettings">The settings to be used by the <see cref="XmlSerializer"/>.</param>
public XmlSerializerOutputFormatter([NotNull] XmlWriterSettings writerSettings)
{
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
WriterSettings = writerSettings;
WrapperProviderFactories = new List<IWrapperProviderFactory>();
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
}
/// <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 the type of the object to be serialized.
/// </summary>
/// <param name="declaredType">The declared type of the object.</param>
/// <param name="runtimeType">The runtime type of the object</param>
/// <returns>A type that needs to be serialized.</returns>
protected virtual Type ResolveType(Type declaredType, Type runtimeType)
{
if (declaredType == null || declaredType == typeof(object))
{
if (runtimeType != null)
{
return runtimeType;
}
}
return declaredType;
}
/// <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"/>.</returns>
protected virtual Type GetSerializableType(Type type)
{
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(
new WrapperProviderContext(type, isSerialization: true));
return wrapperProvider?.WrappingType ?? type;
}
/// <inheritdoc />
protected override bool CanWriteType(Type declaredType, Type runtimeType)
{
var type = ResolveType(declaredType, runtimeType);
if (type == null)
{
return false;
}
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
/// Create a new instance of <see cref="XmlSerializer"/> 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="XmlSerializer"/></returns>
protected virtual XmlSerializer CreateSerializer([NotNull] Type type)
{
try
{
// If the serializer does not support this type it will throw an exception.
return new XmlSerializer(type);
}
catch (Exception)
{
// 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 stream and the <see cref="WriterSettings"/>.
/// </summary>
/// <param name="writeStream">The stream on which the XmlWriter should operate on.</param>
/// <returns>A new instance of <see cref="XmlWriter"/></returns>
public virtual XmlWriter CreateXmlWriter(
[NotNull] Stream writeStream,
[NotNull] XmlWriterSettings xmlWriterSettings)
{
return XmlWriter.Create(
new HttpResponseStreamWriter(writeStream, xmlWriterSettings.Encoding),
xmlWriterSettings);
}
/// <inheritdoc />
public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context)
{
var response = context.HttpContext.Response;
var tempWriterSettings = WriterSettings.Clone();
tempWriterSettings.Encoding = context.SelectedEncoding;
using (var xmlWriter = CreateXmlWriter(context.HttpContext.Response.Body, tempWriterSettings))
{
var obj = context.Object;
var runtimeType = obj?.GetType();
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 = GetCachedSerializer(wrappingType);
xmlSerializer.Serialize(xmlWriter, obj);
}
return Task.FromResult(true);
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="XmlSerializer"/> instance.</returns>
protected virtual XmlSerializer GetCachedSerializer(Type type)
{
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (XmlSerializer)serializer;
}
}
}