モデル バインドを使用すると、コントローラー アクションは HTTP 要求ではなく、モデル型 (メソッド引数として渡される) を直接操作できます。 受信要求データとアプリケーション モデル間のマッピングは、モデル バインダーによって処理されます。 開発者は、カスタム モデル バインダーを実装することで、組み込みのモデル バインド機能を拡張できます (ただし、通常、独自のプロバイダーを記述する必要はありません)。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
既定のモデル バインダーの制限事項
既定のモデル バインダーは、ほとんどの一般的な .NET Core データ型をサポートしており、ほとんどの開発者のニーズを満たす必要があります。 要求からのテキストベースの入力をモデル型に直接バインドすることが想定されています。 バインドする前に入力の変換が必要になる場合があります。 たとえば、モデル データの検索に使用できるキーがある場合などです。 カスタム モデル バインダーを使用して、キーに基づいてデータをフェッチできます。
モデル バインドの単純型と複合型
モデル バインドは、操作の対象とする型に特定の定義を使用します。
単純型は、TypeConverter または TryParse
メソッドを使用して、1 つの文字列から変換されます。
複合型は、複数の入力値から変換されます。 フレームワークは、TypeConverter
または TryParse
の存在の有無によって違いを判断します。 外部リソースや複数の入力を必要としない TryParse
から string
への変換には、型コンバーターを作成するか、SomeType
を使用することをお勧めします。
モデル バインダーが文字列から変換できる型の一覧については、「 単純 型」を参照してください。
独自のカスタム モデル バインダーを作成する前に、既存のモデル バインダーの実装方法を確認する必要があります。 base64 でエンコードされた文字列をバイト配列に変換するために使用できる ByteArrayModelBinder について考えてみましょう。 バイト配列は、多くの場合、ファイルまたはデータベース BLOB フィールドとして格納されます。
ByteArrayModelBinder の操作
Base64 でエンコードされた文字列を使用して、バイナリ データを表すことができます。 たとえば、画像は文字列としてエンコードできます。 このサンプルには、base64 でエンコードされた文字列としての画像 がBase64String.txtに含まれています。
ASP.NET Core MVC では、base64 でエンコードされた文字列を受け取り、 ByteArrayModelBinder
を使用してバイト配列に変換できます。
ByteArrayModelBinderProviderは、byte[]
引数をByteArrayModelBinder
にマップします。
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ByteArrayModelBinder(loggerFactory);
}
return null;
}
独自のカスタム モデル バインダーを作成するときは、独自の IModelBinderProvider
型を実装することも、 ModelBinderAttributeを使用することもできます。
次の例は、 ByteArrayModelBinder
を使用して base64 でエンコードされた文字列を byte[]
に変換し、結果をファイルに保存する方法を示しています。
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。
curl などのツールを使用して、base64 でエンコードされた文字列を前の api メソッドに POST できます。
バインダーが要求データを適切な名前のプロパティまたは引数にバインドできる限り、モデル のバインドは成功します。 次の例は、ビュー モデルで ByteArrayModelBinder
を使用する方法を示しています。
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
カスタム モデル バインダーのサンプル
このセクションでは、次のカスタム モデル バインダーを実装します。
- 受信要求データを厳密に型指定されたキー引数に変換します。
- Entity Framework Core を使用して、関連付けられているエンティティをフェッチします。
- 関連付けられたエンティティを引数としてアクション メソッドに渡します。
次の例では、ModelBinder
モデルで Author
属性を使用します。
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
前のコードでは、 ModelBinder
属性は、アクション パラメーターのバインドに使用する IModelBinder
の種類 Author
指定します。
次の AuthorEntityBinder
クラスは、Entity Framework Core とAuthor
を使用してデータ ソースからエンティティをフェッチすることによって、authorId
パラメーターをバインドします。
public class AuthorEntityBinder : IModelBinder
{
private readonly AuthorContext _context;
public AuthorEntityBinder(AuthorContext context)
{
_context = context;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _context.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Note
上記の AuthorEntityBinder
クラスは、カスタム モデル バインダーを示すことを目的としています。 このクラスは、検索シナリオのベスト プラクティスを示すものではありません。 検索の場合は、 authorId
をバインドし、アクション メソッドでデータベースにクエリを実行します。 この方法では、モデル バインドエラーを NotFound
ケースから分離します。
次のコードは、アクション メソッドで AuthorEntityBinder
を使用する方法を示しています。
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
ModelBinder
属性を使用して、既定の規則を使用しないパラメーターにAuthorEntityBinder
を適用できます。
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
この例では、引数の名前が既定の authorId
ではないので、 ModelBinder
属性を使用してパラメーターに指定します。 コントローラーメソッドとアクションメソッドの両方が、アクションメソッドでエンティティを調べるのと比較して簡略化されています。 Entity Framework Core を使用して著者を取得するロジックは、モデルバインダーに移行されました。 これは、 Author
モデルにバインドするいくつかのメソッドがある場合に、大幅に簡略化される可能性があります。
ModelBinder
属性は、個々のモデル プロパティ (ビューモデルなど) またはアクション メソッドパラメーターに適用して、その型またはアクションのみに対して特定のモデル バインダーまたはモデル名を指定できます。
ModelBinderProvider の実装
属性を適用する代わりに、 IModelBinderProvider
を実装できます。 これにより、組み込みのフレームワーク バインダーが実装されます。 バインダーが操作する型を指定する場合は、バインダーが受け入れる入力 ではなく 、生成される引数の型を指定します。 次のバインダー プロバイダーは、 AuthorEntityBinder
で動作します。 MVC のプロバイダーのコレクションに追加されるときに、ModelBinder
またはAuthor
型指定されたパラメーターでAuthor
属性を使用する必要はありません。
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
注: 上記のコードは
BinderTypeModelBinder
を返します。BinderTypeModelBinder
は、モデル バインダーのファクトリとして機能し、依存関係の挿入 (DI) を提供します。AuthorEntityBinder
では、DI がEF Coreにアクセスする必要があります。 モデル バインダーに DI からのサービスが必要な場合は、BinderTypeModelBinder
を使用します。
カスタム モデル バインダー プロバイダーを使用するには、 ConfigureServices
に追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
モデル バインダーを評価するときに、プロバイダーのコレクションが順番に調べられます。 入力モデルに一致するバインダーを返す最初のプロバイダーが使用されます。 そのため、プロバイダーをコレクションの末尾に追加すると、カスタム バインダーが実行される前に組み込みモデル バインダーが先に呼び出される可能性があります。 この例では、カスタム プロバイダーがコレクションの先頭に追加され、アクション引数 Author
常に使用されます。
ポリモーフィック モデル バインド
派生型のさまざまなモデルへのバインドは、ポリモーフィック モデル バインドと呼ばれます。 要求値を特定の派生モデル型にバインドする必要がある場合は、ポリモーフィックなカスタム モデル バインドが必要です。 ポリモーフィック モデル バインド:
- すべての言語と相互運用するように設計された REST API では一般的ではありません。
- バインドされたモデルについて推論するのが困難になります。
ただし、アプリでポリモーフィックなモデル バインドが必要な場合、実装は次のコードのようになります。
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
推奨事項とベスト プラクティス
カスタム モデル バインダー:
- 状態コードを設定したり、結果を返したりしないでください (例: 404 Not Found)。 モデル バインドが失敗した場合、 アクション メソッド自体のアクション フィルター またはロジックでエラーを処理する必要があります。
- 繰り返しコードや横断的な問題をアクション メソッドから排除するのに最も役立ちます。
- 通常、文字列をカスタム型に変換するために使用しないでください。通常は、 TypeConverter を使用することをお勧めします。
作成者: Steve Smith
モデル バインドを使用すると、コントローラー アクションは HTTP 要求ではなく、モデル型 (メソッド引数として渡される) を直接操作できます。 受信要求データとアプリケーション モデル間のマッピングは、モデル バインダーによって処理されます。 開発者は、カスタム モデル バインダーを実装することで、組み込みのモデル バインド機能を拡張できます (ただし、通常、独自のプロバイダーを記述する必要はありません)。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
既定のモデル バインダーの制限事項
既定のモデル バインダーは、ほとんどの一般的な .NET Core データ型をサポートしており、ほとんどの開発者のニーズを満たす必要があります。 要求からのテキストベースの入力をモデル型に直接バインドすることが想定されています。 バインドする前に入力の変換が必要になる場合があります。 たとえば、モデル データの検索に使用できるキーがある場合などです。 カスタム モデル バインダーを使用して、キーに基づいてデータをフェッチできます。
モデル バインディング評価
モデル バインドは、操作の対象とする型に特定の定義を使用します。
単純型は、入力内の 1 つの文字列から変換されます。
複合型は、複数の入力値から変換されます。 フレームワークは、 TypeConverter
の存在に基づいて違いを決定します。 外部リソースを必要としない単純な string
->SomeType
マッピングがある場合は、型コンバーターを作成することをお勧めします。
独自のカスタム モデル バインダーを作成する前に、既存のモデル バインダーの実装方法を確認する必要があります。 base64 でエンコードされた文字列をバイト配列に変換するために使用できる ByteArrayModelBinder について考えてみましょう。 バイト配列は、多くの場合、ファイルまたはデータベース BLOB フィールドとして格納されます。
ByteArrayModelBinder の操作
Base64 でエンコードされた文字列を使用して、バイナリ データを表すことができます。 たとえば、画像は文字列としてエンコードできます。 このサンプルには、base64 でエンコードされた文字列としての画像 がBase64String.txtに含まれています。
ASP.NET Core MVC では、base64 でエンコードされた文字列を受け取り、 ByteArrayModelBinder
を使用してバイト配列に変換できます。
ByteArrayModelBinderProviderは、byte[]
引数をByteArrayModelBinder
にマップします。
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}
return null;
}
独自のカスタム モデル バインダーを作成するときは、独自の IModelBinderProvider
型を実装することも、 ModelBinderAttributeを使用することもできます。
次の例は、 ByteArrayModelBinder
を使用して base64 でエンコードされた文字列を byte[]
に変換し、結果をファイルに保存する方法を示しています。
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
curl などのツールを使用して、base64 でエンコードされた文字列を前の api メソッドに POST できます。
バインダーが要求データを適切な名前のプロパティまたは引数にバインドできる限り、モデル のバインドは成功します。 次の例は、ビュー モデルで ByteArrayModelBinder
を使用する方法を示しています。
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
カスタム モデル バインダーのサンプル
このセクションでは、次のカスタム モデル バインダーを実装します。
- 受信要求データを厳密に型指定されたキー引数に変換します。
- Entity Framework Core を使用して、関連付けられているエンティティをフェッチします。
- 関連付けられたエンティティを引数としてアクション メソッドに渡します。
次の例では、ModelBinder
モデルで Author
属性を使用します。
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
前のコードでは、 ModelBinder
属性は、アクション パラメーターのバインドに使用する IModelBinder
の種類 Author
指定します。
次の AuthorEntityBinder
クラスは、Entity Framework Core とAuthor
を使用してデータ ソースからエンティティをフェッチすることによって、authorId
パラメーターをバインドします。
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Note
上記の AuthorEntityBinder
クラスは、カスタム モデル バインダーを示すことを目的としています。 このクラスは、検索シナリオのベスト プラクティスを示すものではありません。 検索の場合は、 authorId
をバインドし、アクション メソッドでデータベースにクエリを実行します。 この方法では、モデル バインドエラーを NotFound
ケースから分離します。
次のコードは、アクション メソッドで AuthorEntityBinder
を使用する方法を示しています。
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
ModelBinder
属性を使用して、既定の規則を使用しないパラメーターにAuthorEntityBinder
を適用できます。
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
この例では、引数の名前が既定の authorId
ではないので、 ModelBinder
属性を使用してパラメーターに指定します。 コントローラーメソッドとアクションメソッドの両方が、アクションメソッドでエンティティを調べるのと比較して簡略化されています。 Entity Framework Core を使用して著者を取得するロジックは、モデルバインダーに移行されました。 これは、 Author
モデルにバインドするいくつかのメソッドがある場合に、大幅に簡略化される可能性があります。
ModelBinder
属性は、個々のモデル プロパティ (ビューモデルなど) またはアクション メソッドパラメーターに適用して、その型またはアクションのみに対して特定のモデル バインダーまたはモデル名を指定できます。
ModelBinderProvider の実装
属性を適用する代わりに、 IModelBinderProvider
を実装できます。 これにより、組み込みのフレームワーク バインダーが実装されます。 バインダーが操作する型を指定する場合は、バインダーが受け入れる入力 ではなく 、生成される引数の型を指定します。 次のバインダー プロバイダーは、 AuthorEntityBinder
で動作します。 MVC のプロバイダーのコレクションに追加されるときに、ModelBinder
またはAuthor
型指定されたパラメーターでAuthor
属性を使用する必要はありません。
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
注: 上記のコードは
BinderTypeModelBinder
を返します。BinderTypeModelBinder
は、モデル バインダーのファクトリとして機能し、依存関係の挿入 (DI) を提供します。AuthorEntityBinder
では、DI がEF Coreにアクセスする必要があります。 モデル バインダーに DI からのサービスが必要な場合は、BinderTypeModelBinder
を使用します。
カスタム モデル バインダー プロバイダーを使用するには、 ConfigureServices
に追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("App"));
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
モデル バインダーを評価するときに、プロバイダーのコレクションが順番に調べられます。 バインダーを返す最初のプロバイダーが使用されます。 プロバイダーをコレクションの末尾に追加すると、カスタム バインダーが実行される前に組み込みのモデル バインダーが呼び出される可能性があります。 この例では、カスタム プロバイダーがコレクションの先頭に追加されて、Author
アクション引数に使用されるようにします。
ポリモーフィック モデル バインド
派生型のさまざまなモデルへのバインドは、ポリモーフィック モデル バインドと呼ばれます。 要求値を特定の派生モデル型にバインドする必要がある場合は、ポリモーフィックなカスタム モデル バインドが必要です。 ポリモーフィック モデル バインド:
- すべての言語と相互運用するように設計された REST API では一般的ではありません。
- バインドされたモデルについて推論するのが困難になります。
ただし、アプリでポリモーフィックなモデル バインドが必要な場合、実装は次のコードのようになります。
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
推奨事項とベスト プラクティス
カスタム モデル バインダー:
- 状態コードを設定したり、結果を返したりしないでください (例: 404 Not Found)。 モデル バインドが失敗した場合、 アクション メソッド自体のアクション フィルター またはロジックでエラーを処理する必要があります。
- 繰り返しコードや横断的な問題をアクション メソッドから排除するのに最も役立ちます。
- 通常、文字列をカスタム型に変換するために使用しないでください。通常は、 TypeConverter を使用することをお勧めします。
ASP.NET Core