70

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

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

控制器

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

模型

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

调试证明

证明

4

11 回答 11

81

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

因为没有输入,所以Values集合将为ModelState.IsValidtrue

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

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

}

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

于 2013-07-29T12:14:16.890 回答
27

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

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

您可以在全球范围内注册它:

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

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

 [ValidateViewModel]
 public class UsersController : ApiController
 { ...
于 2014-09-14T03:42:06.417 回答
12

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

[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;
    }
}

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

config.Filters.Add (new ValidateModelAttribute ());
于 2014-07-02T07:58:15.653 回答
5

为 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)
{
    //...
}
于 2017-05-19T13:06:27.500 回答
3

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

于 2016-02-18T23:28:03.380 回答
3

我一直在寻找这个问题的解决方案,并首先来到这里。经过一些进一步的研究,我已经实现了以下解决方案:

你如何使用我的解决方案?您可以在全球范围内注册它:

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

或者根据需要使用它来上课

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

或方法

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

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

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

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

这是我的代码:

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);
            });
        }
    }
}
于 2018-07-02T12:47:27.107 回答
2

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

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

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

于 2018-12-18T10:25:36.617 回答
1

尝试

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

startup.cs文件的ConfigureServices()

于 2019-03-28T04:12:44.387 回答
0

我所做的是创建 anAttribute以及 anActionFilter和 aExtension Method以避免空模型。

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

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

于 2017-11-22T21:49:28.703 回答
0

如果您忘记在模型上添加 getter 和 setter(OP 没有忘记,但我做到了,这让我想到了这个问题),也会出现这个“ModelState.IsValid 即使不应该返回 true”的问题。我希望可以提供具有相同症状但与 OP 代码略有不同的解决方案:

错误的:

    public class UserRegisterModel
    {
        [Required]
        public string Login; // WRONG
        [Required]
        public string Password; // WRONG
    }

好的:

    public class UserRegisterModel
    {
        [Required]
        public string Login { get; set; }
        [Required]
        public string Password { get; set; }
    }
于 2021-09-30T11:26:59.270 回答
-2

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

于 2015-09-28T16:06:35.367 回答