【问题标题】:Why does POST parameter type have to be generic Object type?为什么 POST 参数类型必须是泛型 Object 类型?
【发布时间】:2015-02-24 22:16:45
【问题描述】:

使用 asp.net web api v2,我有一个有效的 POST 方法,我能够从不同的应用程序发布自定义类型,并且通过使用 JSONConvert,我能够反序列化它并在我的 POST 方法主体中使用它。

但是,我的 POST 的参数必须是“object”类型,否则找不到参数(null)。

为什么会这样?理想情况下,我会将自定义类型作为参数类型,以便我的 API 文档可以填充正确的请求信息,因为它会根据使用的参数类型自动生成 API 文档(看不到覆盖它的方法 - - 如果可以的话,那就太好了)。

请参阅下面的代码——如果“incomingInformation”是“RemoteFileInfo”类型而不是“object”类型,当我尝试对其进行 .toString() 时会引发空异常。

[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>();
...

这是我在发送应用程序 (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

下面是我的 remoteFileInfo 类型,它在接收应用程序和发送应用程序上都以这种方式声明。通过 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

接收应用定义:

 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;
}

来自发送应用程序的 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"}"

来自提琴手的完整 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!\"}"

【问题讨论】:

  • 这可能与不正确的 ContentType 有关。有关示例,请参见 stackoverflow.com/questions/20226169/…
  • 这不是您所要求的,但您可以输入 JObject _incomingInformation = incomingInformation as JObject; 这会将 _incomingInformation 识别为 typeof(JObject) 并使用传入Information 参数传递的值。
  • @B2K 请查看已编辑的帖子——我确实有您链接帖子中总结的 application/json 类型。您看到的任何其他可能导致此问题的原因?
  • 您没有提供足够的信息来提供明确的答案。你发的json数据和RemoteFileInfo的定义在哪里?
  • @B2K RemoteFileInfo 的定义已添加。如果有帮助,我可以粘贴一个示例原始 json

标签: c# json rest data-structures asp.net-web-api


【解决方案1】:

这种行为的原因是您的参数必须可以通过请求字符串传输。在您的Route 属性中,您是说对您的方法的请求应该是这样的:api/xx/uploadfiletoalfresco/。并且浏览器无法在查询字符串中为您的对象创建字符串表示形式 - 这只能用于字符串或值类型,例如整数。

所以您有两个选择:让事情保持原样或使用 WCF 服务,您可以在其中为您的类型提供 WSDL。

【讨论】:

  • 浏览器查询字符串中没有传递参数——只有post数据。发布数据可以表示为字符串 (json)。我确实看到了许多成功发布自定义类型的示例,例如线程stackoverflow.com/questions/20226169/…
  • @nismonster 你能提供一个带有Route 属性的工作样本吗?
  • 我删除了 Route 属性并观察到相同的处理方式——incomingInformation 为空,除非在我的接收应用程序上定义为对象类型。因此,无论 Route 属性如何,问题仍然存在。我最初有 Route 以在开发时提供视觉帮助,但由于我遵循 api/{controller}/{method} 的默认 routeTemplate,因此没有必要。
【解决方案2】:

我刚刚使用您在帖子中提供的数据使用 WebAPI 2 对此进行了测试,并将 string 用于 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;
        }
    }

绑定模型不需要手动反序列化

实际上,如果您使用string,那么 Web API 模型绑定器将为您完成所有工作:

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

但是,如果您确实需要 fileByteArray 成为 Byte[] 的数组,您将需要从 RemoteFileInfo(这只是一个 DTO,因此永远不应该离开 API 控制器)到另一个类进行一些转换在你处理它之前。

您可以编写自定义模型绑定器,但我认为接受更简单的模型并显式转换更简单。

更新

我认为我的回答可能有点混乱,所以我会尝试澄清我的想法以及我认为在这种情况下发生了什么。

  1. WebAPI 模型绑定器正在查看 POST 数据,然后尝试查看它是否适合 Action 方法上声明的参数类型。如果这是object,那么它将起作用,因此允许它以incomingInformation 的形式向方法提供非空的object 实例

  2. 您的代码正在手动处理序列化,显然JsonConvert 能够处理将string 转换为Byte[]

  3. 您在尝试将 incomingInformation 声明为类型 RemoteFileInfo 时看到失败的原因是因为默认 WebAPI 模型绑定器无法处理 string-&gt;Byte[] 的转换因此放弃尝试将传入的 POST 数据序列化为RemoteFileInfo 的实例,而是将null 传递给方法的incomingInformation 参数。

  4. 当然,任何试图引用incomingInformation 或对incomingInformation 执行任何操作的尝试都会以NullReferenceException 失败,以此类推。

  5. 让 Web API 模型绑定器成功绑定该模型的唯一方法是 a) 将 fileByteArray 更改为字符串(因为它是线上的字符串),在这种情况下,我的第二个代码示例应该只是工作,或者 b) 编写自定义模型绑定器(个人,不是粉丝)

我当然可能完全错了,但由于你的代码对我使用 Fiddler 和我提到的 chanes 来说很好,我怀疑上面的几点是这种情况,或者还有其他一些你没有包括在这里的因素。

【讨论】:

  • 嗨斯蒂芬,谢谢你的帖子。实际上,完全删除 FileByteArray 会导致同样的问题。我在序列化或反序列化 JSON 时没有问题;我只是无法将 POST 数据作为“RemoteFileInfo”而不是对象接收。在您的示例代码中,您有 public string Test(object model);将我的问题转换为您的代码是我想要公共字符串测试(RemoteFileInfo 模型)。文件字节数组不是问题,因为删除它会得到相同的结果。
  • @nismonster 这很奇怪,因为我发现使用您的代码和您的数据没有任何问题,除非接收类型使用 Byte[] 表示fileByteArray - 在这种情况下,模型变量将是 @ 987654346@ 并且我在 ToString() 调用中观察到异常(因为 null 模型)。我在答案中注意到了这一点。
【解决方案3】:

感谢所有评论的人。事实证明,问题与使用 JsonConvert.SerializeObject 有关。虽然在 Fiddler 中 json 字符串看起来很完美,但当我一时兴起将序列化方法切换到 JavaScriptSerializer().Serialize 时,RemoteFileInfo 对象被成功传递和接收(完整字节数组!)

这是我的请求的最终代码,它将允许接收应用程序将 RemoteFileInfo 作为参数类型。仍然不确定为什么 JsonConvert.Serialize 不起作用; 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();
            }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-06
    • 2023-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多