Skip to content

[API Proposal]: DataContract API's to enable external Schema Import/Export package #72242

Closed
@StephenMolloy

Description

@StephenMolloy

Background and motivation

We're trying to bring DCS and friends back to par with .Net 4.8 in this release. One of the capabilities of the DCS library in .Net 4.8 is to generate XSD schemas or import XSD schemas into usable code. This functionality is used to support WCF and svcutil. The code that handles schema importing however, had a dependency on System.CodeDom. After some extended discussion around the goals and motivations, we decided to take a two-pronged approach here. The existing DCS assembly in the runtime will be recieving updates to align with 4.8 and to support this schema work under the covers... but the top-level functionality (and public surface area) of the schema support will live in a separate package in dotnet/runtime that is not part of the runtime. (Like the System.CodeDom package it depends on.) See the PR #71752 and other half of the API review #72243.

Schema support in 4.8 relied heavily on DCS internals, which wasn't a problem since those and CodeDom were all there. To do this schema work in an external package though, we need to expose some API points from the System.Runtime.Serialization namespace. I've tried to keep these to a minimum, and at a somewhat logical and abstract level so they don't look too contrived or out of place for a public API. But the intent here is to support what we need for XSD schema processing - not to open up the internals of DCS anymore than we need to.

Classes being exposed by this proposal are as follows:

  • DataContractSet
  • DataContract
  • XmlDataContract
  • DataMember

Additionally, a new interface is being added, and the DataContractJsonSerializer is receiving an extension method that the Xml DCS already had for supplying surrogate providers.

API Proposal

namespace System.Runtime.Serialization
{
    public sealed class DataContractSet
    {
        [RequiresUnreferencedCode("")]
        public DataContractSet(DataContractSet dataContractSet);
        public DataContractSet(ISerializationSurrogateProvider? dataContractSurrogate, ICollection<Type>? referencedTypes, ICollection<Type>? referencedCollectionTypes);

        public Dictionary<XmlQualifiedName, DataContract> Contracts { get; }
        public Dictionary<XmlQualifiedName, DataContract>? KnownTypesForObject { get; }
        public Dictionary<DataContract, object> ProcessedContracts { get; }
        public Hashtable SurrogateData { get; }

        [RequiresUnreferencedCode("")]
        public void Add(Type type);
        [RequiresUnreferencedCode("")]
        public void ExportSchemaSet(XmlSchemaSet schemaSet);
        [RequiresUnreferencedCode("")]
        public DataContract GetDataContract(Type type);
        [RequiresUnreferencedCode("")]
        public DataContract? GetDataContract(XmlQualifiedName key);
        [RequiresUnreferencedCode("")]
        public Type? GetReferencedType(XmlQualifiedName stableName, DataContract dataContract, out DataContract? referencedContract, out object[]? genericParameters, bool? supportGenericTypes = null);
        [RequiresUnreferencedCode("")]
        public void ImportSchemaSet(XmlSchemaSet schemaSet, ICollection<XmlQualifiedName>? typeNames, bool importXmlDataType);
        [RequiresUnreferencedCode("")]
        public IList<XmlQualifiedName> ImportSchemaSet(XmlSchemaSet schemaSet, ICollection<XmlSchemaElement> elements, bool importXmlDataType);
    }

    public abstract class DataContract
    {
        public virtual bool IsBuiltInDataContract { get; }
        [DynamicallyAccessedMembers(*)]
        public virtual Type UnderlyingType { get; }
        public virtual XmlQualifiedName StableName { get; }
        public virtual Type OriginalUnderlyingType { get; }
        public virtual List<DataMember>? Members { get; }
        public virtual Dictionary<XmlQualifiedName, DataContract>? KnownDataContracts { get; set; }
        public virtual bool IsValueType { get; }
        public virtual bool IsReference { get; }
        public virtual bool IsISerializable { get; }
        public virtual XmlDictionaryString? TopLevelElementName { get; }
        public virtual XmlDictionaryString? TopLevelElementNamespace { get; }
        public virtual DataContract? BaseContract { get; }
        public virtual string? ContractType { get; }

        public static string EncodeLocalName(string localName);
        [RequiresUnreferencedCode("")]
        public static DataContract? GetBuiltInDataContract(string name, string ns);
        [RequiresUnreferencedCode("")]
        public static DataContract GetDataContract(Type type);
        [RequiresUnreferencedCode("")]
        public static XmlQualifiedName GetStableName(Type type);
        [RequiresUnreferencedCode("")]
        public static Type GetSurrogateType(ISerializationSurrogateProvider surrogateProvider, Type type);
        [RequiresUnreferencedCode("")]
        public static bool IsTypeSerializable(Type type);
        [RequiresUnreferencedCode("")]
        public virtual XmlQualifiedName GetArrayTypeName(bool isNullable);
        public virtual bool IsKeyValue(out string? keyName, out string? valueName, out string? itemName);
    }

    public sealed class XmlDataContract : DataContract
    {
        public bool HasRoot { get; }
        public bool IsAnonymous { get; }
        public bool IsTopLevelElementNullable { get; }
        public bool IsTypeDefinedOnImport { get; set; }
        public bool IsValueType { get; set; }
        public XmlSchemaType? XsdType { get; }
    }

    public sealed class DataMember
    {
        public bool EmitDefaultValue { get; }
        public bool IsNullable { get; }
        public bool IsRequired { get; }
        public DataContract MemberTypeContract { get; }
        public string Name { get; }
        public long Order { get; }
    }

    public interface ISerializationExtendedSurrogateProvider : ISerializationSurrogateProvider
    {
        object? GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType);
        object? GetCustomDataToExport(Type clrType, Type dataContractType);
        void GetKnownCustomDataTypes(Collection<Type> customDataTypes);
        Type? GetReferencedTypeOnImport(string typeName, string typeNamespace, object? customData);
    }
}

namespace System.Runtime.Serialization.Json
{
    public static class DataContractJsonSerializerExtensions
    {
        public static System.Runtime.Serialization.ISerializationSurrogateProvider? GetSerializationSurrogateProvider(this DataContractJsonSerializer serializer);
        public static void SetSerializationSurrogateProvider(this DataContractJsonSerializer serializer, System.Runtime.Serialization.ISerializationSurrogateProvider? provider);
    }
}

API Usage

I'm not sure where to begin on this one. The schema support package needs to reason about and manage DataContracts, and these API's allow it to do so without exposing all the internals.

Alternative Designs

No response

Risks

These are largely methods that existed as-is or very close to how they are in 4.8. But they were internal before, not public. I do believe they reasonably support what we need. I expect there will be quite a bit of discussion though as it is a large and publicly un-proven surface we are opening up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-SerializationblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions