2017-12-04 11 views
0

ASP.NET Core Controllerでオプションの配列をバインドすることができません。配列には、カスタムタイプの要素が含まれています。このタイプの単一要素は、カスタムモデルバインダーでバインドされ、その中で検証されます。ここカスタムモデルバインドタイプのオプションの配列をバインドするモデル

サンプルレポ: 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); 
    } 
} 

コントローラー:

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

スタートアップ:

https://github.com/MarcusKohnert/OptionalArrayModelBinding

私はサンプルテストプロジェクトでの作業3のうちの2つだけのテストを取得します

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

TのMVCのデフォルトのArrayModelBinderが正しく、オプションの配列を結合し、trueにModelState.IsValidを設定します。独自のCustomIdentifierModelBinderを使用する場合、ModelState.IsValidはfalseになります。空の配列は有効と認識されません。

どうすればこの問題を解決できますか?前もって感謝します。

答えて

0

あなたは非常に近いです。パラメータがない場合は、組み込みのArrayModelBinderの動作をカスタマイズするだけです。抽出された値が空の文字列の場合は、モデルを空の配列で埋めるだけです。それ以外の場合は、通常ArrayModelBinderと呼ぶことができます。ここで

は、すべての3つの試験に合格するワーキングサンプルです:

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

感謝を。私はあなたが常に別のものを書く必要があるとは思えませんでした... ArrayModelBinder、MVCのソースコードをもう一度見ました。私の答えに言及された変更は私の場合の問題を解決するようです。それが何らかのシナリオを解決するかどうかはまだ分かりません。お返事ありがとうございます、あなたの返信は私を正しい方向に押し込んでくれました。 – MarcusK

0

ソリューションは、次のコード変更で、コミット本に反映: https://github.com/MarcusKohnert/OptionalArrayModelBinding/commit/552f4d35d8c33c002e1aa0c05acb407f1f962102

私は検査することによって解決策を見つけましたMVCのソースコードをもう一度。 https://github.com/aspnet/Mvc/blob/35601f95b345d0ef938fb21ce1c51f5a67a1fb62/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs#L37

NoneのvalueProviderResultをチェックする必要があります。それがnoneの場合、パラメータは指定されず、ModelBinderは正しくバインドされます。

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

そしてまた、あなたはあなたのカスタムModelBinderとTの提供ArrayModelBinderを登録します。ご返信用

 if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[])) 
     { 
      return new ArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder()); 
     } 
関連する問題