如何传递 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 控制器数据,以便发布模型在绑定/反序列化或验证时可以访问的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 jquery ajax 将 web api 控制器中的 web 表单值作为模型类传递
如何使用数据注释为模型属性 WebApi .NET Core 添加布尔验证