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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 10:35:58  来源:igfitidea点击:

Model always null on XML POST

c#.netasp.net-web-apihttp-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:

两件事情:

  1. You don't need quotes ""around the content type and accept header values in Fiddler:

    User-Agent: Fiddler
    Content-Type: application/xml
    Accept: application/xml
    
  2. Web 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 your WebApiConfig.Register:

    config.Formatters.XmlFormatter.UseXmlSerializer = true;
    

    Then you don't need the namespace in your XML data:

     <TestModel><Output>Sito</Output></TestModel>
    
  1. 您不需要""在内容类型周围加上引号 并在 Fiddler 中接受标题值:

    User-Agent: Fiddler
    Content-Type: application/xml
    Accept: application/xml
    
  2. Web APIDataContractSerializer默认使用 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/xmlconfig.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>