Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El sustituto del contrato de datos es una característica avanzada desarrollada sobre el modelo de contrato de datos. Esta característica está diseñada para usarse para la personalización y sustitución de tipos en situaciones en las que los usuarios quieren cambiar cómo se serializa, deserializa o proyecta un tipo en metadatos. Algunos escenarios en los que se puede usar un suplente es cuando no se ha especificado un contrato de datos para el tipo, los campos y las propiedades no están marcados con el DataMemberAttribute atributo o los usuarios desean crear dinámicamente variaciones de esquema.
La serialización y deserialización se logran con el suplente del contrato de datos al utilizar DataContractSerializer para convertir de .NET Framework en un formato conveniente, como XML. El suplente del contrato de datos también se puede utilizar para modificar los metadatos exportados para los tipos, al generar representaciones de metadatos como documentos de esquema XML (XSD). Tras la importación, el código se crea a partir de metadatos y el suplente se puede usar en este caso para personalizar también el código generado.
Cómo funciona el suplente
Un suplente funciona asignando un tipo (el tipo "original") a otro tipo (el tipo “suplente”). En el ejemplo siguiente se muestra el tipo Inventory original y un nuevo tipo suplente InventorySurrogated . El Inventory tipo no es serializable, pero el InventorySurrogated tipo es:
public class Inventory
{
public int pencils;
public int pens;
public int paper;
}
Dado que no se ha definido un contrato de datos para esta clase, convierta la clase en una clase suplente con un contrato de datos. La clase surrogada se muestra en el ejemplo siguiente:
[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; }
}
}
Implementación de IDataContractSurrogate
Para utilizar el suplente del contrato de datos, implemente la interfaz IDataContractSurrogate.
A continuación se muestra información general de cada método de IDataContractSurrogate con una posible implementación.
GetDataContractType
El GetDataContractType método asigna un tipo a otro. Este método es necesario para la serialización, deserialización, importación y exportación.
La primera tarea está definiendo qué tipos se asignarán a otros tipos. Por ejemplo:
public Type GetDataContractType(Type type)
{
Console.WriteLine("GetDataContractType");
if (typeof(Inventory).IsAssignableFrom(type))
{
return typeof(InventorySurrogated);
}
return type;
}
Durante la serialización, la asignación devuelta por este método se utiliza subsiguientemente para transformar la instancia original en una instancia suplente llamando al método GetObjectToSerialize.
Durante la deserialización la asignación devuelta por este método es utilizada por el serializador para deserializar en una instancia del tipo suplente. Posteriormente, llama a GetDeserializedObject para transformar la instancia sustituta en una instancia del tipo original.
Durante la exportación, el tipo de suplente devuelto por este método se refleja para obtener el contrato de datos para utilizar para generar los metadatos.
Durante la importación, el tipo inicial se cambia a un tipo suplente que se refleja para obtener el contrato de datos para utilizarlos para propósitos como referenciar el soporte.
El Type parámetro es el tipo del objeto que se está serializando, deserializando, importando o exportando. El GetDataContractType método debe devolver el tipo de entrada si el suplente no controla el tipo. De lo contrario, devuelva el tipo suplente adecuado. Si existen varios tipos de sustitutos, se pueden definir numerosos mapeos en este método.
El método GetDataContractType no se llama para primitivos de contrato de datos integrados, como Int32 o String. Para otros tipos, como matrices, tipos definidos por el usuario y otras estructuras de datos, se llamará a este método para cada tipo.
En el ejemplo anterior, el método comprueba si el type parámetro y Inventory son comparables. Si es así, el método lo asigna a InventorySurrogated. Cada vez que se llama a una serialización, deserialización, esquema de importación o esquema de exportación, se llama primero a esta función para determinar la asignación entre tipos.
Método GetObjectToSerialize
El GetObjectToSerialize método convierte la instancia de tipo original en la instancia de tipo suplente. El método es necesario para la serialización.
El siguiente paso consiste en definir la forma en que se asignarán los datos físicos de la instancia original al suplente mediante la implementación del GetObjectToSerialize método . Por ejemplo:
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;
}
Se llama al GetObjectToSerialize método cuando se serializa un objeto . Este método transfiere datos del tipo original a los campos del tipo suplente. Los campos se pueden asignar directamente a campos suplentes o las manipulaciones de los datos originales se pueden almacenar en el suplente. Algunos usos posibles incluyen: asignar directamente los campos, realizar operaciones en los datos que se almacenarán en los campos suplentes o almacenar el XML del tipo original en el campo suplente.
El targetType parámetro hace referencia al tipo declarado del miembro. Este parámetro es el tipo suplente devuelto por el GetDataContractType método . El serializador no exige que el objeto devuelto se pueda asignar a este tipo. El obj parámetro es el objeto que se va a serializar y se convertirá en su suplente si es necesario. Este método debe devolver el objeto de entrada si el suplente no controla el objeto . De lo contrario, se devolverá el nuevo objeto suplente. No se llama al suplente si el objeto es NULL. Se pueden definir numerosos mapas suplentes para diferentes instancias dentro de este método.
Al crear un DataContractSerializer, puede indicarle que conserve las referencias de objeto. (Para obtener más información, consulte Serialización y deserialización.) Para ello, establezca el parámetro preserveObjectReferences en true en su constructor. En tal caso, se llama al suplente solo una vez para un objeto puesto que todas las serializaciones subsiguientes simplemente escriben la referencia en la secuencia. Si preserveObjectReferences se establece en false, entonces se invoca al sustituto cada vez que se encuentra una instancia.
Si el tipo de la instancia serializada difiere del tipo declarado, la información de tipo se escribe en la secuencia, por ejemplo, xsi:type para permitir que la instancia se deserialice al otro extremo. Este proceso se produce tanto si el objeto ha sido sustituido como si no.
En el ejemplo anterior se convierten los datos de la Inventory instancia en el de InventorySurrogated. Comprueba el tipo del objeto y realiza las manipulaciones necesarias para convertir al tipo suplente. En este caso, los campos de la Inventory clase se copian directamente en los campos de InventorySurrogated clase.
Método GetDeserializedObject
El GetDeserializedObject método convierte la instancia de tipo surrogada en la instancia de tipo original. Es necesario para la deserialización.
La siguiente tarea consiste en definir la forma en que se asignarán los datos físicos de la instancia suplente al original. Por ejemplo:
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;
}
Solo se llama a este método durante la deserialización de un objeto . Proporciona asignación inversa de datos para la deserialización del tipo suplente a su tipo original. De forma similar al GetObjectToSerialize método , algunos usos posibles pueden ser intercambiar directamente datos de campo, realizar operaciones en los datos y almacenar datos XML. Al deserializar, es posible que no siempre obtenga los valores de datos exactos del original debido a manipulaciones en la conversión de datos.
El targetType parámetro hace referencia al tipo declarado del miembro. Este parámetro es el tipo suplente devuelto por el GetDataContractType método . El obj parámetro hace referencia al objeto que se ha deserializado. El objeto puede reconvertirse a su tipo original si ha sido subrogado. Este método devuelve el objeto de entrada si el suplente no controla el objeto . De lo contrario, el objeto deserializado se devolverá una vez completada su conversión. Si existen varios tipos suplentes, puede proporcionar la conversión de datos del suplente al tipo principal para cada uno indicando cada tipo y su conversión.
Al devolver un objeto, las tablas de objetos internos se actualizan con el objeto devuelto por este suplente. Las referencias posteriores a una instancia obtendrán la instancia surrogada de las tablas de objetos.
En el ejemplo anterior se convierten los objetos de tipo InventorySurrogated en el tipo Inventoryinicial . En este caso, los datos se transfieren directamente de InventorySurrogated a sus campos correspondientes en Inventory. Dado que no hay manipulaciones de datos, cada uno de los campos miembro contendrá los mismos valores que antes de la serialización.
Método GetCustomDataToExport
Al exportar un esquema, el GetCustomDataToExport método es opcional. Se usa para insertar datos o sugerencias adicionales en el esquema exportado. Se pueden insertar datos adicionales en el nivel de miembro o en el nivel de tipo. Por ejemplo:
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";
}
}
Este método (con dos sobrecargas) permite la inclusión de información adicional en los metadatos en el nivel de miembro o tipo. Es posible incluir sugerencias sobre si un miembro es público o privado, y comentarios que se conservarán a lo largo de la exportación e importación del esquema. Esta información se perdería sin este método. Este método no provoca la inserción o eliminación de miembros o tipos, sino que agrega datos adicionales a los esquemas en cualquiera de estos niveles.
El método está sobrecargado y puede tomar un Type (clrtype parámetro) o MemberInfo (memberInfo parámetro). El segundo parámetro siempre es un Type (dataContractType parámetro). Se llama a este método para cada miembro y tipo del tipo suplente dataContractType.
Cualquiera de estas sobrecargas debe devolver null o un objeto serializable. Un objeto no NULL se serializará como anotación en el esquema exportado. Para la sobrecarga de Type, cada tipo exportado al esquema se envía a este método como el primer parámetro, junto con el tipo suplente como parámetro dataContractType. Para la sobrecarga MemberInfo, cada miembro que se exporta al esquema envía su información como el parámetro memberInfo con el tipo suplente en el segundo parámetro.
Método GetCustomDataToExport (Tipo, Tipo)
Se llama al IDataContractSurrogate.GetCustomDataToExport(Type, Type) método durante la exportación de esquemas para cada definición de tipo. El método agrega información a los tipos dentro del esquema al exportar. Cada tipo definido se envía a este método para determinar si hay datos adicionales que se deben incluir en el esquema.
Método GetCustomDataToExport (MemberInfo, Tipo)
Se llama a IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type) durante la exportación para cada miembro en los tipos que se exportan. Esta función permite personalizar los comentarios de los miembros que se incluirán en el esquema tras la exportación. La información de cada miembro de la clase se envía a este método para comprobar si es necesario agregar datos adicionales en el esquema.
El ejemplo anterior realiza búsquedas a través de dataContractType para cada miembro del suplente. A continuación, devuelve el modificador de acceso adecuado para cada campo. Sin esta personalización, el valor predeterminado para los modificadores de acceso es público. Por lo tanto, todos los miembros se definirían como públicos en el código generado mediante el esquema exportado, independientemente de cuáles sean sus restricciones de acceso reales. Cuando no se usa esta implementación, el miembro numpens sería público en el esquema exportado aunque se definió en el suplente como privado. Mediante el uso de este método, en el esquema exportado, el modificador de acceso se puede generar como privado.
Método GetReferencedTypeOnImport
Este método asigna Type del suplente al tipo original. Este método es opcional para la importación de esquemas.
Al crear un suplente que importa un esquema y genera código para él, la siguiente tarea consiste en definir el tipo de una instancia suplente en su tipo original.
Si el código generado necesita hacer referencia a un tipo de usuario existente, esto se realiza mediante la implementación del GetReferencedTypeOnImport método .
Al importar un esquema, se llama a este método para cada declaración de tipo para asignar el contrato de datos suplente a un tipo. Los parámetros typeName de cadena y typeNamespace definen el nombre y el espacio de nombres del tipo suplente. El valor devuelto para GetReferencedTypeOnImport se usa para determinar si se debe generar un nuevo tipo. Este método debe devolver un tipo válido o null. Para los tipos válidos, el tipo devuelto se usará como un tipo al que se hace referencia en el código generado. Si se devuelve null, no se hará referencia a ningún tipo y se debe crear un nuevo tipo. Si existen varios suplentes, es posible volver a realizar la asignación de cada tipo suplente a su tipo inicial.
El customData parámetro es el objeto devuelto originalmente de GetCustomDataToExport. Esto customData se usa cuando los autores suplentes desean insertar sugerencias o datos adicionales en los metadatos que se usarán durante la importación para generar código.
Método ProcessImportedType
El ProcessImportedType método personaliza cualquier tipo creado a partir de la importación de esquemas. Este método es opcional.
Al importar un esquema, este método permite personalizar cualquier tipo importado e información de compilación. Por ejemplo:
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;
}
Durante la importación, se llama a este método para cada tipo generado. Cambie el objeto especificado CodeTypeDeclaration o modifique el CodeCompileUnit. Esto incluye cambiar el nombre, los miembros, los atributos y muchas otras propiedades de .CodeTypeDeclaration Al procesar el CodeCompileUnit, es posible modificar las directivas, los espacios de nombres, los ensamblados a los que se hace referencia y otros aspectos.
El CodeTypeDeclaration parámetro contiene la declaración de tipo de código DOM. El CodeCompileUnit parámetro permite la modificación para procesar el código. Devolver null resulta en que la declaración de tipo se descarte. Por el contrario, al devolver un CodeTypeDeclaration, se conservan las modificaciones.
Si los datos personalizados se insertan durante la exportación de metadatos, debe proporcionarse al usuario durante la importación para que se pueda usar. Estos datos personalizados se pueden usar para las sugerencias del modelo de programación u otros comentarios. Cada instancia CodeTypeDeclaration y CodeTypeMember incluye datos personalizados como la propiedad UserData, convertida al tipo IDataContractSurrogate.
En el ejemplo anterior se realizan algunos cambios en el esquema importado. El código conserva los miembros privados del tipo original utilizando un suplente. El modificador de acceso predeterminado al importar un esquema es public. Por lo tanto, todos los miembros del esquema suplente serán públicos a menos que se modifiquen, como en este ejemplo. Durante la exportación, los datos personalizados se insertan en los metadatos sobre qué miembros son privados. En el ejemplo se buscan los datos personalizados, se comprueba si el modificador de acceso es privado y, a continuación, se modifica el miembro adecuado para que sea privado estableciendo sus atributos. Sin esta personalización, el numpens miembro se definiría como público en lugar de privado.
Método GetKnownCustomDataTypes
Este método obtiene tipos de datos personalizados definidos a partir del esquema. El método es opcional para la importación de esquemas.
Se llama al método al principio de la exportación e importación del esquema. El método devuelve los tipos de datos personalizados usados en el esquema exportado o importado. Al método se le pasa un Collection<T> (parámetro customDataTypes), que es una colección de tipos. El método debe agregar tipos conocidos adicionales a esta colección. Los tipos de datos personalizados conocidos son necesarios para habilitar la serialización y deserialización de datos personalizados mediante .DataContractSerializer Para obtener más información, vea Tipos conocidos del contrato de datos.
Implementación de un sustituto
Para utilizar el suplente del contrato de datos en WCF, debe seguir unos procedimientos especiales.
Para utilizar un suplente para la serialización y deserialización
Utilice DataContractSerializer para realizar la serialización y deserialización de datos con el suplente. El DataContractSerializer es creado por el DataContractSerializerOperationBehavior. También se debe especificar el suplente.
Para implementar la serialización y la deserialización
Cree una instancia del ServiceHost para su servicio. Para obtener instrucciones completas, consulte Programación básica de WCF.
Para cada ServiceEndpoint del host del servicio especificado, busque OperationDescription.
Revise los comportamientos operativos para determinar si se encuentra una instancia de DataContractSerializerOperationBehavior.
Si se encuentra DataContractSerializerOperationBehavior, establezca su propiedad DataContractSurrogate en una nueva instancia del suplente. Si no se encuentra DataContractSerializerOperationBehavior, cree una nueva instancia y establezca el miembro DataContractSurrogate del nuevo comportamiento en una nueva instancia del suplente.
Por último, agregue este nuevo comportamiento a los comportamientos de la operación actual, como se muestra en el ejemplo siguiente:
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); } } }
Para usar un suplente para la importación de metadatos
Al importar metadatos como WSDL y XSD para generar código del lado cliente, el suplente debe agregarse al componente responsable de generar código a partir del esquema XSD, XsdDataContractImporter. Para ello, modifique directamente el WsdlImporter usado para importar metadatos.
Para implementar un suplente para la importación de metadatos
Importe los metadatos mediante la WsdlImporter clase .
Use el TryGetValue método para comprobar si se ha definido un XsdDataContractImporter .
Si el TryGetValue método devuelve
false, cree un nuevo XsdDataContractImporter y establezca su Options propiedad en una nueva instancia de la ImportOptions clase . De lo contrario, utilice el importador devuelto por el parámetrooutdel método TryGetValue.Si XsdDataContractImporter no tiene ImportOptions definido, establezca la propiedad como una nueva instancia de la clase ImportOptions.
Establezca la propiedad DataContractSurrogate de ImportOptions de XsdDataContractImporter en una nueva instancia del suplente.
Agregue XsdDataContractImporter a la colección devuelta por la propiedad State de WsdlImporter (heredada de la clase MetadataExporter).
Use el ImportAllContracts método de WsdlImporter para importar todos los contratos de datos dentro del esquema. Durante el último paso, el código se genera a partir de los esquemas cargados invocando al sustituto.
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();
Para usar un suplente para la exportación de metadatos
De forma predeterminada, al exportar metadatos de WCF para un servicio, es necesario generar el esquema WSDL y XSD. El suplente debe agregarse al componente responsable de generar el esquema XSD para los tipos de contrato de datos, XsdDataContractExporter. Para ello, use un comportamiento que implemente IWsdlExportExtension para modificar el WsdlExporter o modificar directamente el WsdlExporter usado para exportar metadatos.
Para usar un suplente para la exportación de metadatos
Cree un nuevo WsdlExporter o use el parámetro
wsdlExporterpasado al método ExportContract.Usa la función TryGetValue para comprobar si se ha definido un XsdDataContractExporter.
Si TryGetValue devuelve
false, cree un nuevo XsdDataContractExporter con los esquemas XML generados de WsdlExportery agréguelo a la colección devuelta por la State propiedad de WsdlExporter. De lo contrario, utilice el exportador devuelto por el parámetrooutdel método TryGetValue.Si el XsdDataContractExporter no tiene ExportOptions definido, establezca la propiedad Options en una nueva instancia de la clase ExportOptions.
Establezca la propiedad DataContractSurrogate de ExportOptions de XsdDataContractExporter en una nueva instancia del suplente. Los pasos posteriores para exportar metadatos no requieren ningún cambio.
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();