2012-05-25 29 views
18

Tôi đang sử dụng ServiceStack để tuần tự hóa và deserialize một số đối tượng thành JSON. Hãy xem xét ví dụ này:Nhận ServiceStack để giữ lại thông tin loại

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 

Dòng cuối cùng ném một InvalidCastException, vì trường Động vật được khởi tạo dưới dạng Loại động vật, không phải loại Dog. Có cách nào tôi có thể nói với ServiceStack để giữ lại thông tin rằng trường hợp cụ thể này là của loại Dog?

Trả lời

35

Thừa kế trong DTO là một ý tưởng tồi - DTO nên tự mô tả nhất có thể và bằng cách sử dụng khách hàng kế thừa một cách hiệu quả không biết dịch vụ cuối cùng sẽ trả về. Đó là lý do tại sao các lớp DTO của bạn sẽ không de/serialize đúng trong hầu hết các serializers dựa trên tiêu chuẩn. Không có lý do chính đáng để có giao diện trong DTO (và rất ít lý do để có chúng trên các mô hình POCO), đó là thói quen sử dụng giao diện để giảm khớp nối trong mã ứng dụng đang bị rò rỉ một cách vô tư vào DTO. Nhưng trên các ranh giới quá trình, các giao diện chỉ thêm các liên kết (nó chỉ giảm mã) vì người tiêu dùng không biết loại bê tông nào để deserialize thành nên nó phải phát ra các gợi ý triển khai thực hiện cụ thể mà bây giờ nhúng các mối quan tâm C# vào dây (vì vậy bây giờ thậm chí C# không gian tên sẽ phá vỡ serialization) và bây giờ hạn chế phản ứng của bạn sẽ được sử dụng bởi một serializer cụ thể. Rò rỉ C# mối quan tâm trên dây vi phạm một trong những mục tiêu cốt lõi của dịch vụ cho phép khả năng tương tác.

Vì không có khái niệm về 'thông tin loại' trong spec JSON, để thừa kế để làm việc trong JSON serializers họ cần phải phát ra mở rộng độc quyền đến JSON wireformat để bao gồm thông tin loại này - mà hiện nay các cặp vợ chồng JSON của bạn tải trọng cho việc triển khai trình serializer JSON cụ thể.

ServiceStack's JsonSerializer cửa hàng thông tin loại này trong các tài sản __type và vì nó có thể sưng lên đáng kể tải trọng, sẽ chỉ phát ra thông tin kiểu này với nhiều loại cần nó, ví dụ: Interfaces, cuối-bound object loại hoặc abstract lớp.

Với những gì đã nói các giải pháp sẽ được thay đổi Animal để hoặc là một Interface hoặc một trừu tượng lớp, đề nghị tuy nhiên không phải là để sử dụng thừa kế trong DTOs.

+0

Việc cần làm trong trường hợp tôi có nhiều dịch vụ chủ yếu là cùng một DTO? Ví dụ: dịch vụ đăng ký người dùng và dịch vụ bảo trì người dùng? Cả hai đều mong đợi một DTO UserAccount với một số khác biệt trong đó các trường sẽ được điền vào. Kho lưu trữ đang mong đợi một tài khoản người dùng DTO vì nó sẽ chèn hoặc cập nhật bảng UserAccount. Vì vậy, tôi đã tạo một DTO tài khoản người dùng. DTO UserRegistration và UserMaintenance DTO kế thừa từ nó. [Cũng muốn thêm ở đây một lời cảm ơn lớn vì đã tạo ServiceStack.] – GeorgeBarker

+4

Ugh ... để trả lời nhận xét/câu hỏi dường như rõ ràng của tôi: Thư không nên "là" đối tượng miền doanh nghiệp, nên "chứa" một (hoặc hơn). Một vấn đề đơn giản của "has" vs "is". Vì vậy, ví dụ của tôi, UpdateUserMsg và RegisterUserMsg sẽ chứa cả một DTO UserAccount. Không phải là một DTO tài khoản người dùng. Không cần thừa kế. – GeorgeBarker

+0

Bạn sẽ xử lý danh sách mà không cần thừa kế như thế nào? Ví dụ, giả sử tôi có một DTO với thuộc tính 'List ' và sau đó tôi có 20 lớp khác nhau triển khai 'IAnimal'. – Cocowalla

1

Bạn đang tuần tự hóa các thuộc tính của đối tượng động vật, cho dù đối tượng được tuần tự hóa có phải là con chó hay không. Ngay cả khi bạn thêm thuộc tính công khai vào lớp con chó, như "Tên", nó sẽ không được sắp xếp theo thứ tự sao cho khi bạn deserialize bạn sẽ chỉ có các thuộc tính của lớp "Động vật".

Nếu bạn thay đổi thành mục sau, nó sẽ hoạt động;

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

Cảm ơn bạn.Tôi không thể sử dụng Generics để có được xung quanh này không may - tôi về cơ bản có một ´List ´, nơi mà mỗi container có các loại khác nhau của 'tối'. Tôi đã hy vọng có một số thiết lập, hoặc lừa để có được xung quanh này, nhưng tôi đoán tôi phải sống mà không có chức năng này. – larspars

1

lẽ là off-topic nhưng Newtonsoft serializer thể làm điều đó bao gồm các tùy chọn:

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

Nó sẽ tạo ra một tài sản bên trong json gọi $ loại với kiểu mạnh mẽ của các đối tượng. Khi bạn gọi deserializer, giá trị đó sẽ được sử dụng để xây dựng lại đối tượng với cùng loại. Bài kiểm tra tiếp theo hoạt động bằng cách sử dụng newtonsoft với loại mạnh mẽ, không phải với 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; } 
} 
Các vấn đề liên quan