ModelState.IsValid, даже если этого не должно быть? - программирование
Подтвердить что ты не робот

ModelState.IsValid, даже если этого не должно быть?

У меня есть API, где мне нужно проверить мою модель пользователя. Я выбираю подход, в котором я создаю разные классы для создания/редактирования действий, чтобы избежать массового присвоения и разделить валидацию и фактическую модель.

Я не знаю, почему, но ModelState.IsValid возвращает true, даже если это не так. Я что-то делаю неправильно?

Controller

public HttpResponseMessage Post(UserCreate user)
{
    if (ModelState.IsValid) // It 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; }
}

Проверка отладки

proof

4b9b3361

Ответ 1

ModelState.IsValid внутренне проверяет выражение Values.All(modelState => modelState.Errors.Count == 0).

Поскольку не было ввода, коллекция Values будет пустой, поэтому ModelState.IsValid будет true.

Поэтому вам нужно явно обработать этот случай с помощью:

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

}

Является ли это хорошим или плохим дизайнерским решением, что, если вы ничего не подтверждаете, это будет другой вопрос...

Ответ 2

Вот фильтр действий для проверки нулевых моделей или недопустимых моделей. (так что вам не нужно писать чек на каждое действие)

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

Ответ 3

Я написал настраиваемый фильтр, который не только гарантирует, что все свойства необязательного объекта передаются, но также проверяет, действительно ли состояние модели:

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

И я поместил его в глобальный фильтр для всех действий веб-API:

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

Ответ 4

Немного обновлено для ядра 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)
{
    //...
}

Ответ 5

Это произошло со мной, и в моем случае мне пришлось изменить using Microsoft.Build.Framework; на using System.ComponentModel.DataAnnotations; (и добавить ссылку).

Ответ 6

Я искал решение этой проблемы и пришел сюда первым. После некоторых дальнейших исследований я понял следующее решение:

Как вы используете мое решение? Вы можете зарегистрировать это глобально:

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

Ответ 7

Есть простое решение для вашей проблемы

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

Здесь AllowEmptyStrings = false может использоваться для вашей проверки

Ответ 8

Я сделал, чтобы создать Attribute вместе с ActionFilter и Extension Method, чтобы избежать нулевых моделей.

Метод расширения ищет параметры с атрибутом NotNull и проверяет, являются ли они пустыми, если они истинны, они создаются и устанавливаются в свойстве ActionArguments.

Это решение можно найти здесь: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

Ответ 9

Пытаться

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

в файле startup.cs ConfigureServices()

Ответ 10

эта проблема произошла со мной. Я не знаю, почему, но просто успокойтесь, просто измените свое действие. Имя объекта (UserCreate User) каким-то другим (UserCreate User_create)