简体   繁体   English

为什么POST参数类型必须是通用的对象类型?

[英]Why does POST parameter type have to be generic Object type?

Using asp.net web api v2, I have a working POST method that I am able to POST a custom type from a different application, and by using JSONConvert I am able to deserialize it and use it in my POST method body. 使用asp.net web api v2,我有一个工作的POST方法,我可以从不同的应用程序POST一个自定义类型,并通过使用JSONConvert我能够反序列化它并在我的POST方法体中使用它。

However, the parameter to my POST must be of type "object" or else the parameter is not found (null). 但是,我的POST参数必须是“object”类型,否则找不到参数(null)。

Why does this happen? 为什么会这样? I would ideally have the custom type as the parameter type, so that my API Documentation can populate with the proper request info, since it auto-generates the API docs based on the parameter type used (don't see a way to override that -- would be great if that was possible). 我理想情况下将自定义类型作为参数类型,以便我的API文档可以填充正确的请求信息,因为它会根据所使用的参数类型自动生成API文档(看不到覆盖它的方法 - - 如果可能的话会很棒)。

See my code below -- if "incomingInformation" is of type "RemoteFileInfo" rather than type "object", a null exception is thrown when I try to .toString() it. 请参阅下面的代码 - 如果“incomingInformation”的类型为“RemoteFileInfo”而不是类型“object”,则在尝试.toString()时会抛出null异常。

[Route("api/xx/uploadfiletoalfresco/")]
    [HttpPost()]
    public ResultStruct UploadFileToAlfresco(object incomingInformation)
    {
        JObject deserializedJObject = (JObject)JsonConvert.DeserializeObject(incomingInformation.ToString());
        SA.Services.RemoteFileInfo convertedRemoteFileInfo = deserializedJObject.ToObject<SA.Services.RemoteFileInfo>();
...

Here is my sample code on the sending application (vb.net) - the content type is set as application/json and is serialized before sending 这是我在发送应用程序(vb.net)上的示例代码 - 内容类型设置为application / json并在发送之前被序列化

Dim req As WebRequest = WebRequest.Create(_restEndpointURL & "/uploadfiletoalfresco/")
    req.ContentType = "application/json"
    req.Method = "POST"
    Using sw As New StreamWriter(req.GetRequestStream())
        Dim ser As New JavaScriptSerializer
        Dim serJSON = ser.Serialize(JsonConvert.SerializeObject(remoteFileInfo))
        sw.Write(serJSON)
        sw.Flush()
        sw.Close()
    End Using

Below is my remoteFileInfo type, it is declared this way on both the receiving app and sending app. 下面是我的remoteFileInfo类型,它在接收应用程序和发送应用程序上都以这种方式声明。 It is converted to JSON string before sending by the method JsonConvert.SerializeObject 在通过JsonConvert.SerializeObject方法发送之前,它将转换为JSON字符串

Partial Public Class RemoteFileInfo
    Public CategoryID As Integer
    Public FileName As String
    Public Length As Long
    Public Note As String
    Public SFSubmissionID As String
    Public SourceInstance As String
    Public Subject As String
    Public UserID As Integer
    Public Visibility As Boolean
    Public riskID As Integer
    Public fileByteArray As Byte()
End Class

Receiving app definition: 接收应用定义:

 public class RemoteFileInfo
{

    public int CategoryID;
    public string FileName;
    public long Length;
    public string Note;
    public string SFSubmissionID;
    public string SourceInstance;
    public string Subject;
    public int UserID;
    public bool Visibility;
    public int riskID;
    public Byte[] fileByteArray;
}

Sample JSON from the sending application: 来自发送应用程序的示例JSON:

"{"CategoryID":2,"FileName":"Scrum postponed until this afternoon .msg","Length":62976,"Note":"asdf","SFSubmissionID":"006E000000OuYxP","SourceInstance":"Addin","Subject":"Scrum postponed until this afternoon ","UserID":0,"Visibility":true,"riskID":0,"fileByteArray":"VERY LONG STRING"}"

Full JSON from fiddler: 来自fiddler的完整JSON:

POST http://yyyy/api/xxx/uploadfiletoalfresco/ HTTP/1.1
Content-Type: application/json
Host: yyyyy
Content-Length: 84273
Expect: 100-continue
Connection: Keep-Alive

"{\"CategoryID\":2,\"FileName\":\"Scrum postponed until this afternoon .msg\",\"Length\":62976,\"Note\":\"asdf\",\"SFSubmissionID\":\"006E000000OuYxP\",\"SourceInstance\":\"Addin\",\"Subject\":\"Scrum postponed until this afternoon \",\"UserID\":0,\"Visibility\":true,\"riskID\":0,\"fileByteArray\":\"VERY LONG STRING - user edited this is not how it looks in fiddler!\"}"

The reason for a such behaviour is that your parameters must be transportable via the request string. 出现这种行为的原因是您的参数必须通过请求字符串传输。 In your Route attribute you are saying that the request for your method should be like this: api/xx/uploadfiletoalfresco/ . 在您的Route属性中,您说您的方法请求应如下所示: api/xx/uploadfiletoalfresco/ And there is no way the browser will be available to create a string representation for your object in a query string - this can be done only for a strings or value types such as integers. 并且浏览器无法在查询字符串中为对象创建字符串表示形式 - 这只能用于字符串或值类型(如整数)。

So you have two choices: let the things stay as they are or use the WCF services, where you can provide a WSDL for your types. 因此,您有两个选择:让事物保持原样或使用WCF服务,您可以在其中为类型提供WSDL。

I just tested this with WebAPI 2 using the data you supplied in your post, and using string for fileByteArray and declaring the model as RemoteFileInfo works just fine for me. 我刚刚使用您在帖子中提供的数据使用WebAPI 2对此进行了测试,并使用string for fileByteArray并将模型声明为RemoteFileInfo对我来说效果很好。

 public class RemoteFileInfo
    {

        public int CategoryID;
        public string FileName;
        public long Length;
        public string Note;
        public string SFSubmissionID;
        public string SourceInstance;
        public string Subject;
        public int UserID;
        public bool Visibility;
        public int riskID;
        public string fileByteArray;
    }


    public class ValuesController : ApiController
    {
        [HttpPost]
        public string Test(object incomingInformation)
        {
            JObject deserializedJObject = (JObject)JsonConvert.DeserializeObject(incomingInformation.ToString());
            var convertedRemoteFileInfo = deserializedJObject.ToObject<RemoteFileInfo>();
            return convertedRemoteFileInfo.fileByteArray;
        }
    }

No manual deserialisation required to bind the model 绑定模型不需要手动反序列化

Actually if you do use string then the Web API model binder will do all of the work for you: 实际上,如果您使用string那么Web API模型绑定器将为您完成所有工作:

 [HttpPost]
 public string Test(RemoteFileInfo incomingInformation)
 {
      return incomingInformation.fileByteArray; //or whatever
 }

But, you will need to do some conversion from RemoteFileInfo (which is just a DTO and therefore should really never leave the API Controller) to another class if you really need fileByteArray to be an array of Byte[] before you process it. 但是,如果在处理它之前确实需要fileByteArrayByte[]数组,则需要从RemoteFileInfo (它只是一个DTO,因此应该永远不会离开API控制器)进行一些转换。

You could write a custom model binder but I think it's simpler to accept the simpler model and convert explicitly. 您可以编写自定义模型绑定器,但我认为接受更简单的模型并明确转换更简单。

Update 更新

I think there may be a bit of confusion about my answer so I'll attempt to clarify my thinking and what I think is going on in this case. 我想我的答案可能会有些混乱,所以我会试着澄清我的想法以及我认为在这种情况下会发生什么。

  1. WebAPI Model Binder is looking at the POST data and then trying to see if it can fit it into the parameter type declared on the Action method . WebAPI Model Binder正在查看POST数据,然后尝试查看它是否适合Action方法中声明的参数类型 In the case that this is object then that will work and so that allows it to supply a non-null object instance to the method in the form of incomingInformation 如果这是object那么它将起作用,因此允许它以incomingInformation的形式向方法提供非null object实例

  2. Your code is manually handling serialisation and obviously JsonConvert is able to handle converting a string to a Byte[] 您的代码是手动处理序列化,显然JsonConvert能够处理将string转换为Byte[]

  3. The reason why you are seeing failure when trying to declare incomingInformation as type RemoteFileInfo is because the default WebAPI Model Binder is not able to handle a conversion of string->Byte[] and therefore gives up trying to serialise the incoming POST Data into an instance of RemoteFileInfo and instead passes null to the method's incomingInformation parameter. 在尝试将incomingInformation声明为RemoteFileInfo类型时,您看到失败的原因是因为默认的WebAPI Model Binder 无法处理 string- string->Byte[]的转换,因此放弃尝试将传入的POST数据序列化为实例RemoteFileInfo ,而是将null传递给方法的incomingInformation参数。

  4. At that point of course any attempt to refer to or perform any action on incomingInformation will fail with NullReferenceException and so on. 在这一点上,任何尝试引用或执行任何对incomingInformation都将失败,并且NullReferenceException等等。

  5. The only way to get the Web API Model binder to sucessfully bind that model is a)change fileByteArray to a string (because it is a string on the wire) in which case my second code sample should just work, or b) write a custom Model Binder (personally, not a fan) 让Web API模型绑定器成功绑定该模型的唯一方法是a)将fileByteArray更改为字符串(因为它是线上的字符串),在这种情况下,我的第二个代码示例应该正常工作,或者b)编写自定义Model Binder(个人而非粉丝)

I could of course be totally wrong but since your code works fine for me using Fiddler with the chanes I mentioned, I suspect that the points above are the situation or that there is some other factor you haven't included here. 我当然可能完全错了,但由于你的代码对我使用Fiddler和我提到的chanes很好,我怀疑上面的观点是这种情况还是你还没有包含其他一些因素。

Thanks to all who commented. 感谢所有评论的人。 It turns out the problem was something to do with using the JsonConvert.SerializeObject. 事实证明这个问题与使用JsonConvert.SerializeObject有关。 Although in Fiddler the json string looked perfect, when on a whim I switched serialization methods to JavaScriptSerializer().Serialize, the RemoteFileInfo object is successfully passed and received (with full byte array in tact!) 虽然在Fiddler中,json字符串看起来很完美,但我突然想到将序列化方法切换到JavaScriptSerializer()。Serialize,RemoteFileInfo对象成功传递和接收(完整的字节数组!)

Here is the final code of my request, which will allow the receiving app to have the RemoteFileInfo as the parameter type. 这是我的请求的最终代码,它将允许接收应用程序将RemoteFileInfo作为参数类型。 Still not sure why the JsonConvert.Serialize won't work; 仍然不确定为什么JsonConvert.Serialize不起作用; the Newstonsoft package was installed on both the client and receiving app. Newstonsoft软件包安装在客户端和接收应用程序上。

var httpWebRequest = (HttpWebRequest)WebRequest.Create(@"http://yyyy/api/xxx/uploadfiletoalfresco/");
            httpWebRequest.ContentType = "application/json";
            httpWebRequest.Method = "POST";

            using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
            {
                string json = new JavaScriptSerializer().Serialize(remoteFileInfo);
                streamWriter.Write(json);
            }

            var response = (HttpWebResponse)httpWebRequest.GetResponse();
            using (var streamReader = new StreamReader(response.GetResponseStream()))
            {
                var result = streamReader.ReadToEnd();
            }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM