ModelState.IsValid même quand il ne devrait pas l’être?

J’ai une API où je dois valider mon modèle d’utilisateur. Je choisis une approche où je crée différentes classes pour les actions de création / modification afin d’éviter les validations d’assignation en masse et de division et l’écart entre les modèles.

Je ne sais pas pourquoi mais ModelState.IsValid renvoie true même si ce n’est pas le cas. Est-ce que je fais quelque chose de mal?

Manette

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

Modèle

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

Preuve de débogage

preuve

ModelState.IsValid vérifie en interne l’ Values.All(modelState => modelState.Errors.Count == 0) .

Comme il n’y avait pas d’entrée, la collection Values sera vide, donc ModelState.IsValid sera true .

Vous devez donc gérer explicitement ce cas avec:

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

Que ce soit une bonne ou une mauvaise décision de conception, si vous ne validez rien, cela sera une question différente …

Voici un filtre d’action pour rechercher les modèles nuls ou les modèles non valides. (vous n’avez donc pas à écrire le chèque pour chaque 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 ValidateViewModelAtsortingbute : ActionFilterAtsortingbute { 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); } } } } 

Vous pouvez l’enregistrer globalement:

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

Ou l’utiliser à la demande sur des classes / actions

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

J’ai écrit un filtre personnalisé qui garantit non seulement que toutes les propriétés d’object non optionnelles sont transmises, mais vérifie également si l’état du modèle est valide:

 [AtsortingbuteUsage (AtsortingbuteTargets.Class | AtsortingbuteTargets.Method, AllowMultiple = false)] public sealed class ValidateModelAtsortingbute : ActionFilterAtsortingbute { private static readonly ConcurrentDictionary> NotNullParameterNames = new ConcurrentDictionary> (); ///  /// Occurs before the action method is invoked. ///  /// The action context. 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 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 (ssortingng)) .Select (p => p.ParameterName) .ToList ()); return result; } } 

Et je l’ai mis dans un filtre global pour toutes les actions de l’API Web:

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

Mis à jour légèrement pour le kernel asp.net …

 [AtsortingbuteUsage(AtsortingbuteTargets.Method)] public sealed class CheckRequiredModelAtsortingbute : ActionFilterAtsortingbute { public override void OnActionExecuting(ActionExecutingContext context) { var requiredParameters = context.ActionDescriptor.Parameters.Where( p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAtsortingbute() != null).Select(p => p.Name); foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, SsortingngComparer.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); } } [AtsortingbuteUsage(AtsortingbuteTargets.Parameter)] public sealed class RequiredModelAtsortingbute : Atsortingbute { } services.AddMvc(options => { options.Filters.Add(typeof(CheckRequiredModelAtsortingbute)); }); public async Task CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken) { //... } 

Cela m’est arrivé et, dans mon cas, j’ai dû changer en using Microsoft.Build.Framework; using System.ComponentModel.DataAnnotations; (et ajoutez la référence).

Ce que j’ai fait a été de créer un Atsortingbute avec un ActionFilter et une Extension Method pour éviter les modèles nuls.

La méthode d’extension recherche les parameters avec l’atsortingbut NotNull et vérifie s’ils sont null, si true, ils sont instanciés et définis dans la propriété ActionArguments .

Cette solution peut être trouvée ici: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

Je cherchais une solution à ce problème et je suis sorti ici en premier. Après quelques recherches supplémentaires, j’ai réalisé la solution suivante:

Comment utilisez-vous ma solution? Vous pouvez l’enregistrer globalement:

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

Ou l’utiliser à la demande pour un cours

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

ou pour une méthode

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

Comme vous pouvez le constater, un atsortingbut [System.ComponentModel.DataAnnotations.Required] a été placé dans le paramètre method. Cela indique que le modèle est requirejs et ne peut pas être null .

Vous pouvez également utiliser un message personnalisé:

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

Voici mon 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 { [AtsortingbuteUsage(AtsortingbuteTargets.Class | AtsortingbuteTargets.Method, Inherited = true)] public class ValidateModelStateAtsortingbute : ActionFilterAtsortingbute { private delegate void ValidateHandler(HttpActionContext actionContext); private static readonly ConcurrentDictionary _validateActionByActionBinding; static ValidateModelStateAtsortingbute() { _validateActionByActionBinding = new ConcurrentDictionary(); } 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(true); if (atsortingbute != null) handler += CreateValidateHandler(atsortingbute, parameterDescriptor.ParameterName); } return handler; } private static ValidateHandler CreateValidateHandler(ValidationAtsortingbute atsortingbute, ssortingng name) { return CreateValidateHandler(atsortingbute, new ValidationContext(new object()) { MemberName = name }); } private static ValidateHandler CreateValidateHandler(ValidationAtsortingbute atsortingbute, ValidationContext context) { return new ValidateHandler(actionContext => { object value; actionContext.ActionArguments.TryGetValue(context.MemberName, out value); var validationResult = atsortingbute.GetValidationResult(value, context); if (validationResult != null) actionContext.ModelState.AddModelError(context.MemberName, validationResult.ErrorMessage); }); } } } 

ce problème m’est arrivé .je ne sais pas pourquoi, mais il est facile de changer l’action Nom de l’utilisateur (UserCreate User) par un autre utilisateur (UserCreate User_create)