2011-12-24 29 views
11

Tôi có dịch vụ WCF C# bằng cách sử dụng điểm cuối webHttpBinding sẽ nhận và trả về dữ liệu ở định dạng JSON. Dữ liệu để gửi/nhận cần phải sử dụng một loại đa hình để dữ liệu của các loại khác nhau có thể được trao đổi trong cùng một "gói dữ liệu". Tôi có mô hình dữ liệu sau:Bảo quản các loại đa hình trong Dịch vụ WCF bằng cách sử dụng JSON

[DataContract] 
public class DataPacket 
{ 
    [DataMember] 
    public List<DataEvent> DataEvents { get; set; } 
} 

[DataContract] 
[KnownType(typeof(IntEvent))] 
[KnownType(typeof(BoolEvent))] 
public class DataEvent 
{ 
    [DataMember] 
    public ulong Id { get; set; } 

    [DataMember] 
    public DateTime Timestamp { get; set; } 

    public override string ToString() 
    { 
     return string.Format("DataEvent: {0}, {1}", Id, Timestamp); 
    } 
} 

[DataContract] 
public class IntEvent : DataEvent 
{ 
    [DataMember] 
    public int Value { get; set; } 

    public override string ToString() 
    { 
     return string.Format("IntEvent: {0}, {1}, {2}", Id, Timestamp, Value); 
    } 
} 

[DataContract] 
public class BoolEvent : DataEvent 
{ 
    [DataMember] 
    public bool Value { get; set; } 

    public override string ToString() 
    { 
     return string.Format("BoolEvent: {0}, {1}, {2}", Id, Timestamp, Value); 
    } 
} 

dịch vụ của tôi sẽ gửi/nhận các sự kiện sub-type (IntEvent, BoolEvent vv) trong một gói dữ liệu duy nhất, như sau:

[ServiceContract] 
public interface IDataService 
{ 
    [OperationContract] 
    [WebGet(UriTemplate = "GetExampleDataEvents")] 
    DataPacket GetExampleDataEvents(); 

    [OperationContract] 
    [WebInvoke(UriTemplate = "SubmitDataEvents", RequestFormat = WebMessageFormat.Json)] 
    void SubmitDataEvents(DataPacket dataPacket); 
} 

public class DataService : IDataService 
{ 
    public DataPacket GetExampleDataEvents() 
    { 
     return new DataPacket { 
      DataEvents = new List<DataEvent> 
      { 
       new IntEvent { Id = 12345, Timestamp = DateTime.Now, Value = 5 }, 
       new BoolEvent { Id = 45678, Timestamp = DateTime.Now, Value = true } 
      } 
     }; 
    } 

    public void SubmitDataEvents(DataPacket dataPacket) 
    { 
     int i = dataPacket.DataEvents.Count; //dataPacket contains 2 events, but both are type DataEvent instead of IntEvent and BoolEvent 
     IntEvent intEvent = dataPacket.DataEvents[0] as IntEvent; 
     Console.WriteLine(intEvent.Value); //null pointer as intEvent is null since the cast failed 
    } 
} 

Khi tôi gửi gói của tôi đến phương thức SubmitDataEvents, mặc dù tôi nhận được các loại DataEvent và cố gắng truyền chúng trở lại loại cơ sở của chúng (chỉ cho mục đích thử nghiệm) dẫn đến một số InvalidCastException. Gói của tôi là:

POST http://localhost:4965/DataService.svc/SubmitDataEvents HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:4965 
Content-Type: text/json 
Content-Length: 340 

{ 
    "DataEvents": [{ 
     "__type": "IntEvent:#WcfTest.Data", 
     "Id": 12345, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "Value": 5 
    }, { 
     "__type": "BoolEvent:#WcfTest.Data", 
     "Id": 45678, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "Value": true 
    }] 
} 

Xin lỗi vì bài đăng dài, nhưng tôi có thể làm gì để bảo vệ các loại cơ sở của từng đối tượng không? Tôi nghĩ việc thêm gợi ý kiểu vào JSON và thuộc tính KnownType là DataEvent sẽ cho phép tôi bảo tồn các loại - nhưng nó dường như không hoạt động.

Sửa: Nếu tôi gửi yêu cầu đến SubmitDataEvents ở định dạng XML (với Content-Type: text/xml thay vì text/json) thì List<DataEvent> DataEvents có chứa các tiểu loại thay cho loại siêu. Ngay sau khi tôi đặt yêu cầu thành text/json và gửi gói ở trên thì tôi chỉ nhận được siêu loại và tôi không thể chuyển chúng sang loại phụ. yêu cầu cơ XML của tôi là:

<ArrayOfDataEvent xmlns="http://schemas.datacontract.org/2004/07/WcfTest.Data"> 
    <DataEvent i:type="IntEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
    <Id>12345</Id> 
    <Timestamp>1999-05-31T11:20:00</Timestamp> 
    <Value>5</Value> 
    </DataEvent> 
    <DataEvent i:type="BoolEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
    <Id>56789</Id> 
    <Timestamp>1999-05-31T11:20:00</Timestamp> 
    <Value>true</Value> 
    </DataEvent> 
</ArrayOfDataEvent> 

Chỉnh sửa 2: Cập nhật mô tả dịch vụ sau bình luận của Pavel dưới đây. Điều này vẫn không hoạt động khi gửi gói JSON trong Fiddler2. Tôi chỉ nhận được một số List có chứa DataEvent thay vì IntEventBoolEvent.

Chỉnh sửa 3: Như Pavel đã đề xuất, đây là kết quả từ System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString(). Có vẻ ổn với tôi.

<root type="object"> 
    <DataEvents type="array"> 
     <item type="object"> 
      <__type type="string">IntEvent:#WcfTest.Data</__type> 
      <Id type="number">12345</Id> 
      <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
      <Value type="number">5</Value> 
     </item> 
     <item type="object"> 
      <__type type="string">BoolEvent:#WcfTest.Data</__type> 
      <Id type="number">45678</Id> 
      <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
      <Value type="boolean">true</Value> 
     </item> 
    </DataEvents> 
</root> 

Khi truy tìm các deserialization của gói, tôi nhận được thông điệp sau đây trong các dấu vết:

<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Verbose"> 
    <TraceIdentifier>http://msdn.microsoft.com/en-GB/library/System.Runtime.Serialization.ElementIgnored.aspx</TraceIdentifier> 
    <Description>An unrecognized element was encountered in the XML during deserialization which was ignored.</Description> 
    <AppDomain>1c7ccc3b-4-129695001952729398</AppDomain> 
    <ExtendedData xmlns="http://schemas.microsoft.com/2006/08/ServiceModel/StringTraceRecord"> 
     <Element>:__type</Element> 
    </ExtendedData> 
</TraceRecord> 

Thông báo này được lặp lại 4 lần (hai lần với __type như phần tử và hai lần với Value). Có vẻ như thông tin loại gợi ý đang bị bỏ qua thì các phần tử Value bị bỏ qua vì gói được deserialized thành DataEvent thay vì IntEvent/BoolEvent.

Trả lời

2

Nhờ Pavel Gatilov, tôi đã tìm ra giải pháp cho vấn đề này. Tôi sẽ thêm nó như là câu trả lời riêng ở đây cho bất cứ ai có thể bị phát hiện bởi điều này trong tương lai.

Vấn đề là trình giải mã JSON dường như không chấp nhận khoảng trắng. Dữ liệu trong gói mà tôi đang gửi là "được in đẹp" với các ngắt dòng và khoảng trống để làm cho nó dễ đọc hơn. Tuy nhiên, khi gói này được deserialized, điều này có nghĩa rằng khi tìm kiếm các gợi ý "__type", deserializer JSON đã nhìn vào phần sai của gói. Điều này có nghĩa là gợi ý kiểu bị bỏ qua và gói được deserialized là loại sai.

Các gói sau đây hoạt động chính xác:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1 
User-Agent: Fiddler 
Content-Type: text/json 
Host: localhost:6463 
Content-Length: 233 

{"DataEvents":[{"__type":"IntEvent:#WebApplication1","Id":12345,"Timestamp":"\/Date(1324905383689+0000)\/","IntValue":5},{"__type":"BoolEvent:#WebApplication1","Id":45678,"Timestamp":"\/Date(1324905383689+0000)\/","BoolValue":true}]} 

Tuy nhiên, gói này không làm việc:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1 
User-Agent: Fiddler 
Content-Type: text/json 
Host: localhost:6463 
Content-Length: 343 

{ 
    "DataEvents": [{ 
     "__type": "IntEvent:#WebApplication1", 
     "Id": 12345, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "IntValue": 5 
    }, { 
     "__type": "BoolEvent:#WebApplication1", 
     "Id": 45678, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "BoolValue": true 
    }] 
} 

Những gói là giống hệt nhau ngoài các ngắt dòng và không gian.

+0

Wow ... Rất vui khi biết sự cố của bạn đã được giải quyết.Tuy nhiên, JSON được định dạng hoạt động tốt cho tôi, tôi luôn sao chép nó vào Fiddler và không có vấn đề gì. Tôi tin rằng nếu bạn chưa cài đặt tất cả các bản cập nhật cho khung công tác .NET hoặc phiên bản mới nhất có một lỗi lạ liên quan đến việc xử lý ký tự khoảng trắng. Tôi hoàn toàn bối rối. –

+0

Đừng xấu hổ chút nào, tôi chưa bao giờ thấy rằng nếu không có sự giúp đỡ của bạn. Cách duy nhất tôi phát hiện ra là bằng cách gỡ lỗi mã nguồn WCF mà bạn đã đăng ở trên. Tôi nhận thấy rằng giá trị cho 'offset' luôn luôn quá thấp và xảy ra để nhận thấy rằng việc bao gồm các ký tự' \ r' và '\ n' trong bộ đệm tin nhắn đã loại bỏ tính toán bù trừ bằng cách nào đó. Tôi có thể đăng một câu hỏi khác để xem liệu có ai biết tại sao khoảng trắng cản trở việc phân tích cú pháp, vì tôi muốn tính linh hoạt của nó được bao gồm hay không. –

+1

Ồ, điều đó đáng lẽ phải là 'bối rối' thay vì 'xấu hổ' :). Nhân tiện, tôi đã tìm thấy mô tả về một vấn đề tương tự [trong bài viết này] (http://blog.js-development.com/2010/05/n-will-break-your-json-jquery-wcf.html). –

3

Bất cứ khi nào xử lý việc tuần tự hóa, trước tiên hãy cố gắng tuần tự hóa biểu đồ đối tượng để xem định dạng chuỗi được tuần tự hóa. Sau đó, sử dụng định dạng để tạo chuỗi được xê-ri chính xác.

Gói của bạn không chính xác. Chính xác là:

POST http://localhost:47440/Service1.svc/SubmitDataEvents HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:47440 
Content-Length: 211 
Content-Type: text/json 

[ 
    { 
    "__type":"IntEvent:#WcfTest.Data", 
    "Id":12345, 
    "Timestamp":"\/Date(1324757832735+0700)\/", 
    "Value":5 
    }, 
    { 
    "__type":"BoolEvent:#WcfTest.Data", 
    "Id":45678, 
    "Timestamp":"\/Date(1324757832736+0700)\/", 
    "Value":true 
    } 
] 

cũng lưu ý tiêu đề Content-Type.

Tôi đã thử với mã của bạn và mã hoạt động hoàn hảo (tốt, tôi đã xóa Console.WriteLine và được thử nghiệm trong trình gỡ lỗi). Tất cả các hệ thống phân cấp lớp là tốt, tất cả các đối tượng có thể được đúc thành các loại của chúng. Nó hoạt động.

CẬP NHẬT

Các JSON bạn đã đăng công trình với đoạn mã sau:

[DataContract] 
public class SomeClass 
{ 
    [DataMember] 
    public List<DataEvent> dataEvents { get; set; } 
} 

... 

[ServiceContract] 
public interface IDataService 
{ 
    ... 

    [OperationContract] 
    [WebInvoke(UriTemplate = "SubmitDataEvents")] 
    void SubmitDataEvents(SomeClass parameter); 
} 

Lưu ý rằng một nút cấp cao được thêm vào cây đối tượng.

Và một lần nữa, nó hoạt động tốt với thừa kế.

Nếu sự cố vẫn còn, vui lòng đăng mã mà bạn sử dụng để gọi dịch vụ, cũng như các chi tiết ngoại lệ bạn nhận được.

UPDATE 2

Làm thế nào lạ ... Nó hoạt động trên máy tính của tôi.

Tôi sử dụng .NET 4 và VS2010 với các bản cập nhật mới nhất trên Win7 x64.

Tôi nhận hợp đồng dịch vụ, triển khai và hợp đồng dữ liệu của bạn. Tôi lưu trữ chúng trong một ứng dụng web theo Cassini. Tôi có web.config sau:

<configuration> 
    <connectionStrings> 
    <!-- excluded for brevity --> 
    </connectionStrings> 

    <system.web> 
    <!-- excluded for brevity --> 
    </system.web> 

    <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"/> 
    </system.webServer> 
    <system.serviceModel> 
    <behaviors> 
     <serviceBehaviors> 
     <behavior name=""> 
      <serviceMetadata httpGetEnabled="true" /> 
      <serviceDebug includeExceptionDetailInFaults="false" /> 
     </behavior> 
     </serviceBehaviors> 
     <endpointBehaviors> 
     <behavior name="WebBehavior"> 
      <webHttp /> 
     </behavior> 
     </endpointBehaviors> 
    </behaviors> 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> 
    <services> 
     <service name="WebApplication1.DataService"> 
     <endpoint address="ws" binding="wsHttpBinding" contract="WebApplication1.IDataService"/> 
     <endpoint address="" behaviorConfiguration="WebBehavior" 
      binding="webHttpBinding" 
      contract="WebApplication1.IDataService"> 
     </endpoint> 
     <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 
     </service> 
    </services> 
    </system.serviceModel> 
</configuration> 

Bây giờ tôi làm cho POST sau bởi Fiddler2 (quan trọng: Tôi đã đổi tên không gian tên của các loại có nguồn gốc để phù hợp với trường hợp của tôi):

POST http://localhost:47440/Service1.svc/SubmitDataEvents HTTP/1.1 
User-Agent: Fiddler 
Content-Type: text/json 
Host: localhost:47440 
Content-Length: 336 

{ 
    "DataEvents": [{ 
     "__type": "IntEvent:#WebApplication1", 
     "Id": 12345, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "Value": 5 
    }, { 
     "__type": "BoolEvent:#WebApplication1", 
     "Id": 45678, 
     "Timestamp": "\/Date(1324905383689+0000)\/", 
     "Value": true 
    }] 
} 

Sau đó, tôi có đoạn mã sau trong việc thực hiện dịch vụ:

public void SubmitDataEvents(DataPacket parameter) 
{ 
    foreach (DataEvent dataEvent in parameter.DataEvents) 
    { 
    var message = dataEvent.ToString(); 
    Debug.WriteLine(message); 
    } 
} 

Note debugger cho thấy các chi tiết mặt hàng như DataEvent s, nhưng đại diện chuỗi và mục đầu tiên trong từng chi tiết s cho thấy rõ ràng rằng tất cả các tiểu loại đã được deserialized tốt: Debugger screenshot

Và debug đầu ra chứa những điều sau đây sau khi tôi nhấn phương pháp:

IntEvent: 12345, 26.12.2011 20:16:23, 5 
BoolEvent: 45678, 26.12.2011 20:16:23, True 

Tôi cũng đã cố gắng chạy nó dưới IIS (trên Win7) và mọi thứ đều hoạt động tốt.

Tôi chỉ có loại cơ sở được deserialized sau khi tôi đã hỏng gói bằng cách xóa một dấu gạch dưới khỏi tên trường __type. Nếu tôi sửa đổi giá trị của __type, cuộc gọi sẽ bị lỗi trong quá trình deserialization, cuộc gọi sẽ không ảnh hưởng đến dịch vụ.

Dưới đây là những gì bạn có thể thử:

  1. Hãy chắc chắn rằng bạn không có bất kỳ thông điệp debug, trường hợp ngoại lệ, vv (kiểm tra gỡ lỗi Output).
  2. Tạo một giải pháp ứng dụng web sạch mới, dán mã được yêu cầu và kiểm tra xem nó có hoạt động ở đó hay không. Nếu có, thì dự án ban đầu của bạn phải có một số cài đặt cấu hình lạ.
  3. Trong trình gỡ lỗi, phân tích System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString() trong cửa sổ Xem.Nó sẽ chứa thông điệp XML được dịch từ JSON của bạn. Kiểm tra xem nó có đúng không.
  4. Kiểm tra xem bạn có bản cập nhật nào đang chờ xử lý cho .NET không.
  5. Hãy thử tracing WCF. Mặc dù nó dường như không phát ra bất kỳ cảnh báo nào cho các thông báo có sai tên __type tên trường, nó có thể xảy ra rằng nó sẽ hiển thị cho bạn một số gợi ý cho các lý do sự cố của bạn.

RequestMessage My

Có vẻ như đây là bài hát về vấn đề này: trong khi bạn có __type như yếu tố, tôi có nó như là thuộc tính. Giả sử, các hội đồng WCF của bạn có lỗi trong JSON sang bản dịch XML

<root type="object"> 
    <DataEvents type="array"> 
    <item type="object" __type="IntEvent:#WebApplication1"> 
     <Id type="number">12345</Id> 
     <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
     <Value type="number">5</Value> 
    </item> 
    <item type="object" __type="BoolEvent:#WebApplication1"> 
     <Id type="number">45678</Id> 
     <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
     <Value type="boolean">true</Value> 
    </item> 
    </DataEvents> 
</root> 

Tôi đã tìm thấy nơi mà __type được xử lý. Đây là:

// from System.Runtime.Serialization.Json.XmlJsonReader, System.Runtime.Serialization, Version=4.0.0.0 
void ReadServerTypeAttribute(bool consumedObjectChar) 
{ 
    int offset; 
    int offsetMax; 
    int correction = consumedObjectChar ? -1 : 0; 
    byte[] buffer = BufferReader.GetBuffer(9 + correction, out offset, out offsetMax); 
    if (offset + 9 + correction <= offsetMax) 
    { 
    if (buffer[offset + correction + 1] == (byte) '\"' && 
     buffer[offset + correction + 2] == (byte) '_' && 
     buffer[offset + correction + 3] == (byte) '_' && 
     buffer[offset + correction + 4] == (byte) 't' && 
     buffer[offset + correction + 5] == (byte) 'y' && 
     buffer[offset + correction + 6] == (byte) 'p' && 
     buffer[offset + correction + 7] == (byte) 'e' && 
     buffer[offset + correction + 8] == (byte) '\"') 
    { 
     // It's attribute! 
     XmlAttributeNode attribute = AddAttribute(); 
     // the rest is omitted for brevity 
    } 
    } 
} 

Tôi đã cố gắng tìm địa điểm mà thuộc tính được sử dụng để xác định loại được loại bỏ, nhưng không may mắn.

Hy vọng điều này sẽ hữu ích.

+0

Gói mà tôi đang gửi được tạo bằng cách gọi 'GetExampleDataEvents()' trước, sau đó thay đổi tên trong JSON thành 'dataEvents'. Tôi không bao gồm các tiêu đề HTTP nhưng tôi đã làm như bạn đã làm và gửi trong gói bằng Fiddler2 (bao gồm cả 'Content-Type: text/json'. –

+0

Điều đó rất lạ, vì JSON tôi đã đăng là ** chính xác ** JSON trả về bởi 'GetExampleDataEvents()' ** mà không có bất kỳ sửa đổi nào ** Bạn đã thử nó chưa? Nó có hoạt động không? Nếu nó đã làm, tại sao bạn lại cố thay đổi nó? Khi tôi thử JSON của bạn, tôi đã có danh sách trống , bởi vì nó đòi hỏi một lớp khác có chứa danh sách –

+0

Tôi đã cập nhật mô tả vấn đề với các khuyến nghị của bạn ở trên.Tuy nhiên tôi không có may mắn Tôi sợ hãi! –

Các vấn đề liên quan