ModelState.IsValid anche quando non dovrebbe essere?

Ho API in cui ho bisogno di convalidare il mio modello utente. Scelgo un approccio in cui creo classi diverse per creare / modificare azioni per evitare l’assegnazione di massa e dividere la convalida e il modello reale a parte.

Non so perché, ma ModelState.IsValid restituisce true anche quando non dovrebbe. Sto facendo qualcosa di sbagliato?

controllore

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

Modello

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

Prova di debug

prova

ModelState.IsValid verifica internamente l’ Values.All(modelState => modelState.Errors.Count == 0) .

Poiché non è stato inserito alcun input, la raccolta Values sarà vuota, quindi ModelState.IsValid sarà true .

Quindi devi gestire esplicitamente questo caso con:

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

Se questa è una decisione di progettazione buona o ctriggers che se non convalidare nulla sarà vero è una domanda diversa …

Ecco un filtro azioni per verificare la presenza di modelli nulli o modelli non validi. (quindi non devi scrivere il controllo su ogni azione)

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

Puoi registrarlo globalmente:

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

O usalo su richiesta su classi / azioni

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

Ho scritto un filtro personalizzato che non solo assicura che tutte le proprietà dell’object non facoltative siano passate, ma controlla anche se lo stato del modello è valido:

 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public sealed class ValidateModelAttribute : ActionFilterAttribute { 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 (string)) .Select (p => p.ParameterName) .ToList ()); return result; } } 

E l’ho messo nel filtro globale per tutte le azioni dell’API Web:

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

Aggiornato leggermente per il nucleo di 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() != 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 CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken) { //... } 

Questo è successo a me, e nel mio caso, ho dovuto cambiare using Microsoft.Build.Framework; using System.ComponentModel.DataAnnotations; (e aggiungi il riferimento).

Quello che ho fatto è stato creare un Attribute con un ActionFilter e un Extension Method per evitare modelli nulli.

Il metodo di estensione cerca i parametri con l’attributo NotNull e controlla se sono nulli, se è vero, vengono istanziati e impostati nella proprietà ActionArguments .

Questa soluzione può essere trovata qui: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

Stavo cercando una soluzione a questo problema e sono venuto qui per primo. Dopo alcune ricerche ulteriori ho realizzato la seguente soluzione:

Come usi la mia soluzione? Puoi registrarlo globalmente:

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

O usalo su richiesta per una class

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

o per un metodo

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

Come puoi vedere, un [System.ComponentModel.DataAnnotations.Required] è stato inserito nel parametro method. Questo indica che il modello è richiesto e non può essere null .

Puoi anche usare con un messaggio personalizzato:

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

Ecco il mio codice:

 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 _validateActionByActionBinding; static ValidateModelStateAttribute() { _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 (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); }); } } } 

questo problema mi è accaduto. Non so perché, ma lascia perdere l’idea, basta cambiare la tua azione Nome object (UserCreate User) con altri (UserCreate User_create)