属性路由值到 Model/FromBody 参数
Attribute Routing Values into Model/FromBody Parameter
在Web API(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)
}
}
示例代码 post 到此:
$.ajax({
url: '/api/Products/5',
type: 'POST',
data: {
name: 'New Name' // NB: No ProductId in data
}
});
我希望在进入操作方法之前从路由参数填充模型中的 ProductId
字段(即它对我的验证器可用)。
我不确定我需要尝试覆盖模型绑定过程的哪一部分 - 我认为它是处理 [FromBody]
部分的位(这是本例中的模型参数) .
在操作本身中设置它是不可接受的(例如 model.ProductId = productId
),因为我需要在它到达操作之前设置它。
我没有发现任何问题。我能够从 Uri 中看到 productId。
我在 Postman 中尝试过 POST 到 Uri:http://localhost:42020/api/products/1 和 json 请求:
{
"name": "Testing Prodcu"
}
引用这篇文章Parameter Binding in ASP.NET Web API
模型绑定器
A more flexible option than a type converter is to create a custom
model binder. With a model binder, you have access to things like the
HTTP request, the action description, and the raw values from the
route data.
To create a model binder, implement the IModelBinder
interface
这是 UpdateProductModel
对象的模型绑定器,它将尝试提取路由值并使用找到的任何匹配属性来组合模型。
public class UpdateProductModelBinder : IModelBinder {
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext) {
if (!typeof(UpdateProductModel).IsAssignableFrom(bindingContext.ModelType)) {
return false;
}
//get the content of the body and convert it to model
object model = null;
if (actionContext.Request.Content != null)
model = actionContext.Request.Content.ReadAsAsync(bindingContext.ModelType).Result;
model = model ?? bindingContext.Model
?? Activator.CreateInstance(bindingContext.ModelType);
// check values provided in the route or query string
// for matching properties and set them on the model.
// NOTE: this will override any existing value that was already set.
foreach (var property in bindingContext.PropertyMetadata) {
var valueProvider = bindingContext.ValueProvider.GetValue(property.Key);
if (valueProvider != null) {
var value = valueProvider.ConvertTo(property.Value.ModelType);
var pInfo = bindingContext.ModelType.GetProperty(property.Key);
pInfo.SetValue(model, value, new object[] { });
}
}
bindingContext.Model = model;
return true;
}
}
设置模型绑定器
There are several ways to set a model binder. First, you can add a
[ModelBinder]
attribute to the parameter.
public HttpResponseMessage UpdateProduct(int productId, [ModelBinder(typeof(UpdateProductModelBinder))] UpdateProductModel model)
You can also add a [ModelBinder]
attribute to the type. Web API will
use the specified model binder for all parameters of that type.
[ModelBinder(typeof(UpdateProductModelBinder))]
public class UpdateProductModel {
public int ProductId { get; set; }
public string Name { get; set; }
}
给出以下使用上述模型和 ModelBinder 的简化示例
public class ProductsController : ApiController {
[HttpPost, Route("api/Products/{productId:int}")]
public IHttpActionResult UpdateProduct(int productId, UpdateProductModel model) {
if (model == null) return NotFound();
if (model.ProductId != productId) return NotFound();
return Ok();
}
}
使用了以下集成测试来确认所需的功能
[TestClass]
public class AttributeRoutingValuesTests {
[TestMethod]
public async Task Attribute_Routing_Values_In_Url_Should_Bind_Parameter_FromBody() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
using (var server = new HttpTestServer(config)) {
var client = server.CreateClient();
string url = "http://localhost/api/Products/5";
var data = new UpdateProductModel {
Name = "New Name" // NB: No ProductId in data
};
using (var response = await client.PostAsJsonAsync(url, data)) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
}
如果您不想创建自定义参数绑定器,您可能需要考虑不要将 FromBody 与 FromUrl 混合使用。而是完全使用 FromUrl...
[HttpPost, Route("/api/Products/{productId:int}/{name:string}")]
public void UpdateProduct([FromUri]UpdateProductModel model)
{
}
或者完全使用 FromBody...
[HttpPost, Route("/api/Products")]
public void UpdateProduct([FromBody]UpdateProductModel model)
{
}
并相应地更新 javascript
由于这是更新,因此应该是 HttpPut。 PUT 动词是幂等的,因此对 API(同一个 json 请求)的任何后续请求都应该具有相同的 response/effect(没有在服务器端创建资源)。应在调用客户端中设置模型 productId。
public class ProductsController : ApiController
{
[HttpPut, Route("/api/Products/{productId:int}")]
public void UpdateProduct(UpdateProductModel model)
{
if (ModelState.IsValid)
{
//
}
else
{
BadRequest();
}
}
}
在Web API(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)
}
}
示例代码 post 到此:
$.ajax({
url: '/api/Products/5',
type: 'POST',
data: {
name: 'New Name' // NB: No ProductId in data
}
});
我希望在进入操作方法之前从路由参数填充模型中的 ProductId
字段(即它对我的验证器可用)。
我不确定我需要尝试覆盖模型绑定过程的哪一部分 - 我认为它是处理 [FromBody]
部分的位(这是本例中的模型参数) .
在操作本身中设置它是不可接受的(例如 model.ProductId = productId
),因为我需要在它到达操作之前设置它。
我没有发现任何问题。我能够从 Uri 中看到 productId。
我在 Postman 中尝试过 POST 到 Uri:http://localhost:42020/api/products/1 和 json 请求:
{
"name": "Testing Prodcu"
}
引用这篇文章Parameter Binding in ASP.NET Web API
模型绑定器
A more flexible option than a type converter is to create a custom model binder. With a model binder, you have access to things like the HTTP request, the action description, and the raw values from the route data.
To create a model binder, implement the
IModelBinder
interface
这是 UpdateProductModel
对象的模型绑定器,它将尝试提取路由值并使用找到的任何匹配属性来组合模型。
public class UpdateProductModelBinder : IModelBinder {
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext) {
if (!typeof(UpdateProductModel).IsAssignableFrom(bindingContext.ModelType)) {
return false;
}
//get the content of the body and convert it to model
object model = null;
if (actionContext.Request.Content != null)
model = actionContext.Request.Content.ReadAsAsync(bindingContext.ModelType).Result;
model = model ?? bindingContext.Model
?? Activator.CreateInstance(bindingContext.ModelType);
// check values provided in the route or query string
// for matching properties and set them on the model.
// NOTE: this will override any existing value that was already set.
foreach (var property in bindingContext.PropertyMetadata) {
var valueProvider = bindingContext.ValueProvider.GetValue(property.Key);
if (valueProvider != null) {
var value = valueProvider.ConvertTo(property.Value.ModelType);
var pInfo = bindingContext.ModelType.GetProperty(property.Key);
pInfo.SetValue(model, value, new object[] { });
}
}
bindingContext.Model = model;
return true;
}
}
设置模型绑定器
There are several ways to set a model binder. First, you can add a
[ModelBinder]
attribute to the parameter.
public HttpResponseMessage UpdateProduct(int productId, [ModelBinder(typeof(UpdateProductModelBinder))] UpdateProductModel model)
You can also add a
[ModelBinder]
attribute to the type. Web API will use the specified model binder for all parameters of that type.
[ModelBinder(typeof(UpdateProductModelBinder))]
public class UpdateProductModel {
public int ProductId { get; set; }
public string Name { get; set; }
}
给出以下使用上述模型和 ModelBinder 的简化示例
public class ProductsController : ApiController {
[HttpPost, Route("api/Products/{productId:int}")]
public IHttpActionResult UpdateProduct(int productId, UpdateProductModel model) {
if (model == null) return NotFound();
if (model.ProductId != productId) return NotFound();
return Ok();
}
}
使用了以下集成测试来确认所需的功能
[TestClass]
public class AttributeRoutingValuesTests {
[TestMethod]
public async Task Attribute_Routing_Values_In_Url_Should_Bind_Parameter_FromBody() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
using (var server = new HttpTestServer(config)) {
var client = server.CreateClient();
string url = "http://localhost/api/Products/5";
var data = new UpdateProductModel {
Name = "New Name" // NB: No ProductId in data
};
using (var response = await client.PostAsJsonAsync(url, data)) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
}
如果您不想创建自定义参数绑定器,您可能需要考虑不要将 FromBody 与 FromUrl 混合使用。而是完全使用 FromUrl...
[HttpPost, Route("/api/Products/{productId:int}/{name:string}")]
public void UpdateProduct([FromUri]UpdateProductModel model)
{
}
或者完全使用 FromBody...
[HttpPost, Route("/api/Products")]
public void UpdateProduct([FromBody]UpdateProductModel model)
{
}
并相应地更新 javascript
由于这是更新,因此应该是 HttpPut。 PUT 动词是幂等的,因此对 API(同一个 json 请求)的任何后续请求都应该具有相同的 response/effect(没有在服务器端创建资源)。应在调用客户端中设置模型 productId。
public class ProductsController : ApiController
{
[HttpPut, Route("/api/Products/{productId:int}")]
public void UpdateProduct(UpdateProductModel model)
{
if (ModelState.IsValid)
{
//
}
else
{
BadRequest();
}
}
}