C# XML POST 上的模型始终为空
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14068288/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Model always null on XML POST
提问by Wowca
I'm currently working on an integration between systems and I've decided to use WebApi for it, but I'm running into an issue...
我目前正在研究系统之间的集成,我决定使用 WebApi,但我遇到了一个问题......
Let's say I have a model:
假设我有一个模型:
public class TestModel
{
public string Output { get; set; }
}
and the POST method is:
POST方法是:
public string Post(TestModel model)
{
return model.Output;
}
I create a request from Fiddler with the header:
我使用标题从 Fiddler 创建了一个请求:
User-Agent: Fiddler
Content-Type: "application/xml"
Accept: "application/xml"
Host: localhost:8616
Content-Length: 57
and body:
与身体:
<TestModel><Output>Sito</Output></TestModel>
The modelparameter in the method Postis always nulland I have no idea why.
Does anyone have a clue?
方法中的model参数Post总是null,我不知道为什么。有人有线索吗?
采纳答案by nemesv
Two things:
两件事情:
You don't need quotes
""around the content type and accept header values in Fiddler:User-Agent: Fiddler Content-Type: application/xml Accept: application/xmlWeb API uses the
DataContractSerializerby default for xml serialization. So you need to include your type's namespace in your xml:<TestModel xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace"> <Output>Sito</Output> </TestModel>Or you can configure Web API to use
XmlSerializerin yourWebApiConfig.Register:config.Formatters.XmlFormatter.UseXmlSerializer = true;Then you don't need the namespace in your XML data:
<TestModel><Output>Sito</Output></TestModel>
您不需要
""在内容类型周围加上引号 并在 Fiddler 中接受标题值:User-Agent: Fiddler Content-Type: application/xml Accept: application/xmlWeb API
DataContractSerializer默认使用 xml 序列化。所以你需要在你的 xml 中包含你的类型的命名空间:<TestModel xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace"> <Output>Sito</Output> </TestModel>或者您可以配置 Web API 以
XmlSerializer在您的WebApiConfig.Register:config.Formatters.XmlFormatter.UseXmlSerializer = true;那么您不需要 XML 数据中的命名空间:
<TestModel><Output>Sito</Output></TestModel>
回答by barrypicker
While the answer is already awarded, I found a couple other details worth considering.
虽然答案已经给出,但我发现了一些值得考虑的其他细节。
The most basic example of an XML post is generated as part of a new WebAPI project automatically by visual studio, but this example uses a string as an input parameter.
最基本的 XML 帖子示例由 Visual Studio 自动生成为新 WebAPI 项目的一部分,但此示例使用字符串作为输入参数。
Simplified Sample WebAPI controller generated by Visual Studio
Visual Studio 生成的简化示例 WebAPI 控制器
using System.Web.Http;
namespace webAPI_Test.Controllers
{
public class ValuesController : ApiController
{
// POST api/values
public void Post([FromBody]string value)
{
}
}
}
This is not very helpful, because it does not address the question at hand. Most POST web services have rather complex types as parameters, and likely a complex type as a response. I will augment the example above to include a complex request and complex response...
这不是很有帮助,因为它没有解决手头的问题。大多数 POST Web 服务都具有相当复杂的类型作为参数,并且可能具有复杂的类型作为响应。我将扩充上面的示例以包含复杂的请求和复杂的响应......
Simplified sample but with complex types added
简化示例但添加了复杂类型
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; }
}
}
At this point, I can invoke with fiddler..
在这一点上,我可以用提琴手调用..
Fiddler Request Details
Fiddler 请求详细信息
Request Headers:
请求头:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 63
Request Body:
请求正文:
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
... and when placing a breakpoint in my controller I find the request object is null. This is because of several factors...
...当在我的控制器中放置断点时,我发现请求对象为空。这是由于几个因素...
- WebAPI defaults to using DataContractSerializer
- The Fiddler request does not specify content type, or charset
- The request body does not include XML declaration
- The request body does not include namespace definitions.
- WebAPI 默认使用 DataContractSerializer
- Fiddler 请求未指定内容类型或字符集
- 请求正文不包含 XML 声明
- 请求正文不包括命名空间定义。
Without making any changes to the web service controller, I can modify the fiddler request such that it will work. Pay close attention to the namespace definitions in the xml POST request body. Also, ensure the XML declaration is included with correct UTF settings that match the request header.
在不对 Web 服务控制器进行任何更改的情况下,我可以修改提琴手请求以使其正常工作。请密切注意 xml POST 请求正文中的命名空间定义。此外,请确保 XML 声明包含在与请求标头匹配的正确 UTF 设置中。
Fixed Fiddler request body to work with Complex datatypes
修复了 Fiddler 请求正文以处理复杂数据类型
Request Headers:
请求头:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 276
Content-Type: application/xml; charset=utf-16
Request body:
请求正文:
<?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>
Notice how the namepace in the request refers to the same namespace in my C# controller class (kind of). Because we have not altered this project to use a serializer other than DataContractSerializer, and because we have not decorated our model (class MyRequest, or MyResponse) with specific namespaces, it assumes the same namespace as the WebAPI Controller itself. This is not very clear, and is very confusing. A better approach would be to define a specific namespace.
请注意请求中的命名空间如何引用我的 C# 控制器类(有点)中的相同命名空间。因为我们没有改变这个项目来使用除 DataContractSerializer 之外的序列化器,并且因为我们没有用特定的命名空间修饰我们的模型(类 MyRequest 或 MyResponse),它假定与 WebAPI 控制器本身相同的命名空间。这不是很清楚,而且很混乱。更好的方法是定义特定的命名空间。
To define a specific namespace, we modify the controller model. Need to add reference to System.Runtime.Serialization to make this work.
为了定义一个特定的命名空间,我们修改了控制器模型。需要添加对 System.Runtime.Serialization 的引用才能完成这项工作。
Add Namespaces to model
将命名空间添加到模型
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; }
}
}
Now update the Fiddler request to use this namespace...
现在更新 Fiddler 请求以使用此命名空间...
Fiddler request with custom namespace
具有自定义命名空间的 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>
We can take this idea even further. If a empty string is specified as the namespace on the model, no namespace in the fiddler request is required.
我们可以将这个想法更进一步。如果将空字符串指定为模型上的命名空间,则 fiddler 请求中不需要命名空间。
Controller with empty string namespace
具有空字符串命名空间的控制器
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; }
}
}
Fiddler request with no namespace declared
未声明命名空间的 Fiddler 请求
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Other Gotchas
其他问题
Beware, DataContractSerializer is expecting the elements in the XML payload to be ordered alphabetically by default. If the XML payload is out of order you may find some elements are null (or if datatype is an integer it will default to zero, or if it is a bool it defaults to false). For example, if no order is specified and the following xml is submitted...
请注意,默认情况下,DataContractSerializer 期望 XML 负载中的元素按字母顺序排列。如果 XML 负载乱序,您可能会发现某些元素为空(或者如果数据类型是整数,则默认为零,或者如果数据类型是 bool,则默认为 false)。例如,如果没有指定订单并提交以下xml...
XML body with incorrect ordering of elements
元素顺序不正确的 XML 正文
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Name>MyName</Name>
<Age>99</Age>
</MyRequest>
... the value for Age will default to zero. If nearly identical xml is sent ...
... Age 的值将默认为零。如果发送几乎相同的 xml ...
XML body with correct ordering of elements
具有正确元素顺序的 XML 正文
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
then the WebAPI controller will correctly serialize and populate the Age parameter. If you wish to change the default ordering so the XML can be sent in a specific order, then add the 'Order' element to the DataMember Attribute.
然后 WebAPI 控制器将正确序列化并填充 Age 参数。如果您希望更改默认顺序,以便可以按特定顺序发送 XML,则将“Order”元素添加到 DataMember 属性。
Example of specifying a property order
指定属性顺序的示例
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; }
}
}
In this example, the xml body must specify the Name element before the Age element to populate correctly.
在此示例中,xml 正文必须在 Age 元素之前指定 Name 元素才能正确填充。
Conclusion
结论
What we see is that a malformed or incomplete POST request body (from perspective of DataContractSerializer) does not throw an error, rather is just causes a runtime problem. If using the DataContractSerializer, we need to satisfy the serializer (especially around namespaces). I have found using a testing tool a good approach - where I pass an XML string to a function which uses DataContractSerializer to deserialize the XML. It throws errors when deserialization cannot occur. Here is the code for testing an XML string using DataContractSerializer (again, remember if you implement this, you need to add a reference to System.Runtime.Serialization).
我们看到的是,格式错误或不完整的 POST 请求正文(从 DataContractSerializer 的角度来看)不会引发错误,而只会导致运行时问题。如果使用 DataContractSerializer,我们需要满足序列化器(尤其是命名空间周围)。我发现使用测试工具是一个很好的方法 - 我将一个 XML 字符串传递给一个使用 DataContractSerializer 反序列化 XML 的函数。当反序列化无法发生时,它会抛出错误。下面是使用 DataContractSerializer 测试 XML 字符串的代码(再次提醒,如果你实现了这个,你需要添加一个对 System.Runtime.Serialization 的引用)。
Example Testing Code for evaluation of DataContractSerializer de-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;
}
Options
选项
As pointed out by others, the DataContractSerializer is the default for WebAPI projects using XML, but there are other XML serializers. You could remove the DataContractSerializer and instead use XmlSerializer. The XmlSerializer is much more forgiving on malformed namespace stuff.
正如其他人所指出的,DataContractSerializer 是使用 XML 的 WebAPI 项目的默认设置,但还有其他 XML 序列化程序。您可以删除 DataContractSerializer 并改用 XmlSerializer。XmlSerializer 对格式错误的命名空间内容更加宽容。
Another option is to limit requests to using JSON instead of XML. I have not performed any analysis to determine if DataContractSerializer is used during JSON deserialization, and if JSON interaction requires DataContract attributes to decorate the models.
另一种选择是将请求限制为使用 JSON 而不是 XML。我没有进行任何分析来确定在 JSON 反序列化过程中是否使用了 DataContractSerializer,以及 JSON 交互是否需要 DataContract 属性来装饰模型。
回答by zekedaddy17
Once you make sure that you setup the Content-Typeheader to application/xmland set config.Formatters.XmlFormatter.UseXmlSerializer = true;in the Registermethod of the WebApiConfig.cs, it is important that you will not need any versioning or encoding at the top of your XML document.
一旦确定将Content-Type标头设置为application/xml并config.Formatters.XmlFormatter.UseXmlSerializer = true;在 的Register方法中设置WebApiConfig.cs,重要的是您将不需要在 XML 文档的顶部进行任何版本控制或编码。
This last piece was getting me stuck, hope this helps somebody out there and saves your time.
最后一块让我卡住了,希望这可以帮助那里的人并节省您的时间。
回答by Dave the Sax
I was trying to solve this for two days. Eventually I found out the outer tag needs to be the type name, not the variable name. Effectively, with the POST method as
我试图解决这个问题两天。最终我发现外部标签需要是类型名称,而不是变量名称。有效地,使用 POST 方法作为
public string Post([FromBody]TestModel model)
{
return model.Output;
}
I was providing the body
我提供身体
<model><Output>Sito</Output></model>
instead of
代替
<TestModel><Output>Sito</Output></TestModel>

