当我有一个可为 null 的参数时,ModelState.IsValid 为 false

ModelState.IsValid is false when I have a nullable parameter

我在一个全新的 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

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

ModelState 字典也如下所示:

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

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

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

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

您可以使用自定义可为 null 的模型活页夹解决此问题:

模型活页夹

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 供进一步阅读和在 class(控制器)级别而不是每个参数设置它的替代方法。

这处理了 3 个有效场景:

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

这首先将 "something" 设为 null。任何其他内容(例如 ?something=x)都会出错。

如果您将签名更改为

int? somthing

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

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

模型活页夹:

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);

        // ...
    }
}

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