2013-07-29 12 views
49

Mam API, gdzie muszę zweryfikować mój model użytkownika. Wybieram podejście, w którym tworzę różne klasy dla czynności tworzenia/edycji, aby uniknąć przypisania masy i dzielenia walidacji oraz rzeczywistego modelu oddzielnie.ModelState.IsValid, nawet jeśli nie powinno być?

Nie wiem dlaczego, ale ModelState.IsValid zwraca wartość true, nawet jeśli nie powinna. czy robię coś źle?

Controller

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

model

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

Debug dowód

proof

+0

Pamiętaj, że nazwy wejściowe html zostały przewidziane w widoku powinna dopasować swoje cechy modela. tzn. nazwa elementu wejściowego w widoku powinna być "Nazwa użytkownika", "Hasło", "Nazwa". może to być przyczyną, dla której obiekt użytkownika ma wartość NULL, co spowodowało pojawienie się w ModelState.IsValid –

+0

@KD Nie używa HTML, oczekuje JSON, ponieważ jest to API, a nie strona internetowa. Ale tak, rozumiem, nawet jeśli wyślę pusty obiekt JSON, to zadziała. – sed

+0

Witam @Steve: Czy możesz pokazać widok? –

Odpowiedz

63

ModelState.IsValid wewnętrznie kontroluje ekspresję Values.All(modelState => modelState.Errors.Count == 0).

Ponieważ nie było danych wejściowych kolekcja Values będzie pusta, więc ModelState.IsValid będzie true.

Więc trzeba jawnie obsłużyć ten przypadek z:

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

} 

czy jest to dobre czy złe decyzja projektowa, że ​​jeśli nic nie będzie zweryfikować to prawda jest inna kwestia ...

+0

Ale jak mogę tego uniknąć? Myślałem, że takie przypadki są domyślnie wbudowane. Rzeczywiście, kiedy wysyłam pusty obiekt JSON, pokazuje on błędy. Bardzo dziwne .. – sed

+4

Nie sądzę, że framework powinien obsługiwać tę sprawę dla Ciebie, ponieważ jest to prawidłowy scenariusz, że parametr ma wartość "null". Możesz na przykład utworzyć filtr akcji, który sprawdzi, czy parametr nie może mieć wartości "null" ... – nemesv

+0

Po prostu zaimplementuję jakiś filtr globalny, który sprawdzi, czy jest poprawny i jest pusty, więc nie będę musiał tego pisać w kontrolerach . Dziękuję za odpowiedź! – sed

11

I napisałem własny filtr, który nie tylko zapewnia, że ​​wszyscy nie opcjonalne właściwości obiektu są przekazywane, ale również sprawdza czy stan model jest poprawny:

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

I umieścić go w globalny filtr dla wszystkich działań Web API:

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

Nie wyświetla błędu modelu, gdy brakuje wymaganej właściwości obiektu. I debugowane GetNotNullParameterNames i zwraca tylko sam model i sprawdza, czy jest pusty. Nie patrzy na poszczególnych członków modelu. – tunafish24

+0

@ tunafish24 Jeśli model ma wartość zerową, nie wydaje mi się, aby zwrot poszczególnych błędów sprawdzania poprawności był uzasadniony. Jeśli model ma wartość, ale niektóre z jego elementów są nieprawidłowe, powyższy filtr poprawnie zwróci nieprawidłowe błędy. –

24

Oto filtr działania, który sprawdza modele zerowe lub nieprawidłowe modele. (Więc nie trzeba pisać czek na każdej akcji)

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

można zarejestrować go globalnie:

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

lub wykorzystać go na żądanie o zajęciach/działania

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

Zwraca nieprawidłowe żądanie, jeśli akcja ma opcjonalne parametry, na przykład 'Get (string id, string something = null)'. – Schileru

-2

to problem mi się zdarzył .i nie wiem dlaczego, ale spokojnie, po prostu zmień swoje działanie Nazwa obiektu (UserCreate User) przez inne podobne (UserCreate User_create)

2

To zdarzyło się mi, aw moim przypadku musiałem zmienić using Microsoft.Build.Framework; na using System.ComponentModel.DataAnnotations; (i dodać odniesienie).

4

Updated nieznacznie do rdzenia 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) 
{ 
    //... 
} 
0

Co zrobiłem było stworzenie Attribute wraz z ActionFilter a Extension Method uniknąć modele null.

Metoda rozszerzenia szuka parametrów z atrybutem NotNull i sprawdza, czy są one zerowe, jeśli są prawdziwe, są tworzone i ustawiane we właściwości ActionArguments.

To rozwiązanie można znaleźć tutaj: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9

Powiązane problemy