C# 属性将值路由到模型/FromBody参数中

C# 属性将值路由到模型/FromBody参数中,c#,asp.net-web-api,asp.net-web-api2,model-binding,C#,Asp.net Web Api,Asp.net Web Api2,Model Binding,在WebAPI(2)中使用属性路由时,我希望能够将路由参数从URL自动获取到模型参数中。这样做的原因是,我的验证在到达操作之前在过滤器中执行,没有它,附加信息就不容易获得 考虑以下简化示例: public class UpdateProductModel { public int ProductId { get; set; } public string Name { get; set; } } public class ProductsController : ApiCont

在WebAPI(2)中使用属性路由时,我希望能够将路由参数从URL自动获取到模型参数中。这样做的原因是,我的验证在到达操作之前在过滤器中执行,没有它,附加信息就不容易获得

考虑以下简化示例:

public class UpdateProductModel
{
    public int ProductId { get; set; }
    public string Name { get; set; }
}

public class ProductsController : ApiController 
{
    [HttpPost, Route("/api/Products/{productId:int}")]
    public void UpdateProduct(int productId, UpdateProductModel model) 
    {
         // model.ProductId should == productId, but is default (0)
    }
}
要发布到此的示例代码:

$.ajax({
url:“/api/Products/5”,
键入:“POST”,
数据:{
名称:“新名称”//NB:数据中没有ProductId
}
});
我希望模型中的
ProductId
字段在输入操作方法之前从路由参数填充(即,它将可供我的验证器使用)

我不确定我需要在这里尝试和覆盖模型绑定过程的哪一部分-我认为是处理
[FromBody]
部分的位(在本例中是模型参数)


在操作本身(例如,
model.ProductId=ProductId
)中设置此选项是不可接受的,因为我需要在它到达操作之前设置此选项。

我没有看到任何问题。我能够从Uri中看到productId

我在Postman中使用POST-to-Uri:和json请求尝试了它:

{
  "name": "Testing Prodcu"
}
参考本文

模型活页夹

比类型转换器更灵活的选项是创建自定义 模型活页夹。使用模型活页夹,您可以访问 HTTP请求、操作描述和来自 路线数据

要创建模型绑定器,请实现
IModelBinder
界面

这里是一个用于
UpdateProductModel
对象的模型绑定器,该对象将尝试提取路由值,并使用找到的任何匹配属性对模型进行水合物化

public类UpdateProductModelBinder:IModelBinder{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext,ModelBindingContext bindingContext){
如果(!typeof(UpdateProductModel).IsAssignableFrom(bindingContext.ModelType)){
返回false;
}
//获取主体的内容并将其转换为模型
对象模型=null;
if(actionContext.Request.Content!=null)
model=actionContext.Request.Content.ReadAsAsync(bindingContext.ModelType).Result;
模型=模型??bindingContext.model
??Activator.CreateInstance(bindingContext.ModelType);
//检查路由或查询字符串中提供的值
//用于匹配属性,并在模型上设置它们。
//注意:这将覆盖已设置的任何现有值。
foreach(bindingContext.PropertyMetadata中的var属性){
var valueProvider=bindingContext.valueProvider.GetValue(property.Key);
if(valueProvider!=null){
var value=valueProvider.ConvertTo(property.value.ModelType);
var pInfo=bindingContext.ModelType.GetProperty(property.Key);
SetValue(模型、值、新对象[]{});
}
}
bindingContext.Model=Model;
返回true;
}
}
设置模型活页夹

有几种方法可以设置模型绑定器。首先,您可以添加一个
[ModelBinder]
参数的属性

public httpresponsemessageupdateproduct(int-productId,[ModelBinder(typeof(UpdateProductModelBinder))]UpdateProductModel模型)
您还可以向类型添加
[ModelBinder]
属性。Web API将 对该类型的所有参数使用指定的模型绑定器

[ModelBinder(typeof(UpdateProductModelBinder))]
公共类UpdateProductModel{
public int ProductId{get;set;}
公共字符串名称{get;set;}
}
给出了以下使用上述模型和ModelBinder的简化示例

公共类产品控制器:ApiController{
[HttpPost,路由(“api/Products/{productId:int}”)]
公共IHttpActionResult UpdateProduct(int-productId,UpdateProductModel){
if(model==null)返回NotFound();
if(model.ProductId!=ProductId)返回NotFound();
返回Ok();
}
}
以下集成测试用于确认所需的功能

[TestClass]
公共类AttributeRoutingValuesTests{
[测试方法]
公共异步任务属性\u路由\u Url中的值\u应\u绑定\u参数\u FromBody(){
var config=新的HttpConfiguration();
config.maphttpAttribute路由();
使用(var server=newhttptestserver(config)){
var client=server.CreateClient();
字符串url=”http://localhost/api/Products/5";
var数据=新的UpdateProductModel{
Name=“New Name”//NB:数据中没有ProductId
};
使用(var response=wait client.PostAsJsonAsync(url,数据)){
AreEqual(HttpStatusCode.OK,response.StatusCode);
}
}
}
}

如果不想创建自定义参数绑定器,可能需要考虑不要将FromBody与FromUrl混合。而是完全使用FromUrl

[HttpPost, Route("/api/Products/{productId:int}/{name:string}")]
public void UpdateProduct([FromUri]UpdateProductModel model) 
{

}
[HttpPost, Route("/api/Products")]
public void UpdateProduct([FromBody]UpdateProductModel model) 
{

}
或者完全使用FromBody

[HttpPost, Route("/api/Products/{productId:int}/{name:string}")]
public void UpdateProduct([FromUri]UpdateProductModel model) 
{

}
[HttpPost, Route("/api/Products")]
public void UpdateProduct([FromBody]UpdateProductModel model) 
{

}

并相应地更新javascript

,因为这是一个更新,所以应该是HttpPut。PUT动词是幂等的,因此对API的任何后续请求(相同json请求)都应该具有相同的响应/效果(服务器端不创建任何资源)。应在调用客户端中设置模型productId

public class ProductsController : ApiController 
{
    [HttpPut, Route("/api/Products/{productId:int}")]
    public void UpdateProduct(UpdateProductModel model) 
    {
         if (ModelState.IsValid)
         {
            //
         }
         else
         {
            BadRequest();
         }
    }
}

您没有注意到这一点-我的模型中有一个ProductId字段,我希望从路由值填充该字段-验证器在操作方法之前运行,并且只能访问该模型。您可以通过使用自定义
IModelBinder
检查路由数据以匹配模型上的属性来实现这一点。我希望