ASP.NET Core ApiController 中显式 'null' 和 'not specified' 的区别

Differentiating between explicit 'null' and 'not specified' in ASP.NET Core ApiController

这是我在这里潜伏多年后的第一个问题,所以我希望我没有违反任何规则。

在我的一些 ASP.NET Core API 的 POST 方法中,我想让客户可以只提供他们想要更新的属性他们 POST 请求的正文。

这是我的代码的简化版本:

[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class FooController : ControllerBase
{
    public async Task<IActionResult> UpdateFooAsync(Guid fooGuid, [FromBody]UpdateFooModel model)
    {
        ... Apply updates for specified properties, checking for authorization where needed...

        return Ok();
    }
}

public sealed class UpdateFooModel
{
    [BindProperty] public int? MaxFoo { get; set; }
    [BindProperty] public int? MaxBar { get; set; }
}

public sealed class Foo
{
    public int? MaxFoo { get; set; }
    public int? MaxBar { get; set; }
}

MaxBar 和 MaxFoo 都是可以为 null 的整数值,其中 null 值表示没有最大值。

我正在尝试让客户发送例如此端点的以下内容:

在我的方法 UpdateFooAsync 中,我只想更新请求中指定的属性。

但是,当发生模型绑定时,未指定的属性将设置为其默认值(null 可空类型)。

查明值是否明确设置为 null(应设置为 null)或请求中不存在(应设置为 null)的最佳方法是什么?不更新)?

我试过检查 ModelState,但它不包含 'model' 的键,只包含 Guid 类型参数。

当然,也欢迎任何其他解决核心问题的方法。

谢谢!

这是一个可能的解决方案。使用 UpdateFooModel class 的设置器中的逻辑来检查 null 并分配一个不同的值,例如 Int32.MaxValue。 setter 只在传入参数时调用。在示例中,如果显式传入 null,则会将其转换为 Int32.MaxValue。如果未指定参数,则该值将保持为空。使用 setter 的另一种方法是使用默认构造函数并添加一些逻辑以根据是否指定参数来设置不同的值。示例:

public sealed class UpdateFooModel
{
    private int? _maxFoo;
    public int? MaxFoo
    {
        get
        {
            return _maxFoo;
        }

        set
        {
            _maxFoo = (value == null) ? Int32.MaxValue : value;
        }
    }

    private int? _maxBar;
    public int? MaxBar
    {
        get
        {
            return _maxBar;
        }

        set
        {
            _maxBar = (value == null) ? Int32.MaxValue : value;
        }
    }
}

根据@russ-w 的建议在这里回答我自己的问题(谢谢!): 通过在每个可选的 属性 的 setter 中标记一个 bool 属性,我们可以找出它是否被提供。

public sealed class UpdateFooModel
{
    private int? _maxFoo;
    private int? _maxBar;

    [BindProperty] 
    public int? MaxFoo
    { 
        get => _maxFoo;
        set
        {
            _maxFoo = value;
            MaxFooSet = true;
        }
    }

    public bool MaxFooSet { get; private set; }

    [BindProperty] 
    public int? MaxBar
    { 
        get => _maxBar;
        set
        {
            _maxBar = value;
            MaxBarSet = true;
        }
    }

    public bool MaxBarSet { get; private set; }
}

当然,我们仍然欢迎进一步改进或其他解决方案!