如何传递 WebAPI 控制器数据,以便发布模型在绑定/反序列化或验证时可以访问

Posted

技术标签:

【中文标题】如何传递 WebAPI 控制器数据,以便发布模型在绑定/反序列化或验证时可以访问【英文标题】:How to pass WebAPI Controller data such that Posted Model has access at Binding/Deserialization or Validation Time 【发布时间】:2020-01-28 02:51:52 【问题描述】:

考虑这个 WebAPI 控制器,其中一些元数据在实例化时被检索。

public class MyController : ApiController

    private readonly MetaData _metaData;

    public MyController(IService service) // IService is injected via Unity Container DI
    
        _metaData = service.GetMetaData();
    

    [ValidateModel]
    public IHttpActionResult Post([FromBody] PostModel model)
    
        // do something
        return new ResponseMessageResult(null);
    

假设 Post 动作的 PostModel 类定义如下:

public class PostModel : IValidatableObject

    ComplexObject Data  get; set; 

    [OnDeserialized]
    internal void OnDeserialized(StreamingContext context)
    
        // do something, using _metaData
    

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    
        // validate, using _metaData
    

我希望我的 PostModel 能够在调用 OnDeserialized() 时访问 MyController _metaData 对象,但至少在执行 Validate() 时我会满足于此。

我考虑过:

ActionFilter - 不可能,因为模型绑定/验证发生在之前 ActionFilter 执行 自定义 ValueProvider/ModelBinder - 我想也许这些一起可以让我定义 OnDeserialized() 中使用的 StreamingContext 并将其 Context 属性设置为 _metaData。但即使这是可能的,我也找不到正确的方法。 MessageHandler- 我认为每个路由消息处理程序并不理想,但我可以使用依赖注入来检索 _metaData,然后...将此对象数据附加到发布的数据中?这感觉很老套,我对这种解决方案不是很感兴趣。 依赖注入 - 我可以在 PostModel 自己的实例化期间为服务提供一个引用,但是控制器和模型存在于单独的项目中,模型项目目前没有 Unity 容器依赖项。相反,它依赖于 Controller 项目和其他引用项目来使用它们自己的 DI 来传递任何所需的数据。

也许有某种我不知道的动作级属性会在这里使我受益,或者上面的一个可能是在正确的轨道上?我必须相信有一种方法可以在反序列化期间传递/访问动态服务器端数据,或者至少通过验证我只是不知道或不考虑正确方法的模型。

两个附加说明:

我正在使用 Asp.Net WebAPI,但无权访问 Core WebAPI 属性、过滤器等。 Post 操作在 ProjectA 中,但 PostModel 在 ProjectB 中。 ProjectA 引用了 projectB,但反之则不然。 ProjectB 目前不依赖 Unity Container DI。

提前感谢您的帮助!

【问题讨论】:

为什么不将 IService 注入 PostModel,或者继承自 PostModel 的东西? 这是个好主意,但是,我的解决方案的结构方式我有几个引用 ProjectB(PostModel 所在的位置)的项目,所有这些都将必要的信息传递给 ProjectB 本身。出于这个原因,我已经能够避免 ProjectB 需要依赖于 Unity Container DI。我的偏好是让 ProjectB 与依赖注入容器无关,并依赖其他项目为其提供所需的信息。我在附加说明下松散地提到了这一点,但没有明确说明,因此将编辑我的问题。不过感谢您的评论! 【参考方案1】:

您可以使用 IModelBinder 来反序列化和验证您的 PostModel。这样做,您将可以在反序列化期间访问您的 Controller 实例。这段代码相当粗糙,但你应该明白了。

IModelBinder 实现

public class PostDataModelBinder : IModelBinder

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    
        if (bindingContext.ModelType != typeof(PostDataModel))
        
            return false;
        

        try
        
          // Get the content of the request, deserialize into your model object
          Task<string> bodyTask = actionContext.Request.Content.ReadAsStringAsync();
          PostDataModel vm = JsonConvert.DeserializeObject<PostDataModel>(bodyTask.Result);

          // Get an instance of your IService and get metadata
          var service = (IService)actionContext.ControllerContext.Configuration.DependencyResolver.GetService(typeof(IService));
          var metadata = service.GetMetadata();

          // this is how you would get the Metadata directly from your controller as long as it was accessible
          //var metadata = ((IMetadata)actionContext.ControllerContext.Controller).Metadata;

          // do stuff with metadata to validate your object however you wish
          vm.PopulateValidationErrors();

          // return true if you were able to deserialize this object, false if you couldn't 
          return vm != null;
        
        catch 
          // do logging
          return false;
        
    

通过将 IModelBinder 添加到 WebApiConfig 的 Register 方法来全局使用它 config.BindParameter(typeof(PostDataModel), new PostDataModelBinder());

【讨论】:

建议。使用操作上下文获取依赖解析器并解析服务以访问元数据,而不是尝试从控制器中提取元数据。除此之外,这是一个很好的方法。 我试图不要过多地更改 OP 的代码,但我绝对更喜欢这样。我修改了代码以显示如何从服务而不是控制器获取元数据。 您好,谢谢您的建议!这看起来很有希望,我会尽快实现它并写回我的结果。然而,一个问题是——假设我的 PostModel 有 3 个 SubModelA/B/C 属性,每个属性通常都会被其各自的 SubModel 类实现反序列化和验证。这种方法是否仍然允许这些模型反序列化和验证自己,或者我现在必须在此处手动验证整个对象树? ...我想由于绑定发生在验证之前,在 BindModel() 返回后,正常执行将像以前一样验证? IModelBinder 接口是 ASP.NET MVC 进程的挂钩,因此您告诉 ASP.NET 使用它来绑定您的模型,而不是使用其默认进程。 BindModel() 返回一个布尔值,表示绑定是否成功。也就是说,当您使用 JsonConvert.DeserializeObject 反序列化主体时,它也应该反序列化 SubModelA/B/C 属性。至于验证,我的做法是让 PostModel 的 validate 方法调用任何具有验证方法的属性,所以我想我是说你会在这里验证整个树。 这是朝着正确方向迈出的一步,但事实证明,在自定义模型绑定器中验证复杂的对象图非常乏味,所以我决定不走这条路。我还尝试了自定义格式化程序,但使用该方法不包括访问 ActionContext。我最终会写出一个答案来解释我尝试了什么以及什么对我不起作用,但我仍在寻找完美的解决方案。也就是说,你回答了我的问题,提供了可能对其他人有所帮助的合理建议,所以我很高兴奖励你。谢谢!

以上是关于如何传递 WebAPI 控制器数据,以便发布模型在绑定/反序列化或验证时可以访问的主要内容,如果未能解决你的问题,请参考以下文章

呼叫不是从web api中的控制器传递到模型

如何使用 jquery ajax 将 web api 控制器中的 web 表单值作为模型类传递

如何使用数据注释为模型属性 WebApi .NET Core 添加布尔验证

如何在WEB API中将多个模型作为参数传递

如何将json POST数据作为对象传递给Web API方法?

WebApi系列详解WebApi如何传递参数