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
{....
最后,您可以将它添加到 WebApiConfig
的 Register
方法中的 HttpConfiguration
class:
var provider = new SimpleModelBinderProvider(typeof(InjectedObject), new InjectedObjectModelBinder());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
给出一些简单的 ConstructedObject
和 InjectedObject
实现与上面的活页夹:
[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。
在 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
{....
最后,您可以将它添加到 WebApiConfig
的 Register
方法中的 HttpConfiguration
class:
var provider = new SimpleModelBinderProvider(typeof(InjectedObject), new InjectedObjectModelBinder());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
给出一些简单的 ConstructedObject
和 InjectedObject
实现与上面的活页夹:
[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。