2017-12-04 1 views
0

Ich bin stecken mit Bindung eines optionalen Array in einem ASP.NET Core Controller. Das Array enthält Elemente eines benutzerdefinierten Typs. Einzelne Elemente dieses Typs werden mit einem benutzerdefinierten Modellbinder gebunden und in diesem validiert.Modelbinding ein optionales Array eines benutzerdefinierten Modells gebundenen Typ

Beispiel Repo hier: https://github.com/MarcusKohnert/OptionalArrayModelBinding

ich nur zwei Tests von drei in der Probe Testprojekt arbeiten: https://github.com/MarcusKohnert/OptionalArrayModelBinding/blob/master/OptionalArrayModelBindingTest/TestOptionalArrayCustomModelBinder.cs

public class TestOptionalArrayCustomModelBinder 
{ 
    private readonly TestServer server; 
    private readonly HttpClient client; 

    public TestOptionalArrayCustomModelBinder() 
    { 
     server = new TestServer(new WebHostBuilder().UseStartup<Startup>()); 

     client = server.CreateClient(); 
    } 

    [Fact] 
    public async Task SuccessWithoutProvidingIds() 
    { 
     var response = await client.GetAsync("/api/values"); 

     Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); 
    } 

    [Fact] 
    public async Task SuccessWithValidIds() 
    { 
     var response = await client.GetAsync("/api/values?ids=aaa001&ids=bbb002"); 

     Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); 
    } 

    [Fact] 
    public async Task FailureWithOneInvalidId() 
    { 
     var response = await client.GetAsync("/api/values?ids=xaaa001&ids=bbb002"); 

     Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode); 
    } 
} 

Controller:

[Route("api/[controller]")] 
public class ValuesController : Controller 
{ 
    [HttpGet] 
    public IActionResult Get(CustomIdentifier[] ids) 
    { 
     if (this.ModelState.IsValid == false) return this.BadRequest(); 

     return this.Ok(ids); 
    } 
} 

Startup:

public class Startup 
{ 
    public void ConfigureServices(IServiceCollection services) 
    { 
     services.AddMvc(options => 
     { 
      options.ModelBinderProviders.Insert(0, new CutomIdentifierModelBinderProvider()); 
      //options.ModelBinderProviders.Add(new CutomIdentifierModelBinderProvider()); 
     }); 
    } 

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
    { 
     if (env.IsDevelopment()) 
     { 
      app.UseDeveloperExceptionPage(); 
     } 

     app.UseMvc(); 
    } 
} 

Modelbinder:

public class CutomIdentifierModelBinderProvider : IModelBinderProvider 
{ 
    public IModelBinder GetBinder(ModelBinderProviderContext context) 
    { 
     //if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[])) 
     //{ 
     // return new ArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder()); 
     //} 

     if (context.Metadata.ModelType == typeof(CustomIdentifier)) 
     { 
      return new BinderTypeModelBinder(typeof(CustomIdentifierModelBinder)); 
     } 

     return null; 
    } 
} 

public class CustomIdentifierModelBinder : IModelBinder 
{ 
    public Task BindModelAsync(ModelBindingContext bindingContext) 
    { 
     var attemptedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString(); 
     var parseResult = CustomIdentifier.TryParse(attemptedValue); 

     if (parseResult.Failed) 
     { 
      bindingContext.Result = ModelBindingResult.Failed(); 
      bindingContext.ModelState.AddModelError(bindingContext.ModelName, parseResult.Message.Message); 
     } 
     else 
     { 
      bindingContext.Model = parseResult.Value; 
      bindingContext.Result = ModelBindingResult.Success(parseResult.Value); 
     } 

     return Task.CompletedTask; 
    } 
} 

Die Standard-MVC ArrayModelBinder von T korrekt optionale Arrays bindet und setzt ModelState.IsValid auf true. Wenn ich einen eigenen CustomIdentifierModelBinder verwende, wird ModelState.IsValid jedoch false sein. Leere Arrays werden nicht als gültig erkannt.

Wie kann ich dieses Problem lösen? Danke im Voraus.

Antwort

0

Sie sind sehr nah dran. Passen Sie das Verhalten des eingebauten ArrayModelBinder für den Fall eines fehlenden Parameters an. Wenn der extrahierte Wert eine leere Zeichenfolge ist, füllen Sie das Modell einfach mit einem leeren Array. In allen anderen Fällen könntest du ArrayModelBinder anrufen.

Hier ist eine Arbeitsprobe, die alle Ihre 3 Tests bestanden:

public class CutomIdentifierModelBinderProvider : IModelBinderProvider 
{ 
    public IModelBinder GetBinder(ModelBinderProviderContext context) 
    { 
     if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[])) 
     { 
      return new CustomArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder()); 
     } 

     return null; 
    } 
} 

public class CustomArrayModelBinder<T> : IModelBinder 
{ 
    private readonly ArrayModelBinder<T> innerModelBinder; 

    public CustomArrayModelBinder(IModelBinder elemeBinder) 
    { 
     innerModelBinder = new ArrayModelBinder<T>(elemeBinder); 
    } 

    public Task BindModelAsync(ModelBindingContext bindingContext) 
    { 
     var attemptedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString(); 

     if (String.IsNullOrEmpty(attemptedValue)) 
     { 
      bindingContext.Model = new T[0]; 
      bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); 
      return Task.CompletedTask; 
     } 

     return innerModelBinder.BindModelAsync(bindingContext); 
    } 
} 
+0

Vielen Dank für Ihre Antwort:

 var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == ValueProviderResult.None) 

Und Sie auch die mitgelieferte ArrayModelBinder von T mit Ihrem eigenen Modelbinder registrieren. Ich konnte nicht glauben, dass du immer wieder einen anderen schreiben musst ... ArrayModelBinder, also musste ich mir den Quellcode von MVC nochmal ansehen. Die in meiner Antwort erwähnte Änderung scheint das Problem in meinem Fall zu lösen. Weiß noch nicht, ob es irgendein Szenario löst. Nochmals vielen Dank, Ihre Antwort hat mich in die richtige Richtung gedrängt. – MarcusK

0

Die Lösung wird der folgende Code zu ändern, in diesem reflektiert begehen: https://github.com/MarcusKohnert/OptionalArrayModelBinding/commit/552f4d35d8c33c002e1aa0c05acb407f1f962102

ich die Lösung gefunden habe, durch Inspektion MVCs Quellcode erneut. https://github.com/aspnet/Mvc/blob/35601f95b345d0ef938fb21ce1c51f5a67a1fb62/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs#L37

Sie müssen das valueProviderResult auf None prüfen. Wenn es keine gibt, gibt es keinen Parameter und der ModelBinder bindet korrekt.

 if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[])) 
     { 
      return new ArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder()); 
     } 
Verwandte Themen