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.
Tipos conocidos y resolución genérica
Juval Lowy
Descargar el ejemplo de código
Desde su primera versión, los desarrolladores de Windows Communication Foundation (WCF) han tenido que lidiar con la dificultad de la herencia de contrato de datos, un problema que llamaba tipos conocidos. En este artículo explico primero el origen del problema, analizo las mitigaciones disponibles en Microsoft .NET Framework 3.0 y .NET Framework 4, y luego presento mi técnica que puede eliminar el problema del todo. También verá algunas técnicas avanzadas de programación de WCF.
“Por valor” frente a “por referencia”
En los lenguajes tradicionales orientados a objetos tales como C++ and C#, una clase derivada mantiene una relación es un/una con su clase base. Esto significa que dada esta declaración, cada objeto B es también un objeto A:
class A {...}
class B : A {...}
Gráficamente, esto se parece al diagrama de Venn de la Figura 1, en el cual cada instancia B también es una instancia A (pero no cada A es necesariamente una B).
Figura 1 Relación “es un/una”
Desde una perspectiva tradicional de modelado de dominio orientado a los objetos, la relación “es un/una” le permite diseñar su código para la clase base al interactuar con una subclase. Esto significa que usted puede desarrollar el modelado de entidades de dominio conforme avanza el tiempo, en tanto que minimiza el impacto en la aplicación.
Por ejemplo, considere una aplicación de administración de contactos de negocio con este modelado de un tipo base llamado Contact y una clase derivada llamada Customer que especializa el contacto al agregarle los atributos de un cliente:
class Contact {
public string FirstName;
public string LastName;
}
class Customer : Contact {
public int OrderNumber;
}
Cualquier método en la aplicación que esté escrito inicialmente para el tipo Contact puede aceptar también los objetos Customer , como se muestra en la Figura 2.
Figura 2 Intercambio de referencias de clase base y subclase
interface IContactManager {
void AddContact(Contact contact);
Contact[] GetContacts();
}
class AddressBook : IContactManager {
public void AddContact(Contact contact)
{...}
...
}
IContactManager contacts = new AddressBook();
Contact contact1 = new Contact();
Contact contact2 = new Customer();
Customer customer = new Customer();
contacts.AddContact(contact1);
contacts.AddContact(contact2);
contacts.AddContact(customer);
La razón por la cual el código en la Figura 2 funciona tiene que ver con la forma en que el compilador representa el estado del objeto en la memoria. Para admitir la relación “es un/una” entre una subclase y su clase base, en la asignación de una nueva instancia de subclase, el compilador primero asigna la parte de la clase base del estado del objeto, luego anexa inmediatamente a continuación la parte de la subclase, como se muestra en la Figura 3.
Figura 3 Jerarquía del estado del objeto en la memoria
Cuando a un método, que espera una referencia a un Contact, se le da efectivamente una referencia a un Customer, aún funciona porque la referencia Customer también es una referencia a un Contact.
Lamentablemente, esta complicada configuración se interrumpe cuando se trata de WCF. A diferencia de la tradicional orientación a los objetos o el clásico modelo de programación CLR, WCF pasa todos los parámetros de operación por valor, no por referencia. Aun cuando el código pareciera indicar que los parámetros se pasan por referencia (como en C# normal), el proxy WCF en realidad los serializa en el mensaje. Los parámetros se empaquetan en el mensaje WCF y se transfieren al servicio, donde luego se deserializan en referencias locales con los cuales trabajará la operación de servicio.
Esto es también lo que pasa cuando la operación de servicio devuelve resultados al cliente: Los resultados (o parámetros de salida, o excepciones) en primer lugar se serializan en un mensaje de respuesta y luego se deserializan de vuelta por el lado del cliente.
La forma exacta de la serialización que tiene lugar es generalmente un producto del contrato de datos frente al cual está escrito el contrato de servicio. Por ejemplo, considere estos contratos de datos:
[DataContract]
class Contact {...}
[DataContract]
class Customer : Contact {...}
Al utilizar estos contratos de datos, puede definir este contrato de servicio:
[ServiceContract]
interface IContactManager {
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
Contact[] GetContacts();
}
Con aplicaciones de varios niveles, calcular la referencia de los parámetros por valor funciona mejor que por referencia, porque cualquier nivel en la arquitectura tiene la libertad de suministrar su propia interpretación al comportamiento tras el contrato de datos. El cálculo de referencias por valor también permite llamadas remotas, interoperabilidad, llamadas en cola y flujos de trabajo de larga ejecución.
Pero a diferencia de la orientación a los objetos tradicional, la operación de servicio escrita frente a la clase Contact no puede trabajar de manera predeterminada con la subclase Customer. La razón es sencilla: si pasa una referencia de subclase a una operación de servicio que espera una referencia de clase base, ¿cómo sabría WCF serializar la parte de la clase entregada en el mensaje?
Como resultado, dadas las definiciones hasta ahora, este código WCF arrojará error:
class ContactManagerClient : ClientBase<IContactManager> :
IContactManager{
...
}
IContactManager proxy = new ContactManagerClient();
Contact contact = new Customer();
// This will fail:
contacts.AddContact(contact);
Los soportes de tipo conocido
Con .NET Framework 3.0, WCF pudo abordar el problema de substituir una referencia de clase base por una subclase mediante KnownTypeAttribute, definido como:
[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,
AllowMultiple = true)]
public sealed class KnownTypeAttribute : Attribute {
public KnownTypeAttribute(Type type);
//More members
}
El atributo KnownType le permite designar subclases aceptables para el contrato de datos:
[DataContract]
[KnownType(typeof(Customer))]
class Contact {...}
[DataContract]
class Customer : Contact {...}
Cuando el cliente pasa un contrato de datos que utiliza una declaración de tipo conocido, el formateador de mensaje WCF prueba el tipo (similar a usar el operador “es”) y comprueba si es el tipo conocido esperado. Si es así, serializa el parámetro como la subclase en vez de la clase base.
El atributo KnownType afecta a todos los contratos y operaciones que utilizan la clase base, en todos los servicios y extremos, lo que le permite aceptar subclases en vez de clases base. Además, incluye la subclase en los metadatos de modo que el cliente tendrá su propia definición de la subclase y podrá transferir la subclase en vez de la clase base.
Cuando se esperan varias subclases, el desarrollador debe hacer una lista de todas ellas:
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...}
[DataContract]
class Person : Contact {...}
El formateador WCF utiliza la reflexión para recopilar todos los tipos conocidos de los contratos de datos, luego examina el parámetro suministrado para ver si corresponde a alguno de los tipos conocidos.
Observe que debe agregar de manera explícita todos los niveles en la jerarquía de clases de contrato de datos. Al agregar una subclase no agrega sus clases base:
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...}
[DataContract]
class Customer : Contact {...}
[DataContract]
class Person : Customer {...}
Como es posible que el atributo KnownType sea muy amplio en cuanto a alcance, WCF también suministra el atributo ServiceKnownTypeAttribute, el cual puede aplicar en una operación específica o en un contrato específico.
Por último, en .NET Framework 3.0, WCF también permitió enumerar los tipos conocidos esperados en el archivo de configuración de la aplicación en la sección system.runtime.serialization.
Aunque utilizar tipos conocidos técnicamente funciona bastante bien, debería sentir un poco de inquietud al respecto. En el modelado tradicional orientado a los objetos, nunca debe asociar la clase base a ninguna subclase específica. El distintivo de una buena clase base es precisamente ese: una buena base es una buena clase base para cualquier subclase posible y, sin embargo, el problema de los tipos conocidos la hace adecuada sólo para las subclases en el caso de llegue a enterarse. Si hace todo su modelado por adelantado al diseñar el sistema, puede que esto no sea un obstáculo. En realidad, conforme avanza el tiempo, a medida que la aplicación desarrolla su modelado, se encontrará con tipos aún desconocidos que lo forzarán, por lo menos, a volver a implementar la aplicación (y, lo más probable, también a modificar sus clases base).
Resoluciones de contrato de datos
Para aliviar el problema, en .NET Framework 4 WCF introdujo una forma de resolver los tipos conocidos en el tiempo de ejecución. Esta técnica programática, denominada resoluciones de contrato de datos, es la opción más eficaz porque puede extenderla hasta automatizar por completo la administración de los problemas de tipo conocido. En esencia, le dan la oportunidad de interceptar el intento de la operación por serializar y deserializar parámetros y resolver los tipos conocidos en tiempo de ejecución tanto por el lado del cliente como del servicio.
El primer paso para implementar una resolución programática es derivarla de la clase abstracta DataContractResolver, definida como:
public abstract class DataContractResolver {
protected DataContractResolver();
public abstract bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace);
public abstract Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver);
}
Se llama a su implementación de TryResolveType cuando WCF trata de serializar un tipo en un mensaje y el tipo suministrado (el parámetro tipo) es diferente del tipo declarado en el contrato de operación (el parámetro declaredType). Si desea serializar el tipo, necesita suministrar algunos identificadores únicos que sirvan como claves en un diccionario que asigne identificadores a tipos. WCF suministrará aquellas claves durante la deserialización, de modo que pueda realizar la vinculación con ese tipo.
Observe que la clave de espacio de nombres no puede ser una cadena vacía o nula. Aún cuando virtualmente cualquier valor de cadena único servirá para los identificadores, yo recomiendo utilizar simplemente el nombre tipo y espacio de nombres CLR. Establezca el nombre de tipo y el espacio de nombres en los parámetros de salida typeName y typeNamespace.
Si vuelve verdadero desde TryResolveType, el tipo se considera resuelto, como si hubiera aplicado el atributo KnownType. Si vuelve falso, WCF no puede realizar la llamada. Observe que TryResolveType debe resolver todos los tipos conocidos, incluso aquellos tipos que contienen el atributo KnownType o que aparecen en el archivo de configuración. Esto presenta un riesgo potencial: Se requiere que la resolución se asocie con todos los tipos conocidos en la aplicación y arrojará un error en la llamada de operación con otros tipos que pueden aparecer con el tiempo. Por lo tanto, es preferible como una contingencia de retraso intentar resolver el tipo mediante la resolución predeterminada de tipos conocidos que WCF habría utilizado si su resolución no estuviera en uso. Esto es exactamente para lo que sirve el parámetro knownTypeResolver. Si su implementación de TryResolveType no puede resolver el tipo, debe delegarlo a knownTypeResolver.
Se llama a ResolveName cuando WCF intenta deserializar un tipo desde un mensaje y el tipo proporcionado (el parámetro tipo) es diferente del tipo declarado en el contrato de operación (el parámetro declaredType). En este caso, WCF proporciona los identificadores de nombre tipo y de espacio de nombres, de modo que los pueda asignar de vuelta a un tipo conocido.
A modo de ejemplo, considere nuevamente estos dos contratos de datos:
[DataContract]
class Contact {...}
[DataContract]
class Customer : Contact {...}
En la Figura 4 se muestra una resolución simple para el tipo Customer.
Figura 4 CustomerResolver
class CustomerResolver : DataContractResolver {
string Namespace {
get {
return typeof(Customer).Namespace ?? "global";
}
}
string Name {
get {
return typeof(Customer).Name;
}
}
public override Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver) {
if(typeName == Name && typeNamespace == Namespace) {
return typeof(Customer);
}
else {
return knownTypeResolver.ResolveName(
typeName,typeNamespace,declaredType,null);
}
}
public override bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace) {
if(type == typeof(Customer)) {
XmlDictionary dictionary = new XmlDictionary();
typeName = dictionary.Add(Name);
typeNamespace = dictionary.Add(Namespace);
return true;
}
else {
return knownTypeResolver.TryResolveType(
type,declaredType,null,out typeName,out typeNamespace);
}
}
}
Se debe adjuntar la resolución como un comportamiento para cada operación en el proxy o el extremo del servicio. La clase ServiceEndpoint tiene una propiedad llamada Contract del tipo ContractDescription:
public class ServiceEndpoint {
public ContractDescription Contract
{get;set;}
// More members
}
ContractDescription contiene una colección de descripciones de operación, con una instancia de OperationDescription para cada operación en el contrato:
public class ContractDescription {
public OperationDescriptionCollection Operations
{get;}
// More members
}
public class OperationDescriptionCollection :
Collection<OperationDescription>
{...}
Cada OperationDescription tiene una colección de comportamientos de operación del tipo IOperationBehavior:
public class OperationDescription {
public KeyedByTypeCollection<IOperationBehavior> Behaviors
{get;}
// More members
}
En esta colección de comportamientos, cada operación siempre tiene un comportamiento llamado DataContractSerializerOperationBehavior con una propiedad DataContractResolver:
public class DataContractSerializerOperationBehavior :
IOperationBehavior,... {
public DataContractResolver DataContractResolver
{get;set}
// More members
}
El valor predeterminado de la propiedad DataContractResolver es nulo, pero puede asignarlo a su resolución personalizada. Para instalar una resolución en el host, debe procesar una iteración en la colección de extremos en la descripción de servicio mantenida por el host:
public class ServiceHost : ServiceHostBase {...}
public abstract class ServiceHostBase : ... {
public ServiceDescription Description
{get;}
// More members
}
public class ServiceDescription {
public ServiceEndpointCollection Endpoints
{get;}
// More members
}
public class ServiceEndpointCollection :
Collection<ServiceEndpoint> {...}
Suponga que tiene la siguiente definición de servicio y está utilizando la resolución que aparece en la Figura 4:
[ServiceContract]
interface IContactManager {
[OperationContract]
void AddContact(Contact contact);
...
}
class AddressBookService : IContactManager {...}
En la Figura 5 se muestra cómo instalar la resolución en el host para AddressBookService.
Figura 5 Instalación de una resolución en el host
ServiceHost host =
new ServiceHost(typeof(AddressBookService));
foreach(ServiceEndpoint endpoint in
host.Description.Endpoints) {
foreach(OperationDescription operation in
endpoint.Contract.Operations) {
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new CustomerResolver();
}
}
host.Open();
En el lado del cliente, sigue pasos similares, excepto que necesita establecer la resolución en el extremo único del proxy o la fábrica de canales. Por ejemplo, dada esta definición de clase proxy:
class ContactManagerClient : ClientBase<IContactManager>,IContactManager
{...}
En la Figura 6 se muestra cómo instalar la resolución en el proxy para llamar al servicio de la Figura 5 con un tipo conocido.
Figura 6 Instalación de una resolución en el proxy
ContactManagerClient proxy = new ContactManagerClient();
foreach(OperationDescription operation in
proxy.Endpoint.Contract.Operations) {
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new CustomerResolver();
}
Customer customer = new Customer();
...
proxy.AddContact(customer);
La resolución genérica
Escribir e instalar una resolución para cada tipo es obviamente mucho trabajo, ya que requiere que realice un seguimiento meticuloso de todos los tipos, algo inclinado a los errores y que puede salirse de control rápidamente en un sistema en evolución. Para automatizar la implementación de una resolución, escribí la clase GenericResolver, definida como:
public class GenericResolver : DataContractResolver {
public Type[] KnownTypes
{get;}
public GenericResolver();
public GenericResolver(Type[] typesToResolve);
public static GenericResolver Merge(
GenericResolver resolver1,
GenericResolver resolver2);
}
GenericResolver ofrece dos constructores. Un constructor puede aceptar una matriz de tipos conocidos por resolver: El constructor sin parámetros agregará automáticamente como tipos conocidos todas las clases y estructuras en el ensamblado de llamado y todas las clases y estructuras públicas en ensamblados con referencia de ensamblado de llamado. El constructor sin parámetros no agregará tipos que se originan en un ensamblado al que se ha hecho referencia en .NET Framework.
Además, GenericResolver ofrece el método estático Merge que puede utilizar para combinar los tipos conocidos de dos resoluciones, al devolver un GenericResolver que resuelve la unión de dos resoluciones proporcionadas. En la Figura 7 se muestra la parte pertinente de GenericResolver sin reflejar los tipos en los ensamblados, lo cual no tiene nada que ver con WCF.
Figura 7 Implementación de GenericResolver (parcial)
public class GenericResolver : DataContractResolver {
const string DefaultNamespace = "global";
readonly Dictionary<Type,Tuple<string,string>> m_TypeToNames;
readonly Dictionary<string,Dictionary<string,Type>> m_NamesToType;
public Type[] KnownTypes {
get {
return m_TypeToNames.Keys.ToArray();
}
}
// Get all types in calling assembly and referenced assemblies
static Type[] ReflectTypes() {...}
public GenericResolver() : this(ReflectTypes()) {}
public GenericResolver(Type[] typesToResolve) {
m_TypeToNames = new Dictionary<Type,Tuple<string,string>>();
m_NamesToType = new Dictionary<string,Dictionary<string,Type>>();
foreach(Type type in typesToResolve) {
string typeNamespace = GetNamespace(type);
string typeName = GetName(type);
m_TypeToNames[type] = new Tuple<string,string>(typeNamespace,typeName);
if(m_NamesToType.ContainsKey(typeNamespace) == false) {
m_NamesToType[typeNamespace] = new Dictionary<string,Type>();
}
m_NamesToType[typeNamespace][typeName] = type;
}
}
static string GetNamespace(Type type) {
return type.Namespace ?? DefaultNamespace;
}
static string GetName(Type type) {
return type.Name;
}
public static GenericResolver Merge(
GenericResolver resolver1, GenericResolver resolver2) {
if(resolver1 == null) {
return resolver2;
}
if(resolver2 == null) {
return resolver1;
}
List<Type> types = new List<Type>();
types.AddRange(resolver1.KnownTypes);
types.AddRange(resolver2.KnownTypes);
return new GenericResolver(types.ToArray());
}
public override Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver) {
if(m_NamesToType.ContainsKey(typeNamespace)) {
if(m_NamesToType[typeNamespace].ContainsKey(typeName)) {
return m_NamesToType[typeNamespace][typeName];
}
}
return knownTypeResolver.ResolveName(
typeName,typeNamespace,declaredType,null);
}
public override bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace) {
if(m_TypeToNames.ContainsKey(type)) {
XmlDictionary dictionary = new XmlDictionary();
typeNamespace = dictionary.Add(m_TypeToNames[type].Item1);
typeName = dictionary.Add(m_TypeToNames[type].Item2);
return true;
}
else {
return knownTypeResolver.TryResolveType(
type,declaredType,null,out typeName,
out typeNamespace);
}
}
}
Los miembros más importantes de GenericResolver son los diccionarios m_TypeToNames y m_NamesToType. m_TypeToNames asigna un tipo a una tupla de su nombre y espacio de nombres. m_NamesToType asigna un espacio de nombres y nombre tipo al tipo actual. El constructor que toma la matriz de tipos inicializa aquellos dos diccionarios. El método TryResolveType utiliza el tipo proporcionado como una clave en el diccionario m_TypeToNames para leer el nombre y el espacio de nombres del tipo. El método ResolveName utiliza el espacio de nombres y el nombre proporcionados como una clave en el diccionario m_NamesToType para devolver el tipo resuelto.
Aunque podría utilizar un código tedioso similar al de la Figura 5 y Figura 6 para instalar GenericResolver, lo mejor es simplificarlo con métodos de extensión. Para tal fin, utilice mis métodos AddGenericResolver de GenericResolverInstalle, definidos como:
public static class GenericResolverInstaller {
public static void AddGenericResolver(
this ServiceHost host, params Type[] typesToResolve);
public static void AddGenericResolver<T>(
this ClientBase<T> proxy,
params Type[] typesToResolve) where T : class;
public static void AddGenericResolver<T>(
this ChannelFactory<T> factory,
params Type[] typesToResolve) where T : class;
}
El método AddGenericResolver acepta una matriz de parámetros de tipos, lo cual significa una lista de tipos de duración indefinida y separada por comas. Si no especifica tipos, eso hará que AddGenericResolver agregue como tipos conocidos todas las clases y estructuras en el ensamblado de llamada más las clases y estructuras públicas en los ensamblados a los que se ha hecho referencia. Por ejemplo, considere estos tipos conocidos:
[DataContract]
class Contact {...}
[DataContract]
class Customer : Contact {...}
[DataContract]
class Employee : Contact {...}
En la Figura 8 se muestran varios ejemplos de uso del método de extensión AddGenericResolver para estos tipos.
Figura 8. Instalación de GenericResolver
// Host side
ServiceHost host1 = new ServiceHost(typeof(AddressBookService));
// Resolve all types in this and referenced assemblies
host1.AddGenericResolver();
host1.Open();
ServiceHost host2 = new ServiceHost(typeof(AddressBookService));
// Resolve only Customer and Employee
host2.AddGenericResolver(typeof(Customer),typeof(Employee));
host2.Open();
ServiceHost host3 = new ServiceHost(typeof(AddressBookService));
// Can call AddGenericResolver() multiple times
host3.AddGenericResolver(typeof(Customer));
host3.AddGenericResolver(typeof(Employee));
host3.Open();
// Client side
ContactManagerClient proxy = new ContactManagerClient();
// Resolve all types in this and referenced assemblies
proxy.AddGenericResolver();
Customer customer = new Customer();
...
proxy.AddContact(customer);
GenericResolverInstaller no sólo instala GenericResolver, sino que también intenta combinarlo con la resolución genérica antigua (si está presente). Esto significa que puede llamar al método AddGenericResolver varias veces. Esto es práctico cuando se agregan tipos genéricos entrelazados:
[DataContract]
class Customer<T> : Contact {...}
ServiceHost host = new ServiceHost(typeof(AddressBookService));
// Add all non-generic known types
host.AddGenericResolver();
// Add the generic types
host.AddGenericResolver(typeof(Customer<int>,Customer<string>));
host.Open();
En la Figura 9 se muestra una implementación parcial de GenericResolverInstaller.
Figura 9 Implementación de GenericResolverInstaller
public static class GenericResolverInstaller {
public static void AddGenericResolver(
this ServiceHost host, params Type[] typesToResolve) {
foreach(ServiceEndpoint endpoint in
host.Description.Endpoints) {
AddGenericResolver(endpoint,typesToResolve);
}
}
static void AddGenericResolver(
ServiceEndpoint endpoint,Type[] typesToResolve) {
foreach(OperationDescription operation in
endpoint.Contract.Operations) {
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>();
GenericResolver newResolver;
if(typesToResolve == null ||
typesToResolve.Any() == false) {
newResolver = new GenericResolver();
}
else {
newResolver = new GenericResolver(typesToResolve);
}
GenericResolver oldResolver =
behavior.DataContractResolver as GenericResolver;
behavior.DataContractResolver =
GenericResolver.Merge(oldResolver,newResolver);
}
}
}
Si no se han proporcionado tipos, AddGenericResolver utilizará el constructor sin parámetros de GenericResolver. De lo contrario, utilizará solo los tipos especificados al llamar al otro constructor. Observe la combinación con la resolución antigua si está presente.
El atributo de resolución genérica
Si su servicio cuenta con la resolución genérica mediante diseño, es mejor no estar a merced del host y declarar su necesidad de la resolución genérica en el tiempo de diseño. Con ese fin, escribí GenericResolverBehaviorAttribute:
[AttributeUsage(AttributeTargets.Class)]
public class GenericResolverBehaviorAttribute :
Attribute,IServiceBehavior {
void IServiceBehavior.Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase) {
ServiceHost host = serviceHostBase as ServiceHost;
host.AddGenericResolver();
}
// More members
}
Este atributo conciso hace que el servicio sea independiente del host:
GenericResolverBehaviorAttribute deriva de IServiceBehavior, el cual es una interfaz especial de WCF y es la extensión que se usa con más frecuencia en WCF. Cuando el host carga el servicio, llama a los métodos IServiceBehavior (específicamente al método Validate), lo cual permite que el atributo interactúe con el host. En el caso de GenericResolverBehaviorAttribute, agrega la resolución genérica al host.
Y ya lo tiene: una manera relativamente simple y flexible de eludir el problema de la herencia de contrato de datos. Ponga a trabajar esta técnica en su próximo proyecto WCF.
Juval Lowy es un arquitecto de software con IDesign que ofrece cursos y asesoramiento sobre .NET y arquitectura. Este artículo contiene extractos de su reciente libro “Programming WCF Services, Tercera Edición” (O’Reilly, 2010). También es director regional de Microsoft en Silicon Valley. Puede ponerse en contacto con Lowy en idesign.net.
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Glenn Block y Amadeo Casas Cuadrado