Dela via


Surrogater för datakontrakt

Surrogat för datakontrakt är en avancerad funktion som bygger på datakontraktsmodellen. Den här funktionen är utformad för att användas för typanpassning och ersättning i situationer där användare vill ändra hur en typ serialiseras, deserialiseras eller projiceras till metadata. Vissa scenarier där en surrogat kan användas är när ett datakontrakt inte har angetts för typen, fält och egenskaper inte markeras med DataMemberAttribute attributet eller användare vill dynamiskt skapa schemavariationer.

Serialisering och deserialisering utförs med datakontraktets surrogat när du använder DataContractSerializer för att konvertera från .NET Framework till ett lämpligt format, till exempel XML. Surrogat för datakontrakt kan också användas för att ändra metadata som exporteras för typer, när metadatarepresentationer som XML-schemadokument (XSD) skapas. Vid import skapas kod från metadata och surrogaten kan användas i det här fallet för att anpassa den genererade koden också.

Så här fungerar surrogaten

En surrogattjänst fungerar genom att mappa en typ (den "ursprungliga" typen) till en annan typ (typen "surrogat"). I följande exempel visas den ursprungliga typen Inventory och en ny surrogattyp InventorySurrogated . Typen Inventory är inte serialiserbar men typen InventorySurrogated är:

public class Inventory
{
    public int pencils;
    public int pens;
    public int paper;
}

Eftersom ett datakontrakt inte har definierats för den här klassen konverterar du klassen till en surrogatklass med ett datakontrakt. Klassen surrogat visas i följande exempel:

[DataContract(Name = "Inventory")]
public class InventorySurrogated
{
    [DataMember]
    public int numpencils;
    [DataMember]
    public int numpaper;
    [DataMember]
    private int numpens;

    public int pens
    {
        get { return numpens; }
        set { numpens = value; }
    }
}

Implementera IDataContractSurrogate

Implementera gränssnittet för att använda surrogat för datakontraktet IDataContractSurrogate .

Följande är en översikt över varje metod för IDataContractSurrogate med en möjlig implementering.

GetDataContractType

Metoden GetDataContractType mappar en typ till en annan. Den här metoden krävs för serialisering, deserialisering, import och export.

Den första uppgiften är att definiera vilka typer som ska mappas till andra typer. Till exempel:

public Type GetDataContractType(Type type)
{
    Console.WriteLine("GetDataContractType");
    if (typeof(Inventory).IsAssignableFrom(type))
    {
        return typeof(InventorySurrogated);
    }
    return type;
}
  • Vid serialisering används mappningen som returneras av den här metoden senare för att omvandla den ursprungliga instansen GetObjectToSerialize till en surrogatinstans genom att anropa metoden.

  • Vid deserialisering används mappningen som returneras av den här metoden av serialiseraren för att deserialisera till en instans av surrogattypen. Därefter anropas GetDeserializedObject för att omvandla den surrogatinstansen till en instans av den ursprungliga typen.

  • Vid export återspeglas surrogattypen som returneras av den här metoden för att hämta det datakontrakt som ska användas för att generera metadata.

  • Vid import ändras den första typen till en surrogattyp som återspeglas för att få datakontraktet att använda för ändamål som att referera till support.

Parametern Type är den typ av objekt som serialiseras, deserialiseras, importeras eller exporteras. Metoden GetDataContractType måste returnera indatatypen om surrogaten inte hanterar typen. Annars returnerar du lämplig surrogattyp. Om det finns flera surrogattyper kan flera mappningar definieras i den här metoden.

Metoden GetDataContractType anropas inte för inbyggda datakontraktspri primitiver, till exempel Int32 eller String. För andra typer, till exempel matriser, användardefinierade typer och andra datastrukturer, anropas den här metoden för varje typ.

I föregående exempel kontrollerar metoden om parametern type och Inventory är jämförbara. I så fall mappar metoden den till InventorySurrogated. När ett serialiserings-, deserialiserings-, importschema- eller exportschema anropas anropas den här funktionen först för att fastställa mappningen mellan typerna.

GetObjectToSerialize-metod

Metoden GetObjectToSerialize konverterar den ursprungliga typinstansen till instansen av surrogattyp. Metoden krävs för serialisering.

Nästa steg är att definiera hur fysiska data ska mappas från den ursprungliga instansen till surrogat genom att implementera GetObjectToSerialize metoden. Till exempel:

public object GetObjectToSerialize(object obj, Type targetType)
{
    Console.WriteLine("GetObjectToSerialize");
    if (obj is Inventory)
    {
        InventorySurrogated isur = new InventorySurrogated();
        isur.numpaper = ((Inventory)obj).paper;
        isur.numpencils = ((Inventory)obj).pencils;
        isur.pens = ((Inventory)obj).pens;
        return isur;
    }
    return obj;
}

Metoden GetObjectToSerialize anropas när ett objekt serialiseras. Den här metoden överför data från den ursprungliga typen till fälten av den surrogattyp. Fält kan mappas direkt till surrogatfält, eller så kan manipuleringar av ursprungliga data lagras i surrogaten. Några möjliga användningsområden är: direkt mappa fälten, utföra åtgärder på de data som ska lagras i surrogatfälten eller lagra XML för den ursprungliga typen i det surrogatade fältet.

Parametern targetType refererar till medlemmens deklarerade typ. Den här parametern är den surrogattyp som returneras av GetDataContractType metoden. Serialiseraren framtvingar inte att det returnerade objektet kan tilldelas till den här typen. Parametern obj är det objekt som ska serialiseras och konverteras till dess surrogat om det behövs. Den här metoden måste returnera indataobjektet om surrogatobjektet inte hanterar objektet. Annars returneras det nya surrogatobjektet. Surrogaten anropas inte om objektet är null. Många surrogatmappningar för olika instanser kan definieras i den här metoden.

När du skapar en DataContractSerializerkan du instruera den att bevara objektreferenser. (Mer information finns i Serialisering och deserialisering.) Detta görs genom att ange parametern preserveObjectReferences i konstruktorn till true. I så fall anropas surrogaten bara en gång för ett objekt eftersom alla efterföljande serialiseringar bara skriver referensen till dataströmmen. Om preserveObjectReferences är inställt på falseanropas surrogaten varje gång en instans påträffas.

Om typen av den serialiserade instansen skiljer sig från den deklarerade typen skrivs typinformation till dataströmmen, xsi:type till exempel för att tillåta att instansen deserialiseras i den andra änden. Den här processen sker oavsett om objektet är surrogat eller inte.

Exemplet ovan konverterar instansens Inventory data till InventorySurrogated. Den kontrollerar objektets typ och utför nödvändiga manipuleringar för att konvertera till surrogattypen. I det här fallet kopieras fälten i Inventory klassen direkt till klassfälten InventorySurrogated .

GetDeserializedObject-metod

Metoden GetDeserializedObject konverterar instansen av surrogattyp till den ursprungliga typinstansen. Det krävs för deserialisering.

Nästa uppgift är att definiera hur fysiska data ska mappas från surrogatinstansen till originalet. Till exempel:

public object GetDeserializedObject(object obj, Type targetType)
{
    Console.WriteLine("GetDeserializedObject");
    if (obj is InventorySurrogated)
    {
        Inventory invent = new Inventory();
        invent.pens = ((InventorySurrogated)obj).pens;
        invent.pencils = ((InventorySurrogated)obj).numpencils;
        invent.paper = ((InventorySurrogated)obj).numpaper;
        return invent;
    }
    return obj;
}

Den här metoden anropas endast under deserialiseringen av ett objekt. Den tillhandahåller omvänd datamappning för deserialiseringen från surrogattypen tillbaka till den ursprungliga typen. Precis som GetObjectToSerialize med metoden kan vissa möjliga användningsområden vara att direkt utbyta fältdata, utföra åtgärder på data och lagra XML-data. Vid deserialisering kanske du inte alltid hämtar de exakta datavärdena från originalet på grund av manipuleringar i datakonverteringen.

Parametern targetType refererar till medlemmens deklarerade typ. Den här parametern är den surrogattyp som returneras av GetDataContractType metoden. Parametern obj refererar till objektet som har deserialiserats. Objektet kan konverteras tillbaka till sin ursprungliga typ om det är surrogat. Den här metoden returnerar indataobjektet om surrogaten inte hanterar objektet. Annars returneras det deserialiserade objektet när konverteringen har slutförts. Om det finns flera surrogattyper kan du ange datakonvertering från surrogat till primär typ för var och en genom att ange varje typ och dess konvertering.

När ett objekt returneras uppdateras de interna objekttabellerna med objektet som returneras av den här surrogaten. Eventuella efterföljande referenser till en instans hämtar den surrogatinstansen från objekttabellerna.

I föregående exempel konverteras objekt av typen InventorySurrogated tillbaka till den ursprungliga typen Inventory. I det här fallet överförs data direkt tillbaka från InventorySurrogated motsvarande fält i Inventory. Eftersom det inte finns några datamanipuleringar innehåller vart och ett av medlemsfälten samma värden som före serialiseringen.

GetCustomDataToExport-metod

När du exporterar ett schema GetCustomDataToExport är metoden valfri. Den används för att infoga ytterligare data eller tips i det exporterade schemat. Ytterligare data kan infogas på medlemsnivå eller typnivå. Till exempel:

public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
    Console.WriteLine("GetCustomDataToExport(Member)");
    System.Reflection.FieldInfo fieldInfo = (System.Reflection.FieldInfo)memberInfo;
    if (fieldInfo.IsPublic)
    {
        return "public";
    }
    else
    {
        return "private";
    }
}

Den här metoden (med två överlagringar) möjliggör inkludering av extra information i metadata antingen på medlemsnivå eller typnivå. Det går att inkludera tips om huruvida en medlem är offentlig eller privat och kommentarer som skulle bevaras under hela exporten och importen av schemat. Sådan information skulle gå förlorad utan den här metoden. Den här metoden orsakar inte infogning eller borttagning av medlemmar eller typer, utan lägger i stället till ytterligare data i schemana på någon av dessa nivåer.

Metoden är överbelastad och kan ta antingen en Type (clrtype parameter) eller MemberInfo ( parametermemberInfo ). Den andra parametern är alltid en Type (dataContractType parameter). Den här metoden anropas för varje medlem och typ av surrogattyp dataContractType .

Någon av dessa överlagringar måste returnera antingen null eller ett serialiserbart objekt. Ett icke-null-objekt serialiseras som en anteckning i det exporterade schemat. För överlagringen Type skickas varje typ som exporteras till schemat till den här metoden i den första parametern tillsammans med den surrogattyp som dataContractType parameter. För överlagringen MemberInfo skickar varje medlem som exporteras till schemat sin information som memberInfo parameter med den surrogattyp som finns i den andra parametern.

GetCustomDataToExport-metod (typ, typ)

Metoden IDataContractSurrogate.GetCustomDataToExport(Type, Type) anropas under schemaexport för varje typdefinition. Metoden lägger till information till typerna i schemat när du exporterar. Varje definierad typ skickas till den här metoden för att avgöra om det finns några ytterligare data som måste ingå i schemat.

GetCustomDataToExport-metoden (MemberInfo, typ)

IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type) Anropas under export för varje medlem i de typer som exporteras. Med den här funktionen kan du anpassa eventuella kommentarer för de medlemmar som ska ingå i schemat vid export. Informationen för varje medlem i klassen skickas till den här metoden för att kontrollera om ytterligare data behöver läggas till i schemat.

Exemplet ovan söker igenom dataContractType för varje medlem i surrogaten. Den returnerar sedan lämplig åtkomstmodifierare för varje fält. Utan den här anpassningen är standardvärdet för åtkomstmodifierare offentligt. Därför skulle alla medlemmar definieras som offentliga i koden som genereras med hjälp av det exporterade schemat oavsett vilka deras faktiska åtkomstbegränsningar är. När medlemmen inte använder den här implementeringen skulle den numpens vara offentlig i det exporterade schemat trots att den definierades i surrogaten som privat. Med hjälp av den här metoden kan åtkomstmodifieraren genereras som privat i det exporterade schemat.

GetReferencedTypeOnImport-metod

Den här metoden mappar Type surrogatens till den ursprungliga typen. Den här metoden är valfri för schemaimport.

När du skapar en surrogat som importerar ett schema och genererar kod för det, är nästa uppgift att definiera typen av en surrogatinstans till dess ursprungliga typ.

Om den genererade koden behöver referera till en befintlig användartyp görs detta genom att implementera GetReferencedTypeOnImport metoden.

När du importerar ett schema anropas den här metoden för varje typdeklaration för att mappa surrogatdatakontraktet till en typ. Strängparametrarna typeName och typeNamespace definiera namnet och namnområdet för den surrogattyp. Returvärdet för används för GetReferencedTypeOnImport att avgöra om en ny typ behöver genereras. Den här metoden måste returnera antingen en giltig typ eller null. För giltiga typer används den typ som returneras som en referenstyp i den genererade koden. Om null returneras refereras ingen typ och en ny typ måste skapas. Om det finns flera surrogater går det att utföra mappningen för varje surrogattyp tillbaka till den ursprungliga typen.

Parametern customData är det objekt som ursprungligen returnerades från GetCustomDataToExport. Detta customData används när surrogatförfattare vill infoga extra data/tips i metadata som ska användas under importen för att generera kod.

ProcessImportedType-metod

Metoden ProcessImportedType anpassar alla typer som skapats från schemaimport. Den här metoden är valfri.

När du importerar ett schema tillåter den här metoden att all importerad typ och kompileringsinformation anpassas. Till exempel:

public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
    Console.WriteLine("ProcessImportedType");
    foreach (CodeTypeMember member in typeDeclaration.Members)
    {
        object memberCustomData = member.UserData[typeof(IDataContractSurrogate)];
        if (memberCustomData != null
          && memberCustomData is string
          && ((string)memberCustomData == "private"))
        {
            member.Attributes = ((member.Attributes & ~MemberAttributes.AccessMask) | MemberAttributes.Private);
        }
    }
    return typeDeclaration;
}

Under importen anropas den här metoden för varje typ som genereras. Ändra angiven CodeTypeDeclaration eller ändra CodeCompileUnit. Detta inkluderar att ändra namn, medlemmar, attribut och många andra egenskaper för CodeTypeDeclaration. Genom att bearbeta CodeCompileUnitär det möjligt att ändra direktiv, namnområden, refererade sammansättningar och flera andra aspekter.

Parametern CodeTypeDeclaration innehåller kod-DOM-typdeklarationen. Parametern CodeCompileUnit tillåter ändring för bearbetning av koden. Returnerar null resultat i den typdeklaration som ignoreras. Omvänt bevaras ändringarna när du returnerar en CodeTypeDeclaration.

Om anpassade data infogas under metadataexporten måste de anges till användaren under importen så att de kan användas. Dessa anpassade data kan användas för tips om programmeringsmodeller eller andra kommentarer. Varje CodeTypeDeclaration instans och CodeTypeMember instans innehåller anpassade data som UserData egenskapen, gjutna IDataContractSurrogate till typen.

Exemplet ovan utför vissa ändringar i det importerade schemat. Koden bevarar privata medlemmar av den ursprungliga typen med hjälp av en surrogat. Standardmodifieraren för åtkomst när du importerar ett schema är public. Därför är alla medlemmar i surrogatschemat offentliga om de inte ändras, som i det här exemplet. Under exporten infogas anpassade data i metadata om vilka medlemmar som är privata. Exemplet letar upp anpassade data, kontrollerar om åtkomstmodifieraren är privat och ändrar sedan lämplig medlem så att den är privat genom att ange dess attribut. Utan den här anpassningen numpens skulle medlemmen definieras som offentlig i stället för privat.

GetKnownCustomDataTypes-metod

Den här metoden hämtar anpassade datatyper som definierats från schemat. Metoden är valfri för schemaimport.

Metoden anropas i början av schemaexporten och importen. Metoden returnerar de anpassade datatyper som används i schemat som exporteras eller importeras. Metoden skickas en Collection<T> (parametern customDataTypes ), som är en samling typer. Metoden bör lägga till ytterligare kända typer i den här samlingen. De kända anpassade datatyperna behövs för att aktivera serialisering och deserialisering av anpassade data med hjälp DataContractSerializerav . Mer information finns i Kända typer av datakontrakt.

Implementera en surrogat

Om du vill använda datakontraktets surrogat i WCF måste du följa några särskilda procedurer.

Så här använder du en surrogatfil för serialisering och deserialisering

DataContractSerializer Använd för att utföra serialisering och deserialisering av data med surrogaten. DataContractSerializer skapas av DataContractSerializerOperationBehavior. Surrogaten måste också anges.

Implementera serialisering och deserialisering
  1. Skapa en instans av ServiceHost för din tjänst. Fullständiga instruktioner finns i Grundläggande WCF-programmering.

  2. För varje ServiceEndpoint angiven tjänstvärd hittar du dess OperationDescription.

  3. Sök igenom åtgärdsbeteendena för att avgöra om en instans av DataContractSerializerOperationBehavior hittas.

  4. Om en DataContractSerializerOperationBehavior hittas anger du dess DataContractSurrogate egenskap till en ny instans av surrogaten. Om inget DataContractSerializerOperationBehavior hittas skapar du en ny instans och anger DataContractSurrogate medlemmen i det nya beteendet till en ny instans av surrogaten.

  5. Lägg slutligen till det här nya beteendet i de aktuella åtgärdsbeteendena, som du ser i följande exempel:

    using (ServiceHost serviceHost = new ServiceHost(typeof(InventoryCheck)))
        foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
        {
            foreach (OperationDescription op in ep.Contract.Operations)
            {
                DataContractSerializerOperationBehavior dataContractBehavior =
                    op.Behaviors.Find<DataContractSerializerOperationBehavior>()
                    as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                }
                else
                {
                    dataContractBehavior = new DataContractSerializerOperationBehavior(op);
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                    op.Behaviors.Add(dataContractBehavior);
                }
            }
        }
    

Så här använder du en surrogatfil för metadataimport

När du importerar metadata som WSDL och XSD för att generera kod på klientsidan måste surrogaten läggas till i komponenten som ansvarar för att generera kod från XSD-schemat, XsdDataContractImporter. För att göra detta ändrar du direkt den WsdlImporter som används för att importera metadata.

Så här implementerar du en surrogat för metadataimport
  1. Importera metadata med hjälp av WsdlImporter klassen.

  2. TryGetValue Använd metoden för att kontrollera om en XsdDataContractImporter har definierats.

  3. TryGetValue Om metoden returnerar falseskapar du en ny XsdDataContractImporter och anger dess Options egenskap till en ny instans av ImportOptions klassen. Annars använder du importören som returneras av out metodens TryGetValue parameter.

  4. XsdDataContractImporter Om inte har definierats ImportOptions anger du egenskapen till en ny instans av ImportOptions klassen.

  5. DataContractSurrogate Ange egenskapen ImportOptionsXsdDataContractImporter för för till en ny instans av surrogaten.

  6. Lägg till i XsdDataContractImporter samlingen som returneras av State egenskapen WsdlImporter för (ärvd från MetadataExporter klassen.)

  7. ImportAllContracts Använd metoden för WsdlImporter för att importera alla datakontrakt i schemat. Under det sista steget genereras kod från scheman som läses in genom att anropa till surrogaten.

    MetadataExchangeClient mexClient = new MetadataExchangeClient(metadataAddress);
    mexClient.ResolveMetadataReferences = true;
    MetadataSet metaDocs = mexClient.GetMetadata();
    WsdlImporter importer = new WsdlImporter(metaDocs);
    object dataContractImporter;
    XsdDataContractImporter xsdInventoryImporter;
    if (!importer.State.TryGetValue(typeof(XsdDataContractImporter),
        out dataContractImporter))
        xsdInventoryImporter = new XsdDataContractImporter();
    
    xsdInventoryImporter = (XsdDataContractImporter)dataContractImporter;
    xsdInventoryImporter.Options ??= new ImportOptions();
    xsdInventoryImporter.Options.DataContractSurrogate = new InventorySurrogated();
    importer.State.Add(typeof(XsdDataContractImporter), xsdInventoryImporter);
    
    Collection<ContractDescription> contracts = importer.ImportAllContracts();
    

Så här använder du en surrogat för metadataexport

När du exporterar metadata från WCF för en tjänst måste som standard både WSDL- och XSD-schema genereras. Surrogaten måste läggas till i komponenten som ansvarar för att generera XSD-schema för datakontraktstyper, XsdDataContractExporter. För att göra detta använder du antingen ett beteende som implementeras IWsdlExportExtension för att ändra WsdlExporter, eller direkt ändra det WsdlExporter som används för att exportera metadata.

Så här använder du en surrogat för metadataexport
  1. Skapa en ny WsdlExporter eller använd parametern wsdlExporter som skickas ExportContract till metoden.

  2. TryGetValue Använd funktionen för att kontrollera om en XsdDataContractExporter har definierats.

  3. Om TryGetValue returnerar falseskapar du en ny XsdDataContractExporter med de genererade XML-schemana från WsdlExporteroch lägger till den i samlingen som returneras av State egenskapen för WsdlExporter. Annars använder du exportören som returneras av out metodens TryGetValue parameter.

  4. XsdDataContractExporter Om inte har definierats ExportOptions anger du Options egenskapen till en ny instans av ExportOptions klassen.

  5. DataContractSurrogate Ange egenskapen ExportOptionsXsdDataContractExporter för för till en ny instans av surrogaten. Efterföljande steg för att exportera metadata kräver inga ändringar.

    WsdlExporter exporter = new WsdlExporter();
    //or
    //public void ExportContract(WsdlExporter exporter,
    // WsdlContractConversionContext context) { ... }
    object dataContractExporter;
    XsdDataContractExporter xsdInventoryExporter;
    if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter),
        out dataContractExporter))
    {
        xsdInventoryExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
    }
    else
    {
        xsdInventoryExporter = (XsdDataContractExporter)dataContractExporter;
    }
    
    exporter.State.Add(typeof(XsdDataContractExporter), xsdInventoryExporter);
    
    if (xsdInventoryExporter.Options == null)
        xsdInventoryExporter.Options = new ExportOptions();
    xsdInventoryExporter.Options.DataContractSurrogate = new InventorySurrogated();
    

Se även