2012-12-28 55 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>