XML POST模型始终为空

我目前正在研究系统之间的集成,我决定使用WebApi,但是我遇到了一个问题。

比方说,我有一个模型:

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

而POST方法是:

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

我用Fiddler的标题创build一个请求:

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

与身体:

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

Post方法中的model参数始终为null ,我不知道为什么。 有人有线索吗?

两件事情:

  1. 您不需要在内容types周围引用"" ,并接受Fiddler中的标头值:

     User-Agent: Fiddler Content-Type: application/xml Accept: application/xml 
  2. Web API默认使用DataContractSerializer进行xml序列化。 所以你需要在你的xml中包含你的types的命名空间:

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

    或者,您可以将Web APIconfiguration为在您的WebApiConfig.Register使用XmlSerializer

     config.Formatters.XmlFormatter.UseXmlSerializer = true; 

    那么你不需要XML数据中的命名空间:

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

虽然答案已经被授予,但我发现了其他一些值得考虑的细节。

XMLpost的最基本的例子是由visual studio自动生成的一个新的WebAPI项目的一部分,但是这个例子使用一个string作为input参数。

由Visual Studio生成的简单示例WebAPI控制器

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

这不是很有帮助,因为它没有解决手头的问题。 大多数POST Web服务具有相当复杂的types作为参数,并且可能是复杂的types作为响应。 我将增加上面的例子,以包含一个复杂的请求和复杂的响应…

简单的样本,但添加了复杂的types

 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; } } } 

在这一点上,我可以调用小提琴手

提琴手请求细节

请求标题:

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

请求正文:

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

…当在我的控制器中放置一个断点时,我发现请求对象为空。 这是因为几个因素…

  • WebAPI默认使用DataContractSerializer
  • Fiddler请求不指定内容types或字符集
  • 请求体不包含XML声明
  • 请求主体不包含名称空间定义。

在不对Web服务控制器进行任何更改的情况下,我可以修改提琴手请求以使其工作。 密切关注xml POST请求正文中的命名空间定义。 此外,请确保XML声明包含在与请求标头匹配的正确UTF设置中。

修复了Fiddler请求正文以处理复杂的数据types

请求标题:

 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> 

请注意,请求中的名称空间是如何引用我的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; } } } 

现在更新Fiddler请求来使用这个命名空间…

与自定义命名空间的提琴手请求

 <?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> 

我们可以进一步采纳这个想法。 如果将空string指定为模型上的名称空间,则不需要提琴手请求中的名称空间。

带空string命名空间的控制器

 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有效内容中的元素默认按字母顺序排列。 如果XML有效负载无序,则可能会发现某些元素为空(或者如果数据types是整数,则它将默认为零,或者如果它是bool,则默认为false)。 例如,如果没有指定订单并提交以下xml …

元素sorting不正确的XML正文

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

Age的值将默认为零。 如果几乎相同的XML发送…

具有正确的元素sorting的XML正文

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

那么WebAPI控制器将正确地序列化并填充Age参数。 如果您希望更改默认顺序,以便可以按特定顺序发送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主体必须指定Age元素之前的Name元素才能正确填充。

结论

我们所看到的是一个格式不正确或不完整的POST请求体(从DataContractSerializer的angular度来看)不会引发错误,而只是导致运行时问题。 如果使用DataContractSerializer,我们需要满足序列化程序(特别是在命名空间周围)。 我发现使用testing工具是一个很好的方法 – 将XMLstring传递给使用DataContractSerializer反序列化XML的函数。 当反序列化不能发生时,会引发错误。 这里是使用DataContractSerializertestingXMLstring的代码(再次记住,如果你实现这个,你需要添加一个对System.Runtime.Serialization的引用)。

示例testing用于评估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在格式错误的命名空间上更加宽容。

另一种select是将请求限制为使用JSON而不是XML。 我还没有执行任何分析来确定在JSON反序列化过程中是否使用了DataContractSerializer,并且如果JSON交互需要DataContract属性来装饰模型。

一旦你确定你将Content-Type头部设置为application / xml,并设置config.Formatters.XmlFormatter.UseXmlSerializer = true; 在WebApiConfig.cs的Register方法中,重要的是,在XML文档的顶部不需要任何版本或编码。

这最后一块是让我卡住,希望这有助于有人在那里,节省您的时间。