// 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 { /// /// This class handles deserialization of input XML data /// to strongly-typed objects using /// public class XmlSerializerInputFormatter : InputFormatter { private ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); /// /// Initializes a new instance of XmlSerializerInputFormatter. /// public XmlSerializerInputFormatter() { SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); WrapperProviderFactories = new List(); WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } /// /// Gets the list of to /// provide the wrapping type for de-serialization. /// public IList WrapperProviderFactories { get; } /// /// Indicates the acceptable input XML depth. /// public int MaxDepth { get { return _readerQuotas.MaxDepth; } set { _readerQuotas.MaxDepth = value; } } /// /// The quotas include - DefaultMaxDepth, DefaultMaxStringContentLength, DefaultMaxArrayLength, /// DefaultMaxBytesPerRead, DefaultMaxNameTableCharCount /// public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas { get { return _readerQuotas; } } /// public override Task 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); } } /// protected override bool CanReadType(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } return GetCachedSerializer(GetSerializableType(type)) != null; } /// /// Gets the type to which the XML will be deserialized. /// /// The declared type. /// The type to which the XML will be deserialized. 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; } /// /// Called during deserialization to get the . /// /// The from which to read. /// The used to read the stream. /// The used during deserialization. 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); } /// /// Called during deserialization to get the . /// /// The used during deserialization. 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; } } /// /// Gets the cached serializer or creates and caches the serializer for the given type. /// /// The instance. 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; } } }