簡體   English   中英

如何防止在 ASP.NET Web API OData 服務中發布不足?

[英]How to prevent under-posting in ASP.NET Web API OData service?

我創建了一個非常簡單的 OData v4 控制器。 控制器基本上包含以下Pet實體的實體框架支持的 CRUD 方法:

public class Pet
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public int Age { get; set; }
}

這里重要的一點是Pet.Age是不可為空的必需屬性。

這是控制器本身(僅顯示Post方法):

public class PetController : ODataController
{
    private DatabaseContext db = new DatabaseContext();

    // POST: odata/Pet
    public IHttpActionResult Post(Pet pet)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Pet.Add(pet);
        db.SaveChanges();

        return Created(pet);
    }

    // Other controller methods go here...
}

這是我的WebApiConfig控制器配置:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Pet>("Pet");
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());

現在,如果我想在我的數據庫中創建一個新的Pet ,我會發出一個這樣的POST請求:

POST http://localhost:8080/odata/Pet
Content-type: application/json

{ Name: "Cat", Age: 5 }

但是,我可以簡單地省略 JSON 請求負載中的Age屬性,因此 JSON 反序列化器將使用默認值0 ,而我希望返回400 Bad Request狀態。 此問題稱為過帳不足。

使用常規 WebApi 控制器時可以輕松解決此問題( 此處描述了解決方案)。 您只需創建一個PetViewModel並使您的控制器接受一個PetViewModel而不是一個實際的Pet實體:

public class PetViewModel
{
    // Make the property nullable and set the Required attribute
    // to distinguish between "zero" and "not set"
    [Required]
    public int? Age { get; set; }

    // Other properties go here...
}

然后在您的控制器中,您只需將PetViewModel轉換為Pet實體並照常將其保存到數據庫中。

不幸的是,這種方法不適用於 OData 控制器:如果我將Post方法更改為接受PetViewModel而不是Pet ,我會收到以下錯誤:

System.Net.Http.UnsupportedMediaTypeException:沒有 MediaTypeFormatter 可用於從媒體類型為“application/json”的內容中讀取“PetViewModel”類型的對象。

在 System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancelationToken)

在 System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancelationToken)

在 System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancelationToken)

那么,在使用 OData 控制器時,有什么方法可以防止發布不足?

經過一番調查,我解決了這個問題。 不確定它是否是解決 OData 中欠載問題的“官方”或首選方法,但至少對我來說效果很好。 所以,由於缺乏官方信息,這是我的食譜:

首先,為您的 OData 實體創建相應的驗證ViewModel

public class PetViewModel
{
    public int Id { get; set; }

    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    // Make the property nullable and set the Required attribute
    // to distinguish between "zero" and "not set"
    [Required]
    public new int? Age { get; set; }
}

然后,添加您自己的ODataUnderpostingValidationAttribute 我的實現是這樣的:

public class ODataUnderpostingValidationAttribute: ActionFilterAttribute
{
    public ODataUnderpostingValidationAttribute(Type viewModelType)
    {
        ViewModelType = viewModelType;
    }

    public Type ViewModelType { get; set; }

    public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        // Rewind requestStream so it can be read again.
        var requestStream = await actionContext.Request.Content.ReadAsStreamAsync();
        if (requestStream.CanSeek)
        {
            requestStream.Position = 0;
        }

        // Read the actual JSON payload.
        var json = await actionContext.Request.Content.ReadAsStringAsync();

        // Deserialize JSON to corresponding validation ViewModel.
        var viewModel = JsonConvert.DeserializeObject(json, ViewModelType);
        var context = new ValidationContext(viewModel);
        var results = new List<ValidationResult>();
        var isValid = Validator.TryValidateObject(viewModel, context, results);

        if (!isValid)
        {
            // Throw HttpResponseException instead of setting actionContext.Response, so the exception will be logged by the ExceptionLogger.
            var responseMessage = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage)));
            throw new HttpResponseException(responseMessage);
        }

        await base.OnActionExecutingAsync(actionContext, cancellationToken);
    }
}

之后,將此自定義過濾器應用於您的ODataController

[ODataUnderpostingValidation(typeof(PetViewModel))]
public class PetController : ODataController
{ /* Implementation here */ }

瞧! 現在您已准備就緒。 Underposting 驗證工作正常。

在我看來,您有幾個選擇:

首先在您的控制器中,您可以檢查整數值,如果它低於某個值,則返回 404。

if (Age <= 0)
   return NotFound();

這可能是勞動密集型的,如果您為每個控制器方法都這樣做,它並不是很 DRY。

在您的 Pet 類中的第二個,您可以使用 DataAnnotations 屬性范圍,例如

[Range(0, 80, ErrorMessage = "Value for {0} must be between {1} and {2}")]
public int Age { get; set; }

其中 Age 最大為 80。 https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.rangeattribute(v=vs.110).aspx

最后,我認為您的一個更永久的解決方案是創建自己的驗證:

public class AgeValidation : ValidationAttribute {
public override bool IsValid(object value) {
    if (Object.Equals(value, null)) {
        return false;
    }
    int getage;
    if (int.TryParse(value.ToString(), out getage)) {

        if (getage == 0)
            return false;

        if (getage > 0)
            return true;
    }
    return false;
}

}

然后在您的 Pet 類中添加:

[AgeValidation(ErrorMessage = "Age is wack")]
public int Age { get; set; }

借用了如何在 asp.net mvc 2 中進行整數模型驗證

暫無
暫無

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

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