// 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; namespace Microsoft.AspNetCore.Mvc.Formatters { /// /// This class handles serialization of objects /// to XML using /// public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter { private DataContractSerializerSettings _serializerSettings; private ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); /// /// Initializes a new instance of /// with default XmlWriterSettings /// public XmlDataContractSerializerOutputFormatter() : this(FormattingUtilities.GetDefaultXmlWriterSettings()) { } /// /// Initializes a new instance of /// /// The settings to be used by the . public XmlDataContractSerializerOutputFormatter(XmlWriterSettings writerSettings) { if (writerSettings == null) { throw new ArgumentNullException(nameof(writerSettings)); } SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); WriterSettings = writerSettings; _serializerSettings = new DataContractSerializerSettings(); WrapperProviderFactories = new List(); WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } /// /// Gets the list of to /// provide the wrapping type for serialization. /// public IList WrapperProviderFactories { get; } /// /// Gets the settings to be used by the XmlWriter. /// public XmlWriterSettings WriterSettings { get; } /// /// Gets or sets the used to configure the /// . /// public DataContractSerializerSettings SerializerSettings { get { return _serializerSettings; } set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _serializerSettings = value; } } /// /// Gets the type to be serialized. /// /// The original type to be serialized /// The original or wrapped type provided by any s. 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; } /// protected override bool CanWriteType(Type type) { if (type == null) { return false; } return GetCachedSerializer(GetSerializableType(type)) != null; } /// /// Create a new instance of for the given object type. /// /// The type of object for which the serializer should be created. /// A new instance of protected virtual DataContractSerializer CreateSerializer(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } try { #if NET451 // Verify that type is a valid data contract by forcing the serializer to try to create a data contract FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); #endif // If the serializer does not support this type it will throw an exception. return new DataContractSerializer(type, _serializerSettings); } 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; } } /// /// Creates a new instance of using the given and /// . /// /// /// The underlying which the should write to. /// /// /// The . /// /// A new instance of 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); } /// 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(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(); } } /// /// Gets the cached serializer or creates and caches the serializer for the given type. /// /// The instance. protected virtual DataContractSerializer GetCachedSerializer(Type type) { object serializer; if (!_serializerCache.TryGetValue(type, out serializer)) { serializer = CreateSerializer(type); if (serializer != null) { _serializerCache.TryAdd(type, serializer); } } return (DataContractSerializer)serializer; } } }