【问题标题】:ModelState.IsValid is false when I have a nullable parameter当我有一个可为空的参数时,ModelState.IsValid 为 false
【发布时间】:2015-09-30 08:27:32
【问题描述】:

我在一个全新的 MVC Web API 项目中重现了我遇到的问题。

这是默认代码,稍作修改。

public string Get(int? id, int? something = null)
{
    var isValid = ModelState.IsValid;
    return "value";
}

如果你转到http://localhost/api/values/5?something=123,那么这工作正常,isValid 是true

如果你去http://localhost/api/values/5?something=,那么isValid就是false

我遇到的问题是,如果您为可为空的项目提供空值或省略值,则 ModelState.IsValid 会标记验证错误,提示 "A value is required but was not present in the request."

ModelState 字典也如下所示:

something 有两个条目,一个可以为空,我不确定它是否重要。

知道如何解决这个问题,以便在可空参数被省略或作为空参数提供时模型有效吗?我在我的 web api 中使用模型验证,如果每个带有可为空参数的方法都生成模型错误,它会破坏它。

【问题讨论】:

  • 此处可能有用的信息:stackoverflow.com/a/11922978/3130094 - 大概您看到了这种情况,因为您没有完全省略 something 参数,而是添加它并没有分配任何内容
  • 我确实尝试在我的原始项目中弄乱 json 序列化程序,但我不确定如何在这个项目中将其设置为忽略,因为我没有发出 json 请求,而是使用浏览器(我相信是不同的,但可能是错的?)
  • asp.net 4.5.2 如果有帮助的话。我添加了config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; 告诉它忽略空值的序列化和反序列化,但这不起作用。我不知道something= 是否被视为null"" (empty string),或者这个序列化程序是否甚至用于我的测试操作。
  • 查看错误,有 2 个参数,但对 三个 参数有错误。如果您将 url 传递为:“/test/5?something.Nullable`1=" - 那么它通过验证。看起来您需要添加自定义绑定提供程序。
  • @SLC JsonSerializer 是否在此处执行任何操作,参数作为 url 参数提供,而不是通过 post 作为 json 提供。所以这个设置对你的问题没有任何作用

标签: asp.net-mvc asp.net-web-api model-binding


【解决方案1】:

似乎默认绑定模型不能完全理解可为空的类型。从问题中可以看出,它给出了三个参数错误,而不是预期的两个。

您可以使用自定义可为空的模型绑定器解决此问题:

模型绑定器

public class NullableIntModelBinder : IModelBinder
{
    public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(int?))
        {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (val == null)
        {
            return false;
        }

        string rawvalue = val.RawValue as string;

        // Not supplied : /test/5
        if (rawvalue == null)
        {
            bindingContext.Model = null;
            return true;
        }

        // Provided but with no value : /test/5?something=
        if (rawvalue == string.Empty)
        {
            bindingContext.Model = null;
            return true;
        }

        // Provided with a value : /test/5?something=1
        int result;
        if (int.TryParse(rawvalue, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value to int");
        return false;
    }
}

用法

public ModelStateDictionary Get(
    int? id, 
    [ModelBinder(typeof(NullableIntModelBinder))]int? something = null)
{
    var isValid = ModelState.IsValid;

    return ModelState;
}

改编自 asp.net 页面:http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api 以供进一步阅读以及在类(控制器)级别而不是按参数设置它的替代方法。

这处理了 3 个有效场景:

/test/5
/test/5?something=
/test/5?something=2

这首先将“某物”作为空值。其他任何内容(例如?something=x)都会出错。

如果您将签名更改为

int? somthing

(即删除= null)然后您必须明确提供参数,即/test/5 将不是有效路线,除非您也调整路线。

【讨论】:

  • 太棒了,谢谢,这看起来比我想要的方法更整洁。我会试一试的。
  • 辛苦了。非常感激。不寻常的是为什么他们没有开箱即用的这种行为,或者至少没有设置。
【解决方案2】:

您必须为可空类型注册一个自定义模型绑定器,因为默认绑定器也会为可空参数调用验证器,而后者将这些空值视为无效。

模型绑定器:

public class NullableModelBinder<T> : System.Web.Http.ModelBinding.IModelBinder where T : struct
{
    private static readonly TypeConverter converter = TypeDescriptor.GetConverter( typeof( T ) );

    public bool BindModel( HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext )
    {
        var val = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );

        // Cast value to string but when it fails we must not suppress the validation
        if ( !( val?.RawValue is string rawVal ) ) return false;

        // If the string contains a valid value we can convert it and complete the binding
        if ( converter.IsValid( rawVal ) )
        {
            bindingContext.Model = converter.ConvertFromString( rawVal );
            return true;
        }

        // If the string does contain data it cannot be nullable T and we must not suppress this error
        if ( !string.IsNullOrWhiteSpace( rawVal ) ) return false;

        // String is empty and allowed due to it being a nullable type
        bindingContext.ValidationNode.SuppressValidation = true;
        return false;
    }
}

注册:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // ...

        var provider = new SimpleModelBinderProvider(typeof(int?), new NullableModelBinder<int>());
        config.Services.Insert(typeof(ModelBinderProvider), 0, provider);

        // ...
    }
}

【讨论】:

  • 解决方案比公认的答案要优雅得多
【解决方案3】:

从第二个参数中移除默认的空值。如果它不是 int,模型绑定器会将其设置为 null。

【讨论】:

  • 这不影响它,我只是想看看它是否能解决我留下的问题
  • 如果在GET中根本不指定参数怎么办?如:GET /api/values/5
  • 非常有趣,省略时 isValid 为true。如果我删除 =null 默认值,它与路由不匹配。这说明了一点。
  • 当你有一个带前缀的空 URL 值时,它被视为 null。
【解决方案4】:

我为我找到了一个可行的解决方法(只需从正在发送的数据中排除空值 - 而不是将值作为空值发送)。

https://stackoverflow.com/a/66712465/908608

【讨论】:

    猜你喜欢
    • 2021-09-14
    • 2019-05-20
    • 2018-10-07
    • 2012-05-20
    • 1970-01-01
    • 2010-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多