2012-12-28 35 views
34

我目前正在對系統之間的整合,我已經決定要使用的WebAPI它,但我遇到的一個問題...模式總是在XML POST空

比方說,我有一個模型:

public class TestModel 
{ 
    public string Output { get; set; } 
} 

和POST方法是:

public string Post(TestModel model) 
{ 
    return model.Output; 
} 

創建從提琴手與頭的請求:

User-Agent: Fiddler 
Content-Type: "application/xml" 
Accept: "application/xml" 
Host: localhost:8616 
Content-Length: 57 

和身體:

<TestModel><Output>Sito</Output></TestModel> 

的方法Postmodel參數始終是null,我不知道爲什麼。 有沒有人有線索?

+0

你是如何從調用客戶端的'POST'方法?你確定它是一個HTTP POST嗎? – wal

+0

提琴手。此外,WebApi默認POST調用POST方法,GET GET方法,... – Wowca

+0

是的彼得,但你是否在下拉菜單中選擇了POST? (它默認爲GET) – wal

回答

54

兩件事情:

  1. 你不需要引號內容類型""和提琴手接受標頭值:

    User-Agent: Fiddler 
    Content-Type: application/xml 
    Accept: application/xml 
    
  2. 的Web API默認使用DataContractSerializer的XML序列化。所以,你需要在你的XML你的類型的命名空間:

    <TestModel 
    xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace"> 
        <Output>Sito</Output> 
    </TestModel> 
    

    或者你可以配置Web API中使用XmlSerializerWebApiConfig.Register

    config.Formatters.XmlFormatter.UseXmlSerializer = true; 
    

    那麼你不需要命名空間中的XML數據:

    <TestModel><Output>Sito</Output></TestModel> 
    
+3

太棒了! UseXmlSerializer工作!我把它放在'Global.asax.cs'文件中,作爲全局配置的一部分,但它看起來確實必須在'App_Start \ WebApiConfig.cs'中。 – Wowca

+0

我不知道WebAPI默認使用DataContractSerializer - 感謝提示! – barrypicker

+0

謝謝,花了一段時間尋找解決方案。 –

45

雖然這個答案已經頒發,我發現一對夫婦其他DET值得考慮的問題。

XML帖子的最基本示例是由Visual Studio自動生成的新WebAPI項目的一部分,但此示例使用字符串作爲輸入參數。由Visual Studio

using System.Web.Http; 
namespace webAPI_Test.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     // POST api/values 
     public void Post([FromBody]string value) 
     { 
     } 
    } 
} 

這產生

簡體樣品的WebAPI控制器是不是非常有幫助的,因爲它沒有解決眼下的問題。大多數POST Web服務具有相當複雜的類型作爲參數,並且可能是複雜的類型作爲響應。我將增加上面的例子爲包括複雜的請求,並響應複合...

簡化樣品但是具有複雜類型加入

using System.Web.Http; 
namespace webAPI_Test.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     // POST api/values 
     public MyResponse Post([FromBody] MyRequest value) 
     { 
      var response = new MyResponse(); 
      response.Name = value.Name; 
      response.Age = value.Age; 
      return response; 
     } 
    } 

    public class MyRequest 
    { 
     public string Name { get; set; } 
     public int Age { get; set; } 
    } 

    public class MyResponse 
    { 
     public string Name { get; set; } 
     public int Age { get; set; } 
    } 
} 

此時,我可以使用Fiddler調用..

提琴手請求細節

請求報頭:

User-Agent: Fiddler 
Host: localhost:54842 
Content-Length: 63 

請求正文:

<MyRequest> 
    <Age>99</Age> 
    <Name>MyName</Name> 
</MyRequest> 

...在我的控制器放置一個斷點,當我找到所求對象空值。這是因爲以下幾個因素...

  • 的WebAPI默認使用的DataContractSerializer
  • 小提琴手請求未指定內容類型,或charset
  • 請求體不包括XML聲明
  • 請求正文不包含名稱空間定義。

如果不對Web服務控制器進行任何更改,我可以修改提琴手請求以使其工作。密切關注xml POST請求正文中的名稱空間定義。此外,請確保XML聲明包含在與請求標頭匹配的正確UTF設置中。

固定提琴手請求體與複雜數據類型

請求頭工作:

User-Agent: Fiddler 
Host: localhost:54842 
Content-Length: 276 
Content-Type: application/xml; charset=utf-16 

請求正文:

<?xml version="1.0" encoding="utf-16"?> 
    <MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/webAPI_Test.Controllers"> 
     <Age>99</Age> 
     <Name>MyName</Name> 
    </MyRequest> 

通知在請求中namepace如何指的是相同的命名空間在我的C#控制器類(種)。因爲我們沒有改變這個項目使用DataContractSerializer以外的序列化程序,並且因爲我們沒有使用特定的命名空間修飾我們的模型(類MyRequest或MyResponse),所以它假定與WebAPI控制器本身具有相同的命名空間。這不是很清楚,而且很混亂。更好的方法是定義一個特定的命名空間。

要定義特定的命名空間,我們修改控制器模型。需要添加對System.Runtime.Serialization的引用才能完成此工作。

添加命名空間

using System.Runtime.Serialization; 
using System.Web.Http; 
namespace webAPI_Test.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     // POST api/values 
     public MyResponse Post([FromBody] MyRequest value) 
     { 
      var response = new MyResponse(); 
      response.Name = value.Name; 
      response.Age = value.Age; 
      return response; 
     } 
    } 

    [DataContract(Namespace = "MyCustomNamespace")] 
    public class MyRequest 
    { 
     [DataMember] 
     public string Name { get; set; } 

     [DataMember] 
     public int Age { get; set; } 
    } 

    [DataContract(Namespace = "MyCustomNamespace")] 
    public class MyResponse 
    { 
     [DataMember] 
     public string Name { get; set; } 

     [DataMember] 
     public int Age { get; set; } 
    } 
} 

模型現在更新提琴手請求使用此命名空間...

與自定義命名空間提琴手要求

<?xml version="1.0" encoding="utf-16"?> 
    <MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="MyCustomNamespace"> 
     <Age>99</Age> 
     <Name>MyName</Name> 
    </MyRequest> 

我們可以採取這個想法更進一步。如果將空字符串指定爲模型上的名稱空間,則不需要提琴手請求中的名稱空間。

控制器與空字符串命名空間

using System.Runtime.Serialization; 
using System.Web.Http; 

namespace webAPI_Test.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     // POST api/values 
     public MyResponse Post([FromBody] MyRequest value) 
     { 
      var response = new MyResponse(); 
      response.Name = value.Name; 
      response.Age = value.Age; 
      return response; 
     } 
    } 

    [DataContract(Namespace = "")] 
    public class MyRequest 
    { 
     [DataMember] 
     public string Name { get; set; } 

     [DataMember] 
     public int Age { get; set; } 
    } 

    [DataContract(Namespace = "")] 
    public class MyResponse 
    { 
     [DataMember] 
     public string Name { get; set; } 

     [DataMember] 
     public int Age { get; set; } 
    } 
} 

沒有命名空間提琴手請求宣告

<?xml version="1.0" encoding="utf-16"?> 
    <MyRequest> 
     <Age>99</Age> 
     <Name>MyName</Name> 
    </MyRequest> 

其他陷阱

當心,DataContractSerializer的是個期待XML負載中的e元素默認按字母順序排列。如果XML有效負載出現故障,您可能會發現一些元素爲空(或者,如果數據類型是整數,它將默認爲零,或者如果它是bool,則默認爲false)。例如,如果沒有指定順序,下面的XML提交...

XML體元素

<?xml version="1.0" encoding="utf-16"?> 
<MyRequest> 
    <Name>MyName</Name> 
    <Age>99</Age> 
</MyRequest> 

的順序不正確的......時代的價值將默認爲零。如果幾乎相同的XML發送...

XML體元素

<?xml version="1.0" encoding="utf-16"?> 
<MyRequest> 
    <Age>99</Age> 
    <Name>MyName</Name> 
</MyRequest> 

的正確排序,則控制器的WebAPI將正確序列化和填充年齡參數。如果您希望更改默認排序,以便可以按特定順序發送XML,請將「Order」元素添加到DataMember屬性。

指定屬性順序

using System.Runtime.Serialization; 
using System.Web.Http; 

namespace webAPI_Test.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     // POST api/values 
     public MyResponse Post([FromBody] MyRequest value) 
     { 
      var response = new MyResponse(); 
      response.Name = value.Name; 
      response.Age = value.Age; 
      return response; 
     } 
    } 

    [DataContract(Namespace = "")] 
    public class MyRequest 
    { 
     [DataMember(Order = 1)] 
     public string Name { get; set; } 

     [DataMember(Order = 2)] 
     public int Age { get; set; } 
    } 

    [DataContract(Namespace = "")] 
    public class MyResponse 
    { 
     [DataMember] 
     public string Name { get; set; } 

     [DataMember] 
     public int Age { get; set; } 
    } 
} 

在本例的實施例中,XML主體必須指定名稱元素之前的年齡元件正確填充。

結論

我們看到的是一個畸形的或不完整的POST請求體(從DataContractSerializer的透視)不會引發錯誤,而只是導致運行時的問題。如果使用DataContractSerializer,我們需要滿足序列化程序(尤其是名稱空間周圍)。我發現使用測試工具是一種很好的方法 - 我將XML字符串傳遞給使用DataContractSerializer反序列化XML的函數。它在反序列化不能發生時拋出錯誤。以下是使用DataContractSerializer測試XML字符串的代碼(同樣,請記住如果您實現此目的,則需要添加對System.Runtime.Serialization的引用)。

實例測試代碼爲DataContractSerializer的反序列化

public MyRequest Deserialize(string inboundXML) 
{ 
    var ms = new MemoryStream(Encoding.Unicode.GetBytes(inboundXML)); 
    var serializer = new DataContractSerializer(typeof(MyRequest)); 
    var request = new MyRequest(); 
    request = (MyRequest)serializer.ReadObject(ms); 

    return request; 
} 

方案進行評估

正如其他人所指出的,DataContractSerializer的是使用XML的WebAPI項目的默認,但也有其他XML序列化器。您可以刪除DataContractSerializer,而是使用XmlSerializer。 XmlSerializer更加容忍格式錯誤的命名空間。

另一種選擇是將請求限制爲使用JSON而不是XML。我沒有執行任何分析來確定在JSON反序列化過程中是否使用了DataContractSerializer,並且JSON交互是否需要使用DataContract屬性來裝飾模型。

+0

真棒謝謝! – jaxxbo

+1

殺手回答。驚訝於發送/接收XML的困難程度,因爲這應該是REST服務的要點! – Rocklan

+0

兩點 - 1.我需要添加一個對System.Net.Http.Formatting的引用,以便將UseXmlSerializer設置爲true。 2.我需要在請求頭中添加「Accept:application/xml」,以便能夠從請求中獲取XML(看起來像它默認爲JSON)。 – Rocklan

1

確保您將Content-Type標頭設置爲application/xml並設置config.Formatters.XmlFormatter.UseXmlSerializer = true;在WebApiConfig.cs的Register方法中,重要的是,在XML文檔的頂部不需要任何版本控制或編碼。

這最後一塊讓我陷入困境,希望這有助於有人在那裏,並節省您的時間。

0

我試圖解決這個問題兩天。最終我發現外部標籤需要是類型名稱,而不是變量名稱。實際上,與POST方法

public string Post([FromBody]TestModel model) 
{ 
    return model.Output; 
} 

我提供身體

<model><Output>Sito</Output></model> 

代替

<TestModel><Output>Sito</Output></TestModel>