C# WebAPI发布JSON字符串并将其映射到模型

C# WebAPI发布JSON字符串并将其映射到模型,c#,asp.net-web-api,asp.net-web-api2,C#,Asp.net Web Api,Asp.net Web Api2,我必须创建使用JSON消息的webhook端点。 消息以x-www-form-URL格式发送,格式为: key=json 值={“用户Id”:“728409840”,“呼叫Id”:“1114330”,“应答时间”:“2015-04-16 15:37:47”} 如邮递员所示: 请求如下所示: public class CustomModelBinder : IModelBinder { public bool BindModel(HttpActionContext actionConte

我必须创建使用JSON消息的webhook端点。
消息以x-www-form-URL格式发送,格式为:

key=json
值={“用户Id”:“728409840”,“呼叫Id”:“1114330”,“应答时间”:“2015-04-16 15:37:47”}

如邮递员所示:

请求如下所示:

public class CustomModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var body = actionContext.Request.Content.ReadAsStringAsync().Result;
        body = body.Replace("json=", "");
        var json = HttpUtility.UrlDecode(body);

        bindingContext.Model = JsonConvert.DeserializeObject<CallDetails>(json);

        return true;
    }
}
json=%7B%22user_Id%22%3A+%2272840840%22%2C+%22call_Id%22%3A+%2211114330%22%2C%22responsed_time%22%3A+%222015-04-16+15%3A37%3A47%22%7D

要从请求中获取值作为我的类(模型),我必须创建包含单个字符串属性的临时对象:

public class Tmp
{
    public string json { get; set; }
}
以及我的控制器中使用该请求的方法:

[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(Tmp tmp)
{
    JObject json2 = JObject.Parse(tmp.json);
    var details = json2.ToObject<CallDetails>();
    Debug.WriteLine(details);
    //data processing
    return Content(HttpStatusCode.OK, "OK", new TextMediaTypeFormatter(), "text/plain");
}
我知道
IModelBinder
类,但在开始之前,我想知道是否有更简单的方法


我无法更改web请求格式,我的意思是,该格式始终是包含单键的POST-
JSON
y,其值为JSON字符串。

NewtonSoft的JSON.NET可以帮助您。如果json属性名与实际类名不匹配,则可以编写自定义转换器以提供帮助

编辑

如果您使用的是MVC6,您可以试试这个。将参数从类型
Tmp
更改为类型
CallDetails
,并用属性
[FromBody]
标记它,如下所示:

public IHttpActionResult SaveData([FromBody]CallDetails details)

例如,请查看中的“不同模型绑定”一节。但是,我仍然认为您需要手动反序列化,因为class
CallDetails
的属性名称与传入的JSON属性不完全匹配。

在使用JObject.Parse?之前,请不要忘记解码编码的url,它可能会起作用。并且对象的属性与json属性不匹配

您可以使用
JsonProperty
属性将json对象属性映射到c#对象属性:

public class CallDetails
{
    [JsonProperty("user_id")]
    public string UserId { get; set; }
    [JsonProperty("call_id")]
    public string CallId { get; set; }
    [JsonProperty("answered_time")]
    public string AnsweredTime { get; set; }
}
然后可以在没有临时类的情况下使用:

[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)

更新。因为数据是以x-www-form-urlencoded的形式发送的-我认为您处理数据的方式是最直接的,也不是那么糟糕。如果您想查看其他选项,这里有一些选项:

选项1-自定义模型活页夹。大概是这样的:

public class CustomModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var body = actionContext.Request.Content.ReadAsStringAsync().Result;
        body = body.Replace("json=", "");
        var json = HttpUtility.UrlDecode(body);

        bindingContext.Model = JsonConvert.DeserializeObject<CallDetails>(json);

        return true;
    }
}
用法:

[AllowAnonymous]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
缺点-您需要为其定义自定义路线:

config.Routes.MapHttpRoute(
            name: "save_data",
            routeTemplate: "save_data",
            defaults: new { controller = "YourController", action = "SaveData" },
            constraints: null,
            handler: new NormalizeHandler(config)
        );

当您传递的是json时,为什么不能将内容类型更改为application/json?这与问题无关,但我感到困扰的是,您使用
async
关键字只是为了执行
任务。Delay(1)
@matiascero我删除了负责处理数据并将其保存到数据库的代码,我做的每件事都是异步的,所以我没有改变方法声明,而是简单地添加了
Task.Delay(1)
如果让人困惑,我很抱歉。@Callumlington我已经编辑了我的问题。所谓格式,我的意思是,它总是带有单个键值对的POST请求。键是
JSON
,值包含JSONstring@Misiu那么你的问题是如何摆脱Tmp类?我已经在我的问题中添加了更多的细节。基本上,我想删除
Tmp
类的用法,而是将请求映射到Tmp,然后映射到CallDeails。我想从一开始就将请求映射到CallDeails。我在问题中添加了更多细节。基本上,我想删除
Tmp
类的用法,而是将请求映射到Tmp,然后映射到CallDeails。我想从一开始就将我的请求映射到CallDeails。请发布一些代码,这将非常有帮助。难道没有更通用的解决方案吗?我有四种方法有类似的问题。我希望有更多可重用的解决方案。MVC5还具有
FromBody
属性,但没有帮助。当从XML反序列化时,我们可以使用元素名的属性。JSON也可以做类似的事情吗?尝试确保发布表单包含
contentType:“application/JSON”
标题,并更改CallDetails属性以匹配JSON中的名称。您可能会被Tmp类卡住,因为您的页面发布的是封装在密钥/值对中的json。正如我在问题中所写的,我无法修改请求,因为它是由外部服务完成的,我所能设置的只是请求完成的URL。感谢您的回复,但请求格式如何?使用POST发送数据,键=
JSON
值=
{“用户Id”:“728409840”,“呼叫Id”:“1114330”,“应答时间”:“2015-04-16 15:37:47”}
。我想把这个字符串映射到
CallDetails
谢谢你的更新,因为你写的我的初始选项不错。我真正想要的是能够在方法内部使用
ModelState.IsValid
DelegatingHandler
看起来是最好的选择-我会马上得到我的模型,可能是
ModelState。IsValid
会起作用(不确定)。缺点是定制路线。也许可以在属性内指定处理程序?你知道这是否可行吗?@Misiu,我知道不能用属性routing指定处理程序。在哪些情况下验证有效?仅使用DelegatinHandler或ModelBinder或初始解决方案(JObject.ToObject)?对于所有的问题,我很抱歉,但我希望实现可能的最佳解决方案。只有使用DelegatinHandler,在另外两种情况下,您需要手动运行它(这还不错)。
config.Routes.MapHttpRoute(
            name: "save_data",
            routeTemplate: "save_data",
            defaults: new { controller = "YourController", action = "SaveData" },
            constraints: null,
            handler: new NormalizeHandler(config)
        );