2012-05-25 13 views
18

Sto usando ServiceStack per serializzare e deserializzare alcuni oggetti su JSON. Considerate questo esempio:Ottenere ServiceStack per conservare le informazioni sul tipo

public class Container 
{ 
    public Animal Animal { get; set; } 
} 

public class Animal 
{ 
} 

public class Dog : Animal 
{ 
    public void Speak() { Console.WriteLine("Woof!"); } 
} 

var container = new Container { Animal = new Dog() }; 
var json = JsonSerializer.SerializeToString(container); 
var container2 = JsonSerializer.DeserializeFromString<Container>(json); 

((Dog)container.Animal).Speak(); //Works 
((Dog)container2.Animal).Speak(); //InvalidCastException 

L'ultima riga lancia un InvalidCastException, perché il campo degli animali viene istanziato come un tipo di animale, non un tipo di cane. C'è un modo in cui posso dire a ServiceStack di conservare le informazioni che questa particolare istanza era del tipo Cane?

risposta

35

eredità in DTOs è una cattiva idea - DTO dovrebbe essere come auto-descrive come possibile e utilizzando i client di successione si sono effettivamente idea di cosa ritorna in ultima analisi, il servizio. Questo è il motivo per cui le classi DTO non riescono a de/serializzare correttamente nella maggior parte dei serializzatori basati su standard.

Non c'è una buona ragione per avere interfacce in DTO (e pochissimi motivi per averle su modelli POCO), è un'abitudine cult del carico di usare le interfacce per ridurre l'accoppiamento nel codice dell'applicazione che è stato trapelato senza pensieri in DTO. Ma oltre i confini del processo, le interfacce aggiungono solo l'accoppiamento (è solo ridotto nel codice) poiché il consumatore non ha idea di quale tipo concreto deserializzare, quindi deve emettere suggerimenti di implementazione specifici per la serializzazione che ora incorpora i problemi C# sul cavo (quindi ora anche Gli spazi dei nomi C# interromperanno la serializzazione) e ora vincolano la tua risposta ad essere usata da un serializzatore particolare. La dispersione dei problemi di C# sul filo viola uno degli obiettivi principali dei servizi per consentire l'interoperabilità.

Poiché non esiste il concetto di 'tipo info' nelle specifiche JSON, in modo che l'eredità di lavorare in JSON Serializzatori hanno bisogno di emettono estensioni proprietarie al JSON wireformat per includere queste informazioni tipo - che ora le coppie tua JSON carico utile a un'implementazione di serializzatore JSON specifica.

ServiceStack's JsonSerializer negozi questo tipo informazioni nella proprietà __type e dal momento che può gonfiare notevolmente il carico utile, emette solo questo tipo di informazioni per i tipi che ne hanno bisogno, vale a dire, Interfacesobject tipi o classi abstract tardiva.

Detto questo la soluzione potrebbe essere quella di cambiare Animal ad essere sia un interfaccia o un astratto di classe, la raccomandazione è comunque di non utilizzare l'ereditarietà in DTOs.

+0

Cosa fare nel caso in cui disponga di più servizi che richiedono essenzialmente lo stesso DTO? Ad esempio un servizio di registrazione utente e un servizio di manutenzione utente? Entrambi si aspettano un DTO UserAccount con alcune differenze in cui i campi devono essere compilati. Il repository si aspetta un DTO UserAccount perché sta per inserire o aggiornare la tabella UserAccount. Così ho creato un DTO UserAccount. Il DTO UserRegistration e DTO UserMaintenance ereditano da esso. [Voglio anche aggiungere qui un grande ringraziamento per aver creato ServiceStack.] – GeorgeBarker

+4

Ugh ... per rispondere alla mia domanda/domanda apparentemente ovvia ora: un messaggio non dovrebbe "essere" un oggetto dominio aziendale, dovrebbe "contenere" uno (o Di Più). Una semplice questione di "ha" contro "è". Quindi per il mio esempio, UpdateUserMsg e RegisterUserMsg contengono entrambi un DTO UserAccount. Né sarebbe un DTO UserAccount. Non c'è bisogno di ereditarietà. – GeorgeBarker

+0

Come gestiresti le liste senza ereditarietà? Ad esempio, diciamo che ho un DTO con una proprietà 'List ', e quindi ho 20 classi diverse che implementano 'IAnimal'. – Cocowalla

1

Si sta serializzando solo le proprietà dell'oggetto animale, se l'oggetto serializzato è cane o meno. Anche se aggiungi una proprietà pubblica alla classe dog, come "Name", non verrà serializzata, quindi quando deserializzi avrai solo proprietà di una classe "Animal".

Se lo si modifica al seguente funzionerà;

public class Container<T> where T: Animal 
{   
    public T Animal { get; set; } 
} 

public class Animal 
{ 
} 

public class Dog : Animal 
{ 
    public void Speak() { Console.WriteLine("Woof!"); } 
    public string Name { get; set; } 
} 

var c = new Container<Dog> { Animal = new Dog() { Name = "dog1" } }; 
var json = JsonSerializer.SerializeToString<Container<Dog>>(c); 
var c2 = JsonSerializer.DeserializeFromString<Container<Dog>>(json); 

c.Animal.Speak(); //Works 
c2.Animal.Speak(); 
+0

Grazie.Non posso usare i generici per aggirare questo purtroppo - essenzialmente ho un 'elenco ', in cui ogni contenitore ha diversi tipi di'Animal'. Speravo che ci fosse qualche impostazione, o trucco per aggirare questo, ma credo che devo vivere senza questa funzionalità. – larspars

1

Forse è off-topic, ma Newtonsoft serializzatore possono farlo tra cui l'opzione:

  serializer = new JsonSerializer(); 
     serializer.TypeNameHandling = TypeNameHandling.All; 

Si creerà una proprietà all'interno del JSON chiamato $ tipo con il forte tipo di oggetto. Quando chiami il deserializzatore, quel valore verrà utilizzato per creare nuovamente l'oggetto con gli stessi tipi. Il prossimo test funziona utilizzando newtonsoft con un tipo forte, non con ServiceStack

[TestFixture] 
public class ServiceStackTests 
{ 
    [TestCase] 
    public void Foo() 
    { 
     FakeB b = new FakeB(); 
     b.Property1 = "1"; 
     b.Property2 = "2"; 

     string raw = b.ToJson(); 
     FakeA a=raw.FromJson<FakeA>(); 
     Assert.IsNotNull(a); 
     Assert.AreEqual(a.GetType(), typeof(FakeB)); 
    } 
} 

public abstract class FakeA 
{ 
    public string Property1 { get; set; } 
} 

public class FakeB:FakeA 
{ 
    public string Property2 { get; set; } 
} 
Problemi correlati