2012-05-25 12 views
18

Estoy usando ServiceStack para serializar y deserializar algunos objetos a JSON. Considere este ejemplo:Obtención de ServiceStack para retener información de 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 

La última línea lanza una InvalidCastException, porque el campo animal se instancia como un tipo de animal, no un tipo de perro. ¿Hay alguna manera en que pueda decirle a ServiceStack que retenga la información de que esta instancia particular era del tipo Perro?

Respuesta

35

Herencia en dtos es una mala idea - DTO debería ser tan auto-descripción como sea posible y mediante el uso de los clientes de herencia tiene eficacia ni idea de lo regresa en última instancia el servicio. Por eso, las clases de DTO no podrán serializarse correctamente en la mayoría de los serializadores basados ​​en estándares.

No hay una buena razón para tener interfaces en DTO (y muy pocas razones para tenerlas en modelos POCO), es un hábito de culto a la carga de usar interfaces para reducir el acoplamiento en código de aplicación que se filtra irremediablemente en DTO. Pero a través de los límites del proceso, las interfaces solo agregan acoplamiento (solo se reduce en código) ya que el consumidor no tiene idea de qué tipo concreto se debe deserializar, por lo que debe emitir sugerencias de implementación específicas de serialización que ahora incrusta C# preocupaciones en el cable (por lo que Los espacios de nombres C# romperán la serialización) y ahora restringe su respuesta para ser utilizada por un serializador en particular. Las preocupaciones filtradas de C# en el cable violan uno de los objetivos principales de los servicios para permitir la interoperabilidad.

Como no existe el concepto de 'información de tipo' en la especificación JSON, a fin de que la herencia de trabajar en JSON Serializadores que necesitan para emiten extensiones propietarias a la JSON wireformat incluir esta información de tipo - que ahora las parejas su JSON carga útil a una implementación de serializador JSON específica.

ServiceStack's JsonSerializer tiendas de este tipo de información en la propiedad __type y puesto que puede hincharse considerablemente la carga útil, sólo se emitirán este tipo de información para los tipos que lo necesitan, es decir, Interfacesobject tipos o clases abstract enlazado en tiempo.

Dicho esto la solución sería cambiar Animal a ser o bien una interfaz o un resumen de clase , la recomendación es sin embargo no utilizar la herencia en dtos.

+0

¿Qué hacer en el caso en que tengo múltiples servicios que toman esencialmente el mismo DTO? Por ejemplo, un servicio de registro de usuario y un servicio de mantenimiento de usuario? Ambos esperan una DTO de cuenta de usuario con alguna diferencia en los campos que deben rellenarse. El repositorio espera una DTO de cuenta de usuario porque va a insertar o actualizar la tabla de cuentas de usuario. Así que creé una cuenta de usuario DTO. El DTO UserRegistration y el DTO UserMaintenance heredan de él. [También quiero agregar aquí un gran agradecimiento por crear ServiceStack.] – GeorgeBarker

+4

Ugh ... para responder a mi propio comentario/pregunta ahora aparentemente obvio: Un mensaje no debe "ser" un objeto de dominio comercial, debe "contener" uno (o Más). Una simple cuestión de "tiene" frente a "es". Entonces, para mi ejemplo, UpdateUserMsg y RegisterUserMsg contendrían un DTO de Cuenta de Usuario. Tampoco SERÍA una cuenta de usuario DTO. Sin necesidad de herencia. – GeorgeBarker

+0

¿Cómo manejarías las listas sin herencia? Por ejemplo, digamos que tengo un DTO con una propiedad 'List ', y luego tengo 20 clases diferentes que implementan 'IAnimal'. – Cocowalla

1

Está serializando solo las propiedades del objeto animal, ya sea que el objeto sea serializado o no. Incluso si agrega una propiedad pública a la clase de perro, como "Nombre", no se serializará, por lo que cuando se deserialice, solo tendrá propiedades de una clase "Animal".

Si lo cambia a lo siguiente, funcionará;

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

Gracias.No puedo usar medicamentos genéricos para resolver esto desafortunadamente, esencialmente tengo una 'Lista ', donde cada contenedor tiene diferentes tipos de 'Animal'. Esperaba que hubiera alguna configuración o truco para evitar esto, pero creo que tengo que vivir sin esta funcionalidad. – larspars

1

Tal vez es fuera de tema, pero Newtonsoft serializador puede hacer que la inclusión de la opción:

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

Se va a crear una propiedad dentro del llamado tipo JSON $ con el fuerte tipo de objeto. Cuando llamas al deserializador, ese valor se usará para construir el objeto nuevamente con los mismos tipos. La siguiente prueba funciona usando newtonsoft con tipo fuerte, no 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; } 
} 
Cuestiones relacionadas