[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