ASP.NET Core [Require]非空类型

Mor*_*sen 5 c# validation asp.net-core

在这里,提出了一个问题,即如何验证不可为空的必需类型。

在我的情况下,不希望提供如下使字段为空的解决方案。

[Required]
public int? Data { get; set; }
Run Code Online (Sandbox Code Playgroud)

在请求中省略该字段的情况下,如何更改行为以进行以下失败验证。

[Required]
public int Data { get; set; }
Run Code Online (Sandbox Code Playgroud)

我尝试了一个自定义验证器,但是这些没有原始值的信息,只能看到默认0值。我还尝试了自定义模型绑定程序,但它似乎可以在整个请求模型的级别上运行,而不是在所需的整数字段上运行。我的资料夹实验看起来像这样:

public class RequiredIntBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(int))
            throw new InvalidOperationException($"{nameof(RequiredIntBinder)} can only be applied to integer properties");

        var value = bindingContext.ValueProvider.GetValue(bindingContext.BinderModelName);
        if (value == ValueProviderResult.None)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }

        return new SimpleTypeModelBinder(bindingContext.ModelType).BindModelAsync(bindingContext);
    }
}

public class RequiredIntBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(int))
        {
            return new BinderTypeModelBinder(typeof(RequiredIntBinder));
        }

        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样在mvc中注册

options.ModelBinderProviders.Insert(0, new RequiredIntBinderProvider());
Run Code Online (Sandbox Code Playgroud)

但从未使用过模型绑定器。我觉得我可能很近,但无法连接最后一个点。

fra*_*kon 7

使用 json 请求的解决方案

无法验证已创建的模型实例,因为不可为 null 的属性始终具有值(无论它是从 json 分配还是默认值)。该解决方案是反序列化过程中已经报告丢失的价值

创建合约解析器

public class RequiredPropertiesContractResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        foreach (var contractProperty in contract.Properties)
        {
            if (contractProperty.PropertyType.IsValueType
                && contractProperty.AttributeProvider.GetAttributes(typeof(RequiredAttribute), inherit: true).Any())
            {
                contractProperty.Required = Required.Always;
            }
        }

        return contract;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将其分配给SerializerSettings

services.AddMvc()
        .AddJsonOptions(jsonOptions =>
        {
            jsonOptions.SerializerSettings.ContractResolver = new RequiredPropertiesContractResolver();
        });
Run Code Online (Sandbox Code Playgroud)

所述ModelState然后是无效的与非空的属性[Required]属性如果值是从JSON丢失。


例子

JSON 体

var jsonBody = @"{ Data2=123 }"
Run Code Online (Sandbox Code Playgroud)

对模型无效

class Model
{
    [Required]
    public int Data { get; set; }

    public int Data2 { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

  • `System.Text.Json` 有类似的解决方案吗? (5认同)
  • 多谢。如果一切都可以为空,那么使用静态类型语言有什么意义呢?这对我来说毫无意义,我现在对 aspnetcore 很生气。 (2认同)

Chr*_*att 6

请求中的所有内容都只是一个字符串。模型绑定器将请求正文中的键与属性名称进行匹配,然后尝试将它们强制转换为适当的类型。如果该属性未发布或使用空字符串发布,则在尝试转换为 int 时显然会失败。因此,您最终会得到该类型的默认值。在的情况下int这是0,虽然默认值int?null

只有在此绑定过程完成后,模型才会被验证。请记住,您正在验证模型而不是帖子正文。没有合理的方法来验证帖子正文,因为同样,它只是一堆键值对字符串。因此,在int需要但未发布的属性的情况下,值为0,这是 int 的完全有效值,并且验证得到满足。在 的情况下int?,值为null,它不是有效的 int,因此验证失败。这就是为什么需要 nullable 的原因,如果你想要求一个不可为 null 的类型有一个值。这是将空值与简单的“默认”值区分开来的唯一方法。

如果您正在使用视图模型,这应该不是问题。您可以绑定到具有必需属性的可空 int,并且如果您的模型状态有效,您将确信它会有一个值,尽管它可以为空。然后,您可以将其映射到实体上的直接 int。这是处理事情的正确方法。

  • 如果在 ASP.NET 中无法实现这一点,我觉得这是一个不幸的设计选择。在使用请求模型时,我无法在不实际检查类型的情况下知道该字段是必需的,因此您会失去一些意图。我可能会考虑创建一个模型绑定器,将默认值设置为“int.Min”,并让验证器将其解释为 null。虽然是 hack,但它应该可以完成工作。 (2认同)