aspnetcore/src/Microsoft.AspNet.Mvc.Format.../XmlSerializerInputFormatter.cs

187 lines
6.9 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.AspNet.Mvc.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// This class handles deserialization of input XML data
/// to strongly-typed objects using <see cref="XmlSerializer"/>
/// </summary>
public class XmlSerializerInputFormatter : InputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
/// <summary>
/// Initializes a new instance of XmlSerializerInputFormatter.
/// </summary>
public XmlSerializerInputFormatter()
{
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
WrapperProviderFactories = new List<IWrapperProviderFactory>();
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
}
/// <summary>
/// Gets the list of <see cref="IWrapperProviderFactory"/> to
/// provide the wrapping type for de-serialization.
/// </summary>
public IList<IWrapperProviderFactory> WrapperProviderFactories { get; }
/// <summary>
/// Indicates the acceptable input XML depth.
/// </summary>
public int MaxDepth
{
get { return _readerQuotas.MaxDepth; }
set { _readerQuotas.MaxDepth = value; }
}
/// <summary>
/// The quotas include - DefaultMaxDepth, DefaultMaxStringContentLength, DefaultMaxArrayLength,
/// DefaultMaxBytesPerRead, DefaultMaxNameTableCharCount
/// </summary>
public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas
{
get { return _readerQuotas; }
}
/// <inheritdoc />
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var effectiveEncoding = SelectCharacterEncoding(context);
if (effectiveEncoding == null)
{
return InputFormatterResult.FailureAsync();
}
var request = context.HttpContext.Request;
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
{
var type = GetSerializableType(context.ModelType);
var serializer = GetCachedSerializer(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 InputFormatterResult.SuccessAsync(deserializedObject);
}
}
/// <inheritdoc />
protected override bool CanReadType(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
/// Gets the type to which the XML will be deserialized.
/// </summary>
/// <param name="declaredType">The declared type.</param>
/// <returns>The type to which the XML will be deserialized.</returns>
protected virtual Type GetSerializableType(Type declaredType)
{
if (declaredType == null)
{
throw new ArgumentNullException(nameof(declaredType));
}
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(
new WrapperProviderContext(declaredType, isSerialization: false));
return wrapperProvider?.WrappingType ?? declaredType;
}
/// <summary>
/// Called during deserialization to get the <see cref="XmlReader"/>.
/// </summary>
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
/// <param name="encoding">The <see cref="Encoding"/> used to read the stream.</param>
/// <returns>The <see cref="XmlReader"/> used during deserialization.</returns>
protected virtual XmlReader CreateXmlReader(Stream readStream, Encoding encoding)
{
if (readStream == null)
{
throw new ArgumentNullException(nameof(readStream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null);
}
/// <summary>
/// Called during deserialization to get the <see cref="XmlSerializer"/>.
/// </summary>
/// <returns>The <see cref="XmlSerializer"/> used during deserialization.</returns>
protected virtual XmlSerializer CreateSerializer(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 CanRead returns
// false, then this Formatter is not picked up at all.
return null;
}
}
/// <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;
}
}
}