2015-09-30 6 views
8

Ich reproduzierte das Problem, das ich in einem brandneuen MVC Web API-Projekt habe.ModelState.IsValid ist falsch, wenn ich einen nullbaren Parameter habe

Dies ist der Standardcode mit einer leichten Änderung.

public string Get(int? id, int? something = null) 
{ 
    var isValid = ModelState.IsValid; 
    return "value"; 
} 

Wenn Sie http://localhost/api/values/5?something=123 gehen dann funktioniert dies gut, und isValid ist true. Wenn Sie zu http://localhost/api/values/5?something= gehen, dann ist isValid false.

Das Problem, das ich habe ist, dass, wenn Sie einen Null oder weggelassen Wert für ein Element bereitzustellen, die NULL-Werte zulässt, die ModelState.IsValid Fahnen ein Fehler Validierung "A value is required but was not present in the request."

Das Model Wörterbuch sieht auch so sagen:

enter image description here

mit zwei Einträgen für something, ein NULL-Werte zulässt, die ich nicht sicher bin, ob es wichtig ist oder nicht.

Eine Idee, wie ich das beheben kann, so dass das Modell gültig ist, wenn NULL-Parameter weggelassen oder als Null bereitgestellt werden? Ich verwende Modellvalidierung innerhalb meiner Web-API und es bricht es, wenn jede Methode mit einem Nullable-Parameter Modellfehler erzeugt.

+0

Potenziell nützliche Informationen hier: http://stackoverflow.com/a/11922978/3130094 - vermutlich werden Sie diese Situation zu sehen, weil eher Als ich den 'something'-Parameter ganz weglasse, addierst du ihn und gibst nichts zu. –

+0

Ich habe versucht, über den JSON-Serialisierer in meinem ursprünglichen Projekt nachzudenken, ich bin mir nicht sicher, wie ich ihn in diesem Projekt ignorieren soll, da ich JSON nicht mache Anfragen aber mit dem Browser (w hich glaube ich ist anders aber könnte falsch liegen?) – NibblyPig

+0

asp.net 4.5.2 wenn das hilft. Ich fügte 'config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore};' hinzu, um die Serialisierung und Deserialisierung von Nullwerten zu ignorieren, aber dies hat nicht funktioniert. Ich weiß nicht, ob 'something =' als 'null' oder '' '(leere Zeichenfolge) behandelt wird, oder ob dieser Serializer sogar für meine Testoperationen verwendet wird. – NibblyPig

Antwort

3

Es scheint, dass das Standardbindungsmodell NULL-Typen nicht vollständig versteht. Wie in der Frage gezeigt, gibt es drei Parameterfehler statt der erwarteten zwei.

Sie können mit einem benutzerdefinierten Nullable-Modell Bindemittel um dieses zu erhalten:

Modell Binder

public class NullableIntModelBinder : IModelBinder 
{ 
    public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext) 
    { 
     if (bindingContext.ModelType != typeof(int?)) 
     { 
      return false; 
     } 

     ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (val == null) 
     { 
      return false; 
     } 

     string rawvalue = val.RawValue as string; 

     // Not supplied : /test/5 
     if (rawvalue == null) 
     { 
      bindingContext.Model = null; 
      return true; 
     } 

     // Provided but with no value : /test/5?something= 
     if (rawvalue == string.Empty) 
     { 
      bindingContext.Model = null; 
      return true; 
     } 

     // Provided with a value : /test/5?something=1 
     int result; 
     if (int.TryParse(rawvalue, out result)) 
     { 
      bindingContext.Model = result; 
      return true; 
     } 

     bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value to int"); 
     return false; 
    } 
} 

Nutzungs

public ModelStateDictionary Get(
    int? id, 
    [ModelBinder(typeof(NullableIntModelBinder))]int? something = null) 
{ 
    var isValid = ModelState.IsValid; 

    return ModelState; 
} 

von der asp.net Seite Angepasst: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api für das weitere Lesen und eine alternative Methode, um es in der Klasse zu setzen (Kontrolle Ebene) und nicht pro Parameter.

Diese behandelt die drei gültigen Szenarien:

/test/5 
/test/5?something= 
/test/5?something=2 

diese erste give "etwas" als null. Alles andere (zB ?something=x) gibt einen Fehler aus.

Wenn Sie die Signatur zu

ändern
int? somthing 

(dh entfernen = null), dann müssen Sie explizit die Parameter liefern, dh /test/5 wird keine gültige Route sein, wenn Sie auch Ihre Routen optimieren.

+0

Awesome, danke, das sieht besser aus als die Herangehensweise, die ich wollte. Ich werde es versuchen. – NibblyPig

+0

Arbeitete ein Vergnügen. Sehr geschätzt. Ungewöhnlich, warum sie dieses Verhalten nicht out of the box oder zumindest eine Einstellung haben. – NibblyPig

0

Entfernen Sie den Standard-Nullwert aus dem zweiten Parameter. Der Modellbinder setzt es auf null, wenn es etwas anderes als int ist.

+0

Dies hat keinen Einfluss darauf, es war nur etwas, was ich versucht habe zu sehen, ob es das Problem beheben würde, das ich in – NibblyPig

+0

gelassen habe Was ist, wenn Sie den Parameter überhaupt nicht im GET angeben? Wie in: GET/api/values ​​/ 5 – Maetis

+0

Sehr interessant, IsValid ist "wahr", wenn weggelassen. Wenn ich jedoch den Standardwert "= null" entferne, stimmt es nicht mit der Route überein. Dies erklärt ein gutes Stück. – NibblyPig

0

Sie müssen ein benutzerdefiniertes Modellbinder für NULL-Typen registrieren, da der Standardbinder den Validator auch für nullwertfähige Parameter aufruft, wobei letzterer diese leeren Werte als ungültig betrachtet.

Das Modell Binder:

public class NullableModelBinder<T> : IModelBinder where T : struct 
{ 
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) 
    { 
     var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 

     if (val == null) 
      return false; 

     var rawVal = val.RawValue as string; 

     if (rawVal == null) 
      return false; 

     var converter = TypeDescriptor.GetConverter(typeof(T)); 

     if (converter.IsValid(rawVal)) 
     { 
      bindingContext.Model = converter.ConvertFromString(rawVal); 
      return true; 
     } 

     bindingContext.ValidationNode.SuppressValidation = true; 
     return false; 
    } 
} 

Anmeldung:

public static class WebApiConfig 
{ 
    public static void Register(HttpConfiguration config) 
    { 
     // ... 

     var provider = new SimpleModelBinderProvider(typeof(int?), new NullableModelBinder<int>()); 
     config.Services.Insert(typeof(ModelBinderProvider), 0, provider); 

     // ... 
    } 
}