WebAPI 自动绑定另一个参数

Automatically Binding Another Parameter in WebAPI

在 MVC 中,这类事情非常微不足道。假设我有一个 MVC 动作签名:

public ActionResult SomeAction(InjectedObject a, ConstructedObject b)

假设来自客户端的请求包含 ConstructedObject,我想在框架管道中自动构建 InjectedObject。 (在这个例子中,InjectedObject 有很多动作,甚至可能是所有动作。)我可以创建一个 InjectedObjectModelBinder : IModelBinder 并在应用程序启动时注册该活页夹的实例。

那个活页夹会简单地构造一个 InjectedObject 的实例,但我需要这样做。 (来自请求数据、来自其他来源、来源的组合等)这对于 cross-cutting 问题非常有效。


但是,有没有办法在 WebAPI 中执行此操作?有一个新的 IModelBinder,但似乎它的使用假设输入只有一个模型。到目前为止,我的谷歌搜索也指出了这个假设。在 WebAPI 中,是否有可能像这样将某些东西作为 cross-cutting 问题注入管道,同时仍然拥有来自 post body 的构造模型?

这里的具体用例是我想构建自定义 authorization-related object,在本例中来自请求 headers。我可以在动作中构建它,但每个动作都需要这样做。我可以在控制器上添加一个扩展方法,但这会损害单元测试。首选方法是简单地将其注入管道,这样我就可以在对控制器操作进行单元测试时注入模拟。

WebAPI 支持吗?或者也许还有另一种首选方法?

是的,您可以按照您建议的方式使用 IModelBinder,只需确保模型活页夹(仅)处理您的 InjectedObject。例如,下面的简单模型活页夹读取 header "your_key":

public class InjectedObjectModelBinder : IModelBinder
{
    public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(InjectedObject))
        {
            return false;
        }

        IEnumerable<string> values;
        string keyValue = "";

        if (actionContext.Request.Headers.TryGetValues("your_key", out values))
        {
            keyValue = values.First();
        }

        bindingContext.Model = new InjectedObject() { Id = 789, Name = keyValue };

        return true;
    }
}

然后您需要将活页夹连接到 InjectedObject class。有几种方法可以做到这一点: 首先,您可以在每个操作方法上执行此操作:

public void Post([ModelBinder(typeof(InjectedObjectModelBinder))]InjectedObject a, ConstructedObject b)

但是考虑到您想在很多地方使用它,这感觉不对。其次,您可以通过使用 ModelBinder 属性装饰 InjectedObject class 来做到这一点:

[ModelBinder(typeof(InjectedObjectModelBinder))]
public class InjectedObject
{....

最后,您可以将它添加到 WebApiConfigRegister 方法中的 HttpConfiguration class:

var provider = new SimpleModelBinderProvider(typeof(InjectedObject), new InjectedObjectModelBinder());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);

给出一些简单的 ConstructedObjectInjectedObject 实现与上面的活页夹:

[ModelBinder(typeof(InjectedObjectModelBinder))]
public class InjectedObject
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class ConstructedObject
{
    public int A { get; set; }
    public int B { get; set; }
    public string C { get; set; }
}

和这样的操作方法:

public void Post(InjectedObject a, ConstructedObject b)
{
    //a will be populated in our model binder.
}

最后是来自 Fiddler 的请求,如下所示:

POST http://localhost:64577/api/values HTTP/1.1
Host: localhost:64577
Accept: */*
Content-Type: application/json
Connection: keep-alive
Content-Length: 51
your_key: This came from the header

{"a":1,"b":2,"c":"This was 'normal' model binding"}

绑定的行为符合您的预期/希望:

可以找到一篇关于 Web 中模型绑定的好文章 API here