API中的POST上的模型绑定问题

时间:2018-08-27 22:52:02

标签: c# asp.net-core

我无法理解Asp.Net core 2中的模型绑定过程。我有一个非常简单的具有模型的API。它具有一些基本的验证。每当用户发布不正确的模型时,我都会尝试返回422无法处理的实体以及来自modelstate的错误消息。

我要理解的两个问题如下:

  • 如果我发布不带ID的请求,则会绕过必需的属性创建默认ID 0。我假设这是C#功能,用于为字段提供默认值。有办法避免这种情况吗?

  • 另一个问题是,如果我在发布操作中放置一个断点并发送错误的请求,它甚至不会进入该方法。它使用验证属性发送回一个400错误的请求。这是如何运作的?请求尝试建模时是否会立即绑定到无效属性(即名称长度> 10)?我需要做的是发送回具有相同错误消息而不是400的422无法处理的实体。

如果基于验证属性的模型状态验证失败,ASP.NET甚至不会进入该方法吗?解决此问题以返回422错误代码的更好方法是什么?

下面是我各种类的代码(创建项目时使用了API模板):

Startup.cs -我在这里添加的唯一内容是内存中上下文的单例实例

public void ConfigureServices(IServiceCollection services)
{
    //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddMvc();
    services.AddSingleton<IItemRepository, ItemRepository>();
}

IItemRepository.cs -我的DI界面

public interface IItemRepository
{
    List<ItemModel> Items { get; set; }
    void AddValue(ItemModel itemModel);
}

ItemRepository.cs -具体实施方式

public class ItemRepository : IItemRepository
{
    public List<ItemModel> Items { get; set; } = new List<ItemModel>();

    public ItemRepository()
    {
        Items.AddRange(
            new List<ItemModel> {
                new ItemModel {Id = 1, Name = "Test1" },
                new ItemModel {Id = 2, Name = "Test2" }
             }
        );
    }

    public void AddValue(ItemModel itemModel)
    {
        Items.Add(itemModel);
    }
}

ItemModel.cs -用于用户输入的我的模型类

public class ItemModel
{
    [Required]
    public int Id { get; set; }
    [MaxLength(10)]
    public string Name { get; set; }
}

ValuesController.cs

[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
    private IItemRepository _context;

    public ValuesController(IItemRepository context)
    {
        _context = context;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return Ok(_context.Items);
    }

    // GET api/values/5
    [HttpGet("{id}", Name = "GetSingle")]
    public ActionResult<string> Get(int id)
    {
        return Ok(_context.Items.Where(x => x.Id == id));
    }

    // Problem here - placing a breakpoint in below method does not do anytthing as it will return a 400 bad request instead of 422
    [HttpPost]
    public ActionResult Post([FromBody] ItemModel itemModel)
    {
        if (!ModelState.IsValid)
        {
            return new UnprocessableEntityObjectResult(ModelState);
        }

        ItemModel addNew = new ItemModel { Id = itemModel.Id, Name = itemModel.Name };
        _context.AddValue(addNew);
        return Ok(addNew);
    }
}

3 个答案:

答案 0 :(得分:1)

  

如果我发布的请求中没有ID,则默认ID为0   避开必需的属性。我假设这是C#   向字段提供默认值的功能。有办法吗   规避这个?

@StephenMuecke回答here时,您需要将模型更改为

state
  

另一个问题是,如果我在发布操作中放置一个断点   并发送错误的请求,它甚至不会进入该方法。它发送   通过使用验证属性返回400错误的请求。如何   这项工作?尝试建立模型绑定到该请求后,该请求是否会暂停   属性无效(即名称长度> 10)?我需要做的是   发回具有相同错误消息的422无法处理的实体   而不是400。

这是因为您将public class ItemModel { [Required] public int? Id { get; set; } [MaxLength(10)] public string Name { get; set; } } 应用于控制器。 From the documentation

  

验证错误会自动触发HTTP 400响应。以下代码在您的操作中变得不必要:

ApiControllerAttribute

您可以删除属性,或者如同一链接所述,将其添加到启动配置中:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

答案 1 :(得分:1)

可以通过使该属性可为空来解决您的第一个问题。正如Stepen Muecke所说。

也来看看here,也许BindRequired属性可以提供帮助。本文还介绍了如何调整行为。

对于第二个问题,这是Asp.Net Core 2.1的new(中断)行为。新的是automatic 400 response。这就解释了为什么没有击中断点的原因。您可以按以下步骤取消此操作:

Tools::getValue('Name_Kombinezony')

答案 2 :(得分:1)

对于第一个问题,如果您不想使该属性可为空,则还可以放置range属性[Range(1,int.MaxValue)],但是在这种情况下,0将不是有效值。

对于第二个问题,如果您仍然希望通过ApiControllerAttribute进行自动模型验证,但想要422响应代码而不是400,则可以使用InvalidModelStateResponseFactory配置选项。

services.Configure<ApiBehaviorOptions>(options => 
{
  options.InvalidModelStateResponseFactory = ctx => 
     new UnprocessableEntityObjectResult(ctx.ModelState);
});