Wow*_*wca 43 .net c# http-post asp.net-web-api
我目前正致力于系统之间的集成,我决定使用WebApi,但我遇到了一个问题......
假设我有一个模型:
public class TestModel
{
public string Output { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
POST方法是:
public string Post(TestModel model)
{
return model.Output;
}
Run Code Online (Sandbox Code Playgroud)
我使用标题从Fiddler创建一个请求:
User-Agent: Fiddler
Content-Type: "application/xml"
Accept: "application/xml"
Host: localhost:8616
Content-Length: 57
Run Code Online (Sandbox Code Playgroud)
与身体:
<TestModel><Output>Sito</Output></TestModel>
Run Code Online (Sandbox Code Playgroud)
方法中的model参数Post总是如此null,我不知道为什么.有人有线索吗?
nem*_*esv 68
两件事情:
您不需要""内容类型的引号 并接受Fiddler中的标头值:
User-Agent: Fiddler
Content-Type: application/xml
Accept: application/xml
Run Code Online (Sandbox Code Playgroud)Web API DataContractSerializer默认使用xml序列化.因此,您需要在xml中包含类型的命名空间:
<TestModel
xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace">
<Output>Sito</Output>
</TestModel>
Run Code Online (Sandbox Code Playgroud)
或者您可以配置Web API以XmlSerializer在您的WebApiConfig.Register:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
Run Code Online (Sandbox Code Playgroud)
然后,您不需要XML数据中的命名空间:
<TestModel><Output>Sito</Output></TestModel>
Run Code Online (Sandbox Code Playgroud)bar*_*ker 57
虽然已经给出了答案,但我发现了其他一些值得考虑的细节.
XML文章的最基本示例是由visual studio自动生成的新WebAPI项目的一部分,但此示例使用字符串作为输入参数.
Visual Studio生成的简化示例WebAPI控制器
using System.Web.Http;
namespace webAPI_Test.Controllers
{
public class ValuesController : ApiController
{
// POST api/values
public void Post([FromBody]string value)
{
}
}
}
Run Code Online (Sandbox Code Playgroud)
这不是很有帮助,因为它没有解决手头的问题.大多数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; }
}
}
Run Code Online (Sandbox Code Playgroud)
此时,我可以用小提琴手调用..
小提琴请求详细信息
请求标题:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 63
Run Code Online (Sandbox Code Playgroud)
请求机构:
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Run Code Online (Sandbox Code Playgroud)
...当在我的控制器中放置一个断点时,我发现请求对象为空.这是因为有几个因素......
在不对Web服务控制器进行任何更改的情况下,我可以修改fiddler请求以使其正常工作.密切关注xml POST请求正文中的命名空间定义.此外,请确保XML声明包含在与请求标头匹配的正确UTF设置中.
修复了Fiddler请求主体使用复杂数据类型的问题
请求标题:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 276
Content-Type: application/xml; charset=utf-16
Run Code Online (Sandbox Code Playgroud)
请求机构:
<?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>
Run Code Online (Sandbox Code Playgroud)
请注意请求中的名称空间如何引用我的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; }
}
}
Run Code Online (Sandbox Code Playgroud)
现在更新Fiddler请求以使用此命名空间...
具有自定义命名空间的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>
Run Code Online (Sandbox Code Playgroud)
我们可以进一步理解这个想法.如果在模型上将空字符串指定为命名空间,则fiddler请求中不需要命名空间.
带空字符串命名空间的Controller
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; }
}
}
Run Code Online (Sandbox Code Playgroud)
没有声明名称空间的Fiddler请求
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Run Code Online (Sandbox Code Playgroud)
其他陷阱
请注意,DataContractSerializer期望默认情况下按字母顺序排序XML有效内容中的元素.如果XML有效负载乱序,您可能会发现某些元素为空(或者如果数据类型是整数,则它将默认为零,或者如果它是bool,则默认为false).例如,如果未指定订单并且提交了以下xml ...
XML主体,元素排序不正确
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Name>MyName</Name>
<Age>99</Age>
</MyRequest>
Run Code Online (Sandbox Code Playgroud)
... Age的值将默认为零.如果发送几乎相同的xml ...
具有正确元素排序的XML正文
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Run Code Online (Sandbox Code Playgroud)
然后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; }
}
}
Run Code Online (Sandbox Code Playgroud)
在此示例中,xml正文必须在Age元素正确填充之前指定Name元素.
结论
我们看到的是,格式错误或不完整的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;
}
Run Code Online (Sandbox Code Playgroud)
选项
正如其他人所指出的,DataContractSerializer是使用XML的WebAPI项目的默认设置,但还有其他XML序列化程序.您可以删除DataContractSerializer,而是使用XmlSerializer.XmlSerializer对格式错误的命名空间内容更加宽容.
另一种选择是限制使用JSON而不是XML的请求.我没有执行任何分析来确定在JSON反序列化期间是否使用DataContractSerializer,以及JSON交互是否需要DataContract属性来装饰模型.