asp.net - 如何防止 ASP.NET Web API OData 服务中的发布不足?

标签 asp.net .net asp.net-web-api odata asp.net-web-api-odata

我创建了一个非常简单的 OData v4 Controller 。 Controller 基本上包含以下 Pet 实体的 Entity Framework 支持的 CRUD 方法:

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

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

    public int Age { get; set; }
}

这里重要的是 Pet.Age 是不可为 null 的必需属性。

这里是 Controller 本身(只显示了 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 Controller 配置:

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返回错误请求 状态。这个问题称为发布不足。

使用常规的 WebApi Controller 可以很容易地解决这个问题(解决方案描述为 here )。您只需创建一个 PetViewModel 并让您的 Controller 接受一个 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...
}

然后在您的 Controller 中,您只需将 PetViewModel 转换为 Pet 实体,并像往常一样将其保存到数据库中。

不幸的是,这种方法不适用于 OData Controller :如果我将 Post 方法更改为接受 PetViewModel 而不是 Pet,我会收到以下错误:

System.Net.Http.UnsupportedMediaTypeException: No MediaTypeFormatter is available to read an object of type 'PetViewModel' from content with media type 'application/json'.

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

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

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

那么,在使用 OData controller 时,有什么方法可以防止 under-posting 吗?

最佳答案

经过一些调查,我已经解决了这个问题。不确定它是否是解决 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 验证工作正常。

关于asp.net - 如何防止 ASP.NET Web API OData 服务中的发布不足?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34903663/

相关文章:

c# - 项目与 BindItem : Data Binding in ASP. NET 4.5

c# - 如何在 .net 核心中散列密码,使其在 .net 框架中相等

javascript - 如何在 Angularjs 中的 ng-repeat 中显示单行

wcf - 我可以结合使用 WCF 和 WebApi 吗?

asp.net - 如何在 .net web api Controller 中读取多部分表单数据

javascript - 使用 JavaScript 访问 Azure 中运行的 Web 服务

c# - 处理关闭/断开网络套接字连接(CloseAsync 与 CloseOutputAsync)

c# - C#中的唯一键值集合

asp.net - Asp.Net Web API 的默认 header 值 "Accept"

c# - 为什么这条本地化线会这样?