簡體   English   中英

屬性路由值到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)
    }
}

要發布到此的示例代碼:

$.ajax({
    url: '/api/Products/5',
    type: 'POST',
    data: {
        name: 'New Name'   // NB: No ProductId in data
    }
});

我希望在輸入操作方法之前,從路徑參數中填充模型中的ProductId字段(即,我的驗證器可以使用它)。

我不確定模型綁定過程的哪一部分需要在這里嘗試和覆蓋 - 我認為這是處理[FromBody]部分(這個例子中是模型參數)的部分。

在動作本身中設置它是不可接受的(例如model.ProductId = productId ),因為我需要在它到達動作之前設置它。

在ASP.NET Web API中引用本文參數綁定

模型粘合劑

比類型轉換器更靈活的選項是創建自定義模型綁定器。 使用模型綁定器,您可以訪問HTTP請求,操作描述和路徑數據中的原始值。

要創建模型綁定器,請實現IModelBinder接口

這是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;
    }
}

設置模型裝訂器

有幾種方法可以設置模型綁定器。 首先,您可以為參數添加[ModelBinder]屬性。

public HttpResponseMessage UpdateProduct(int productId, [ModelBinder(typeof(UpdateProductModelBinder))] UpdateProductModel model)

您還可以為該類型添加[ModelBinder]屬性。 Web API將使用指定的模型綁定器來處理該類型的所有參數。

[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請求)的任何后續請求應該具有相同的響應/效果(在服務器端沒有創建資源)。 應在調用客戶端中設置modelId模型。

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

我沒有看到任何問題。 我能夠從Uri看到productId。

我在Postman中嘗試使用POST給Uri: http:// localhost:42020 / api / products / 1帶有json請求:

{
  "name": "Testing Prodcu"
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM