C# ModelState.IsValid 即使它不应该是?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17923622/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 10:50:32  来源:igfitidea点击:

ModelState.IsValid even when it should not be?

c#asp.net-web-api

提问by Stan

I have API where I need to validate my user model. I choose an approach where I create different classes for Create/Edit actions to avoid mass-assignment and divide validation and actual model apart.

我有需要验证我的用户模型的 API。我选择了一种方法,为创建/编辑操作创建不同的类,以避免批量分配并将验证和实际模型分开。

I don't know why but ModelState.IsValidreturns true even when it should not. Am I doing something wrong?

我不知道为什么,但ModelState.IsValid即使不应该返回 true。难道我做错了什么?

Controller

控制器

public HttpResponseMessage Post(UserCreate user)
{
    if (ModelState.IsValid) // It's valid even when user = null
    {
        var newUser = new User
        {
            Username = user.Username,
            Password = user.Password,
            Name = user.Name
        };
        _db.Users.Add(newUser);
        _db.SaveChanges();
        return Request.CreateResponse(HttpStatusCode.Created, new { newUser.Id, newUser.Username, newUser.Name });
    }
    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}

Model

模型

public class UserCreate
{
    [Required]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
    [Required]
    public string Name { get; set; }
}

Debug proof

调试证明

proof

证明

采纳答案by nemesv

The ModelState.IsValidinternally checks the Values.All(modelState => modelState.Errors.Count == 0)expression.

ModelState.IsValid内部检查Values.All(modelState => modelState.Errors.Count == 0)的表达。

Because there was no input the Valuescollection will be empty so ModelState.IsValidwill be true.

因为没有输入的Values集将是空的,所以ModelState.IsValidtrue

So you need to explicitly handle this case with:

所以你需要明确地处理这种情况:

if (user != null && ModelState.IsValid)
{

}

Whether this is a good or bad design decision that if you validate nothing it will true is a different question...

这是一个好还是坏的设计决定,如果你什么都不验证,它就会是真的,这是一个不同的问题......

回答by Michael Logutov

I wrote a custom filter which not only ensures that all non optional object properties are passed, but also checks if model state is valid:

我编写了一个自定义过滤器,它不仅可以确保传递所有非可选对象属性,还可以检查模型状态是否有效:

[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class ValidateModelAttribute : ActionFilterAttribute
{
    private static readonly ConcurrentDictionary<HttpActionDescriptor, IList<string>> NotNullParameterNames =
        new ConcurrentDictionary<HttpActionDescriptor, IList<string>> ();


    /// <summary>
    /// Occurs before the action method is invoked.
    /// </summary>
    /// <param name="actionContext">The action context.</param>
    public override void OnActionExecuting (HttpActionContext actionContext)
    {
        var not_null_parameter_names = GetNotNullParameterNames (actionContext);
        foreach (var not_null_parameter_name in not_null_parameter_names)
        {
            object value;
            if (!actionContext.ActionArguments.TryGetValue (not_null_parameter_name, out value) || value == null)
                actionContext.ModelState.AddModelError (not_null_parameter_name, "Parameter \"" + not_null_parameter_name + "\" was not specified.");
        }


        if (actionContext.ModelState.IsValid == false)
            actionContext.Response = actionContext.Request.CreateErrorResponse (HttpStatusCode.BadRequest, actionContext.ModelState);
    }


    private static IList<string> GetNotNullParameterNames (HttpActionContext actionContext)
    {
        var result = NotNullParameterNames.GetOrAdd (actionContext.ActionDescriptor,
                                                     descriptor => descriptor.GetParameters ()
                                                                             .Where (p => !p.IsOptional && p.DefaultValue == null &&
                                                                                          !p.ParameterType.IsValueType &&
                                                                                          p.ParameterType != typeof (string))
                                                                             .Select (p => p.ParameterName)
                                                                             .ToList ());

        return result;
    }
}

And I put it in global filter for all Web API actions:

我把它放在所有 Web API 操作的全局过滤器中:

config.Filters.Add (new ValidateModelAttribute ());

回答by Jose Ch.

Here is an action filter to check for null models or invalid models. (so you dont have to write the check on every action)

这是一个用于检查空模型或无效模型的操作过滤器。(所以你不必对每个动作都写检查)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Studio.Lms.TrackingServices.Filters
{
    public class ValidateViewModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ActionArguments.Any(kv => kv.Value == null)) {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Arguments cannot be null");
            }

            if (actionContext.ModelState.IsValid == false) {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
}

You can register it globally:

您可以全局注册它:

config.Filters.Add(new ValidateViewModelAttribute());

Or use it on demand on classes/actions

或者在类/操作上按需使用它

 [ValidateViewModel]
 public class UsersController : ApiController
 { ...

回答by javad ghamari

this issue happened to me .i do not know why but take it easy just change your action Object name(UserCreate User) by some other like (UserCreate User_create)

这个问题发生在我身上。我不知道为什么但放轻松,只需将您的操作对象名称(UserCreate User)更改为其他类似(UserCreate User_create)

回答by james

This happened to me, and in my case, I had to change using Microsoft.Build.Framework;to using System.ComponentModel.DataAnnotations;(and add the reference).

这发生在我身上,就我而言,我不得不更改using Microsoft.Build.Framework;using System.ComponentModel.DataAnnotations;(并添加引用)。

回答by James Law

Updated slightly for asp.net core...

对 asp.net 核心略有更新...

[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var requiredParameters = context.ActionDescriptor.Parameters.Where(
            p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredModelAttribute>() != null).Select(p => p.Name);

        foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, StringComparer.Ordinal)))
        {
            if (argument.Value == null)
            {
                context.ModelState.AddModelError(argument.Key, $"The argument '{argument.Key}' cannot be null.");
            }
        }

        if (!context.ModelState.IsValid)
        {
            var errors = context.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
            context.Result = new BadRequestObjectResult(errors);
            return;
        }

        base.OnActionExecuting(context);
    }
}

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredModelAttribute : Attribute
{
}

services.AddMvc(options =>
{
    options.Filters.Add(typeof(CheckRequiredModelAttribute));
});

public async Task<IActionResult> CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken)
{
    //...
}

回答by Ariel Moraes

What I did was to create an Attributealong with an ActionFilterand a Extension Methodto avoid null models.

我所做的是创建一个Attribute以及一个ActionFilter和一个Extension Method以避免空模型。

The extension method looks for parameters with the NotNullattribute and check if they are null, if true, they are instantiated and set in the ActionArgumentsproperty.

扩展方法查找具有NotNull属性的参数并检查它们是否为空,如果为真,则将它们实例化并在ActionArguments属性中设置。

This solution can be found here: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

这个解决方案可以在这里找到:https: //gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

回答by Roberto B

I was looking for a solution to this problem and came out here first. After some further research I have realized the following solution:

我正在寻找解决此问题的方法,并首先来到这里。经过一些进一步的研究,我意识到了以下解决方案:

How do you use my solution? You can register it globally:

你如何使用我的解决方案?您可以全局注册它:

config.Filters.Add(new ValidateModelStateAttribute());

Or use it on demand for a class

或者在课堂上按需使用

[ValidateModelState]
public class UsersController : ApiController
{...

or for a methode

或方法

[ValidateModelState]
public IHttpActionResult Create([Required] UserModel data)
{...

As you can see, a [System.ComponentModel.DataAnnotations.Required]atribute has been placed in the method parameter. This indicates that the model is required and can not be null.

如您所见,[System.ComponentModel.DataAnnotations.Required]在方法参数中放置了一个属性。这表明模型是必需的,不能是null

You can also use with a custom message:

您还可以使用自定义消息:

[ValidateModelState]
public IHttpActionResult Create([Required(ErrorMessage = "Custom message")] UserModel data)
{...

Here is my code:

这是我的代码:

using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace your_base_namespace.Web.Http.Filters
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
    public class ValidateModelStateAttribute : ActionFilterAttribute
    {
        private delegate void ValidateHandler(HttpActionContext actionContext);

        private static readonly ConcurrentDictionary<HttpActionBinding, ValidateHandler> _validateActionByActionBinding;

        static ValidateModelStateAttribute()
        {
            _validateActionByActionBinding = new ConcurrentDictionary<HttpActionBinding, ValidateHandler>();
        }

        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            GetValidateHandler(actionContext.ActionDescriptor.ActionBinding)(actionContext);

            if (actionContext.ModelState.IsValid)
                return;

            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
        }

        private ValidateHandler GetValidateHandler(HttpActionBinding actionBinding)
        {
            ValidateHandler validateAction;

            if (!_validateActionByActionBinding.TryGetValue(actionBinding, out validateAction))
                _validateActionByActionBinding.TryAdd(actionBinding, validateAction = CreateValidateHandler(actionBinding));

            return validateAction;
        }

        private ValidateHandler CreateValidateHandler(HttpActionBinding actionBinding)
        {
            ValidateHandler handler = new ValidateHandler(c => { });

            var parameters = actionBinding.ParameterBindings;

            for (int i = 0; i < parameters.Length; i++)
            {
                var parameterDescriptor = (ReflectedHttpParameterDescriptor)parameters[i].Descriptor;
                var attribute = parameterDescriptor.ParameterInfo.GetCustomAttribute<RequiredAttribute>(true);

                if (attribute != null)
                    handler += CreateValidateHandler(attribute, parameterDescriptor.ParameterName);
            }

            return handler;
        }

        private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, string name)
        {            
            return CreateValidateHandler(attribute, new ValidationContext(new object()) { MemberName = name });
        }

        private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, ValidationContext context)
        {
            return new ValidateHandler(actionContext =>
            {
                object value;
                actionContext.ActionArguments.TryGetValue(context.MemberName, out value);

                var validationResult = attribute.GetValidationResult(value, context);
                if (validationResult != null)
                    actionContext.ModelState.AddModelError(context.MemberName, validationResult.ErrorMessage);
            });
        }
    }
}

回答by Yagnesh Khamar

There is a simple Solution for your problem

您的问题有一个简单的解决方案

public class UserCreate
{
    [Required(AllowEmptyStrings = false)]
    public string Username { get; set; }
}

Here AllowEmptyStrings = falsecan be used for your validation

这里AllowEmptyStrings = false可用于您的验证

回答by Iran Canul

Try

尝试

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

in the startup.csfile's ConfigureServices()

startup.cs文件的ConfigureServices()