File: System\Runtime\Serialization\XsdDataContractExporter.cs
Web Access
Project: src\src\libraries\System.Private.DataContractSerialization\src\System.Private.DataContractSerialization.csproj (System.Private.DataContractSerialization)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization.DataContracts;
using System.Xml;
using System.Xml.Schema;
 
namespace System.Runtime.Serialization
{
    /// <summary>
    /// Allows the transformation of a set of .NET types that are used in data contracts into an XML schema file (.xsd).
    /// </summary>
    /// <remarks>
    /// Use the <see cref="XsdDataContractExporter"/> class when you have created a Web service that incorporates data represented by
    /// runtime types and when you need to export XML schemas for each type to be consumed by other Web services.
    /// That is, <see cref="XsdDataContractExporter"/> transforms a set of runtime types into XML schemas. The schemas can then be exposed
    /// through a Web Services Description Language (WSDL) document for use by others who need to interoperate with your service.
    ///
    /// Conversely, if you are creating a Web service that must interoperate with an existing Web service, use the XsdDataContractImporter
    /// to transform XML schemas and create the runtime types that represent the data in a selected programming language.
    ///
    /// The <see cref="XsdDataContractExporter"/> generates an <see cref="XmlSchemaSet"/> object that contains the collection of schemas.
    /// Access the set of schemas through the <see cref="Schemas"/> property.
    /// </remarks>
    public class XsdDataContractExporter
    {
        private ExportOptions? _options;
        private XmlSchemaSet? _schemas;
        private DataContractSet? _dataContractSet;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="XsdDataContractExporter"/> class.
        /// </summary>
        public XsdDataContractExporter()
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="XsdDataContractExporter"/> class with the specified set of schemas.
        /// </summary>
        /// <param name="schemas">An <see cref="XmlSchemaSet"/> that contains the schemas to be exported.</param>
        public XsdDataContractExporter(XmlSchemaSet? schemas)
        {
            _schemas = schemas;
        }
 
        /// <summary>
        /// Gets or sets an <see cref="ExportOptions"/> that contains options that can be set for the export operation.
        /// </summary>
        public ExportOptions? Options
        {
            get { return _options; }
            set { _options = value; }
        }
 
        /// <summary>
        /// Gets the collection of exported XML schemas.
        /// </summary>
        public XmlSchemaSet Schemas
        {
            get
            {
                XmlSchemaSet schemaSet = GetSchemaSet();
                SchemaImporter.CompileSchemaSet(schemaSet);
                return schemaSet;
            }
        }
 
        private XmlSchemaSet GetSchemaSet()
        {
            if (_schemas == null)
            {
                _schemas = new XmlSchemaSet();
                _schemas.XmlResolver = null;
            }
            return _schemas;
        }
 
        private DataContractSet DataContractSet
        {
            get
            {
                return _dataContractSet ??= new DataContractSet(Options?.DataContractSurrogate, null, null);
            }
        }
 
        private static void EnsureTypeNotGeneric(Type type)
        {
            if (type.ContainsGenericParameters)
                throw new InvalidDataContractException(SR.Format(SR.GenericTypeNotExportable, type));
        }
 
        /// <summary>
        /// Transforms the types contained in the specified collection of assemblies.
        /// </summary>
        /// <param name="assemblies">A <see cref="ICollection{T}"/> (of <see cref="Assembly"/>) that contains the types to export.</param>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public void Export(ICollection<Assembly> assemblies)
        {
            ArgumentNullException.ThrowIfNull(assemblies);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                foreach (Assembly assembly in assemblies)
                {
                    if (assembly == null)
                        throw new ArgumentException(SR.Format(SR.CannotExportNullAssembly, nameof(assemblies)));
 
                    Type[] types = assembly.GetTypes();
                    for (int j = 0; j < types.Length; j++)
                        CheckAndAddType(types[j]);
                }
 
                Export();
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
 
        /// <summary>
        /// Transforms the types contained in the <see cref="ICollection{T}"/> passed to this method.
        /// </summary>
        /// <param name="types">A <see cref="ICollection{T}"/> (of <see cref="Type"/>) that contains the types to export.</param>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public void Export(ICollection<Type> types)
        {
            ArgumentNullException.ThrowIfNull(types);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                foreach (Type type in types)
                {
                    if (type == null)
                        throw new ArgumentException(SR.Format(SR.CannotExportNullType, nameof(types)));
                    AddType(type);
                }
 
                Export();
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
 
        /// <summary>
        /// Transforms the specified .NET Framework type into an XML schema definition language (XSD) schema.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to transform into an XML schema.</param>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public void Export(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                AddType(type);
                Export();
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
 
        /// <summary>
        /// Returns the contract name and contract namespace for the <see cref="Type"/>.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> that was exported.</param>
        /// <returns>An <see cref="XmlQualifiedName"/> that represents the contract name of the type and its namespace.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public XmlQualifiedName GetSchemaTypeName(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            type = GetSurrogatedType(type);
            DataContract dataContract = DataContract.GetDataContract(type);
            EnsureTypeNotGeneric(dataContract.UnderlyingType);
            if (dataContract is XmlDataContract xmlDataContract && xmlDataContract.IsAnonymous)
                return XmlQualifiedName.Empty;
            return dataContract.XmlName;
        }
 
        /// <summary>
        /// Returns the XML schema type for the specified type.
        /// </summary>
        /// <param name="type">The type to return a schema for.</param>
        /// <returns>An <see cref="XmlSchemaType"/> that contains the XML schema.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public XmlSchemaType? GetSchemaType(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            type = GetSurrogatedType(type);
            DataContract dataContract = DataContract.GetDataContract(type);
            EnsureTypeNotGeneric(dataContract.UnderlyingType);
            if (dataContract is XmlDataContract xmlDataContract && xmlDataContract.IsAnonymous)
                return xmlDataContract.XsdType;
            return null;
        }
 
        /// <summary>
        /// Returns the top-level name and namespace for the <see cref="Type"/>.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to query.</param>
        /// <returns>The <see cref="XmlQualifiedName"/> that represents the top-level name and namespace for this Type, which is written to the stream when writing this object.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public XmlQualifiedName? GetRootElementName(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            type = GetSurrogatedType(type);
            DataContract dataContract = DataContract.GetDataContract(type);
            EnsureTypeNotGeneric(dataContract.UnderlyingType);
            if (dataContract is not XmlDataContract xdc || xdc.HasRoot) // All non-XmlDataContracts "have root".
            {
                return new XmlQualifiedName(dataContract.TopLevelElementName!.Value, dataContract.TopLevelElementNamespace!.Value);
            }
            else
            {
                return null;
            }
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        private Type GetSurrogatedType(Type type)
        {
            ISerializationSurrogateProvider? surrogate = Options?.DataContractSurrogate;
            if (surrogate != null)
                type = DataContractSurrogateCaller.GetDataContractType(surrogate, type);
            return type;
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        private void CheckAndAddType(Type type)
        {
            type = GetSurrogatedType(type);
            if (!type.ContainsGenericParameters && DataContract.IsTypeSerializable(type))
                AddType(type);
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        private void AddType(Type type)
        {
            DataContractSet.Add(type);
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        private void Export()
        {
            AddKnownTypes();
            SchemaExporter exporter = new SchemaExporter(GetSchemaSet(), DataContractSet);
            exporter.Export();
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        private void AddKnownTypes()
        {
            if (Options != null)
            {
                Collection<Type> knownTypes = Options.KnownTypes;
 
                if (knownTypes != null)
                {
                    for (int i = 0; i < knownTypes.Count; i++)
                    {
                        Type type = knownTypes[i];
                        if (type == null)
                            throw new ArgumentException(SR.CannotExportNullKnownType);
                        AddType(type);
                    }
                }
            }
        }
 
        /// <summary>
        /// Gets a value that indicates whether the set of runtime types contained in a set of assemblies can be exported.
        /// </summary>
        /// <param name="assemblies">A <see cref="ICollection{T}"/> of <see cref="Assembly"/> that contains the assemblies with the types to export.</param>
        /// <returns>true if the types can be exported; otherwise, false.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public bool CanExport(ICollection<Assembly> assemblies)
        {
            ArgumentNullException.ThrowIfNull(assemblies);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                foreach (Assembly assembly in assemblies)
                {
                    if (assembly == null)
                        throw new ArgumentException(SR.Format(SR.CannotExportNullAssembly, nameof(assemblies)));
 
                    Type[] types = assembly.GetTypes();
                    for (int j = 0; j < types.Length; j++)
                        CheckAndAddType(types[j]);
                }
                AddKnownTypes();
                return true;
            }
            catch (InvalidDataContractException)
            {
                _dataContractSet = oldValue;
                return false;
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
 
        /// <summary>
        /// Gets a value that indicates whether the set of runtime types contained in a <see cref="ICollection{T}"/> can be exported.
        /// </summary>
        /// <param name="types">A <see cref="ICollection{T}"/> that contains the specified types to export.</param>
        /// <returns>true if the types can be exported; otherwise, false.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public bool CanExport(ICollection<Type> types)
        {
            ArgumentNullException.ThrowIfNull(types);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                foreach (Type type in types)
                {
                    if (type == null)
                        throw new ArgumentException(SR.Format(SR.CannotExportNullType, nameof(types)));
                    AddType(type);
                }
                AddKnownTypes();
                return true;
            }
            catch (InvalidDataContractException)
            {
                _dataContractSet = oldValue;
                return false;
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
 
        /// <summary>
        /// Gets a value that indicates whether the specified runtime type can be exported.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to export.</param>
        /// <returns>true if the type can be exported; otherwise, false.</returns>
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        public bool CanExport(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            DataContractSet? oldValue = (_dataContractSet == null) ? null : new DataContractSet(_dataContractSet);
            try
            {
                AddType(type);
                AddKnownTypes();
                return true;
            }
            catch (InvalidDataContractException)
            {
                _dataContractSet = oldValue;
                return false;
            }
            catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
            {
                _dataContractSet = oldValue;
                throw;
            }
        }
    }
}