简化样品但是具有复杂类型加入
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属性来装饰模型。
你是如何从调用客户端的'POST'方法?你确定它是一个HTTP POST吗? – wal
提琴手。此外,WebApi默认POST调用POST方法,GET GET方法,... – Wowca
是的彼得,但你是否在下拉菜单中选择了POST? (它默认为GET) – wal