BlogServiceHost.Create()

WCF & Azure - Il blog di Fabio Cozzolino

Recent Posts

Tags

My

Twitter

Community

Archives

Email Notifications

Social Bar Widget

[WCF] La serializzazione e i tipi "sconosciuti"

Immaginiamo di avere questo modello:

  1:     public class Person
  2:     {
  3:         public string FirstName { get; set; }
  4:         public string LastName { get; set; }
  5:     }
  6: 
  7:     public class Student : Person
  8:     {
  9:         public string StudentID { get; set; }
 10:     }
 11: 
 12:     public class Employee : Person
 13:     {
 14:         public string EmployeeID { get; set; }
 15:     }

Obiettivo del nostro servizio è quello di avere un'unica operation eseguita indipendentemente dal tipo specifico scambiato. Supponiamo quindi di avere questo contratto:

  1:     [ServiceContract]
  2:     public interface IPersonService
  3:     {
  4:         [OperationContract]
  5:         void Send(Person person);
  6:     }

Così com'è dichiarato ora, il contratto di servizio non ci consente di inviare i tipi Student e Employee. Sul client infatti avremo semplicemente la classe Person.

Per poter ottenere sul client anche le classi Student e Employee possiamo utilizzare l'attributo KnownType, contenuto nell'assembly System.Runtime.Serialization, da aggiungere alla classe Person:

  1:     [KnownType(typeof(Student))]
  2:     [KnownType(typeof(Employee))]
  3:     public class Person
  4:     {
  5:         public string FirstName { get; set; }
  6:         public string LastName { get; set; }
  7:     }

Magari definire il KnownType sulla classe Person non è proprio bello. Possiamo utilizzare in alternativa il ServiceKnownType. Stesso meccanismo ma questa volta l'attributo lo inseriamo sul servizio:

  1:     [ServiceContract]
  2:     [ServiceKnownType(typeof(Student))]
  3:     [ServiceKnownType(typeof(Employee))]
  4:     public interface IPersonService
  5:     {
  6:         [OperationContract]
  7:         void Send(Person person);
  8:     }

Meglio? In alcuni casi non è sufficiente. Possiamo sfruttare un'altro overload dell'attributo ServiceKnownType per farci restituire a runtime l'elenco dei tipi da uno specifico metodo:

  1:     [ServiceContract]
  2:     [ServiceKnownType("GetKnownTypes", typeof(KnownTypeHelper))]
  3:     public interface IPersonService
  4:     {
  5:         [OperationContract]
  6:         void Send(Person person);
  7:     }
  8: 
  9:     class KnownTypeHelper
 10:     {
 11:         public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
 12:         {
 13:             return new[]
 14:                        {
 15:                            typeof(Student), 
 16:                            typeof(Employee)
 17:                        };
 18:         }
 19:     }

Chiaramente nell'esempio ritorno un elenco fisso di tipi, ma questi potrebbero essere costruiti dinamicamente. Non sempre, infatti, siamo in grado di conoscere o comunque prevedere le evoluzioni future del nostro modello.

Cosa hanno in comune queste tre alternative. Tutte creano sul client i tipi Person, Student e Employee. Uno stralcio:

  1:     [DataContract(Name="Person", 
  2:         Namespace="http://schemas.datacontract.org/2004/07/ConsoleApplication1")]
  3:     [KnownType(typeof(Student))]
  4:     [KnownType(typeof(Employee))]
  5:     public partial class Person : object, IExtensibleDataObject
  6:     {
  7:     }
  8: 
  9:     [DataContract(Name="Student", 
 10:         Namespace="http://schemas.datacontract.org/2004/07/ConsoleApplication1")]
 11:     public partial class Student : Person
 12:     {
 13:     }
 14: 
 15:     [DataContract(Name="Employee", 
 16:         Namespace="http://schemas.datacontract.org/2004/07/ConsoleApplication1")]
 17:     public partial class Employee : Person
 18:     {
 19:     }
 20: 
 21:     [ServiceContract(ConfigurationName="IPersonService")]
 22:     public interface IPersonService
 23:     {   
 24:         [OperationContract(Action="http://tempuri.org/IPersonService/Send", 
 25:             ReplyAction="http://tempuri.org/IPersonService/SendResponse")]
 26:         void Send(ConsoleApplication1.Person person);
 27:     }

Esiste un'ultima alternativa. Il file di configurazione:

  1: <?xml version="1.0" encoding="utf-8" ?>
  2: <configuration>
  3:   <system.runtime.serialization>
  4:     <dataContractSerializer>
  5:       <declaredTypes>
  6:         <add type="Person">
  7:           <knownType type="Student" />
  8:           <knownType type="Employee" />
  9:         </add>
 10:       </declaredTypes>
 11:     </dataContractSerializer>
 12:   </system.runtime.serialization>
 13: </configuration>

Chiaramente il type nel file di configurazione deve comprendere il namespace e la definizione dell'assembly.

Il KnownType è un concetto legato alla serializzazione con il DataContractSerializer. Tra gli overloads disponibili abbiamo infatti la possibilità di utilizzare proprio un elenco di tipi:

  1:     IEnumerable<Type> knownTypes = new[] {typeof (Student), typeof (Employee)};
  2:     DataContractSerializer serializer = new DataContractSerializer(typeof(Person), knownTypes);
  3:     serializer.ReadObject(stream);

Il ServiceKnownType è invece strettamente legato all'utilizzo in WCF e può essere utilizzato solo in questi contesti. Dipedentemente dalla soluzione da adottare, quella che normalmente prediligo è l'utilizzo del ServiceKnownType con un metodo esterno dichiarato in una classe helper che fornisce l'elenco dei tipi. Mi trovo decisamente comodo. Non "sporco" più di tanto il servizio e mi garantisco la possibilità in futuro di estendere il tutto più facilmente.

Comunque le alternative sono diverse e potete scegliere tranquillamente la via che più vi piace.

bye