ModelState.IsValid even when it should not be?

前端 未结 10 1243
说谎
说谎 2020-11-29 03:14

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 validati

相关标签:
10条回答
  • 2020-11-29 03:41

    There is a simple Solution for your problem

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

    Here AllowEmptyStrings = false can be used for your validation

    0 讨论(0)
  • 2020-11-29 03:42

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

    Because there was no input the Values collection will be empty so ModelState.IsValid will be true.

    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...

    0 讨论(0)
  • 2020-11-29 03:42

    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
     { ...
    
    0 讨论(0)
  • 2020-11-29 03:44

    What I did was to create an Attribute along with an ActionFilter and a Extension Method to avoid null models.

    The extension method looks for parameters with the NotNull attribute and check if they are null, if true, they are instantiated and set in the ActionArguments property.

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

    0 讨论(0)
  • 2020-11-29 03:48

    Updated slightly for asp.net core...

    [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)
    {
        //...
    }
    
    0 讨论(0)
  • 2020-11-29 03:49

    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.

    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);
                });
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题