【问题标题】:.NetCore MVC deserialization.Net Core MVC 反序列化
【发布时间】:2017-01-25 02:50:48
【问题描述】:

在 .netcore 应用程序中,我想提供以下(简化):

// Create a new record, assume it returns an ID=1
https://site/MyController/Save?FirstName=John&LastName=Doe&Status=Active

// Update the record without full state
PUT https://site/MyController/1
{
  'DOB': '1/1/1970',
  'Status': null
}

我想将第二次通话翻译为:

UPDATE MyModel SET DOB = '1/1/1970' AND Status=NULL WHERE Id = 1

我当然可以在MyController 中编写我的Create 方法来解析提交值的请求(查询字符串/表单/正文),并相应地创建我的SQL。

但是,我更愿意遵循 MVC 约定并利用 MVC 提供的开箱即用的绑定:

public async Task<MyModel> Save(string id, [FromBody]MyModel instance)
{
  await _MyRepository.UpdateAsync(id, message);
  return message;
}

这里的问题是实例看起来像这样:

{
  'FirstName': null,
  'LastName': null,
  'DOB': '1/1/1970',
  'Status': null
}

此时我无法确定 Db 中哪些字段应为空,哪些字段应保留。

我已经实现了一个包装类:

  • 在反序列化时,设置任何“脏”属性,并且
  • 在序列化时,只写入脏属性

这会稍微改变我的方法签名,但不会给开发人员带来负担:

public async Task<MyModel> Save(string id, [FromBody]MyWrapper<MyModel> wrapper
{
  await _MyRepository.UpdateAsync(id, wrapper.Instance, wrapper.DirtyProperties);
  return wrapper.Instance;
}

我的两个问题是:

  1. 我是在重新发明一种既定模式吗?
  2. 我可以拦截 MVC 反序列化(以优雅的方式)吗?

【问题讨论】:

  • 不要使用持久性模型作为控制器操作的返回值或输入值。它为麻烦而尖叫

标签: c# asp.net-core json.net asp.net-core-mvc


【解决方案1】:

您可以查看自定义模型绑定。

  • 创建自己的模型绑定器:实现 IModelBinder 接口的类:

    /// <summary>
    /// Defines an interface for model binders.
    /// </summary>
    public interface IModelBinder
    {
       /// <summary>
       /// Attempts to bind a model.
       /// </summary>
       /// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
       /// <returns>
       /// <para>
       /// A <see cref="Task"/> which will complete when the model binding process completes.
       /// </para>
       /// <para>
       /// If model binding was successful, the <see cref="ModelBindingContext.Result"/> should have
       /// <see cref="ModelBindingResult.IsModelSet"/> set to <c>true</c>.
       /// </para>
       /// <para>
       /// A model binder that completes successfully should set <see cref="ModelBindingContext.Result"/> to
       /// a value returned from <see cref="ModelBindingResult.Success"/>. 
       /// </para>
       /// </returns>
       Task BindModelAsync(ModelBindingContext bindingContext);
     }
    
  • 注册你的活页夹:

    services.AddMvc().Services.Configure<MvcOptions>(options => {
        options.ModelBinders.Insert(0, new YourCustomModelBinder());
    });
    

MVCgithub repo 和“Custom Model Binding”文章可能会有所帮助:

【讨论】:

  • 这确实达到了我的要求。然而,这也让我很想仅仅创建中间件,因为我(到目前为止)很少利用 MVC 的增值。当然,这与我的问题相矛盾!
【解决方案2】:

PUT 动词需要整个实体,但您可以发送带有 delta 的 HTTP PATCH。关于具体如何完成的官方文档很少,但我确实找到了link,其中列出了如何使用 JSONPatchDocument 来完成此操作,该 JSONPatchDocument 基本上可以完成您的拦截类的工作。

【讨论】:

  • 服务器端的优雅!对于我的用例,我试图让客户/消费者的生活变得简单,让他们采用相当冗长的 PATCH 文档将是一个挑战。 link
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-13
  • 1970-01-01
  • 2021-12-03
  • 2019-08-06
  • 1970-01-01
  • 2018-06-13
  • 2021-03-15
相关资源
最近更新 更多