5

この質問は、MVC3 のコンテキストで SO および他の場所で以前に尋ねられました。ASP.NET Core RC1 および RC2 に関連するビットとボブがありますが、MVC 6 で正しい方法でそれを行う方法を実際に示す単一の例はありません。 .

以下のクラスがあります

 public abstract class BankAccountTransactionModel {

    public long Id { get; set; }
    public DateTime Date { get; set; }
    public decimal Amount { get; set; }
    public readonly string ModelType;
    public BankAccountTransactionModel(string modelType) {
        this.ModelType = modelType;
    }
 }

 public class BankAccountTransactionModel1 : BankAccountTransactionModel{

    public bool IsPending { get; set; }

    public BankAccountTransactionModel1():
            base(nameof(BankAccountTransactionModel1)) {}
 }    

 public class BankAccountTransactionModel2 : BankAccountTransactionModel{

    public bool IsPending { get; set; }

    public BankAccountTransactionModel2():
            base(nameof(BankAccountTransactionModel2)) {}
 }

私のコントローラーには、このようなものがあります

[Route(".../api/[controller]")]  
public class BankAccountTransactionsController : ApiBaseController 
{

 [HttpPost]
 public IActionResult Post(BankAccountTransactionModel model) {

        try {
            if (model == null || !ModelState.IsValid) {
                // failed to bind the model
                return BadRequest(ModelState);
            }

            this.bankAccountTransactionRepository.SaveTransaction(model);

            return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
        } catch (Exception e) {

            this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
            return StatusCode(500);
        }
    }

}        

私のクライアントは BankAccountTransactionModel1 または BankAccountTransactionModel2 のいずれかを投稿する可能性があり、カスタム モデル バインダーを使用して、抽象基本クラス BankAccountTransactionModel で定義されているプロパティ ModelType の値に基づいてバインドする具体的なモデルを決定したいと考えています。

したがって、私は次のことを行いました

1) タイプが BankAccountTransactionModel であることを確認する単純な Model Binder Provider をコーディングしました。この場合、BankAccountTransactionModelBinder のインスタンスが返されます。

public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {

    public IModelBinder GetBinder(ModelBinderProviderContext context) {

        if (context == null) throw new ArgumentNullException(nameof(context));

        if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {     

            var type1 = context.Metadata.ModelType;
            var type2 = typeof(BankAccountTransactionModel);

            // some other code here?

            // tried this but not sure what to do with it!
            foreach (var property in context.Metadata.Properties) {
                propertyBinders.Add(property, context.CreateBinder(property));
            }

            if (type1 == type2) {
                return new BankAccountTransactionModelBinder(propertyBinders);
            }
        }

        return null;
    }
}

2) BankAccountTransactionModel のコード化

 public class BankAccountTransactionModelBinder : IModelBinder {


 private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;

 public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
                this._propertyBinders = propertyBinders;
                  }
 public Task BindModelAsync(ModelBindingContext bindingContext) {

        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));            

        // I would like to be able to read the value of the property
        // ModelType like this or in some way...
        // This does not work and typeValue is...
        var typeValue = bindingContext.ValueProvider.GetValue("ModelType");

        // then once I know whether it is a Model1 or Model2 I would like to
        // instantiate one and get the values from the body of the Http  
        // request into the properties of the instance  

        var model = Activator.CreateInstance(type);

        // read the body of the request in some way and set the 
        // properties of model                                                                                                           

        var key = some key? 
        var result = ModelBindingResult.Success(key, model);

        // Job done  
        return Task.FromResult(result);
    }

 }

3) 最後に、プロバイダーを Startup.cs に登録します。

public void ConfigureServices(IServiceCollection services)
    {       
        services.AddMvc(options => {
            options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
            options.Filters.Add(typeof (SetUserContextAttribute));
        });

プロバイダーが実際に呼び出され、モデル ビルダーの場合も同じであるという点で、すべて問題ないように見えます。ただし、モデル バインダーの BindModelAsync でロジックをコーディングしても、どこにも到達できないようです。

コード内のコメントで既に述べたように、モデル バインダーでやりたいことは、http 要求の本文、特に JSON の ModelType の値を読み取ることだけです。次に、これに基づいて、BankAccountTransactionModel1 または BankAccountTransactionModel のいずれかをインスタンス化し、最後に本文の JSON を読み取って、このインスタンスのプロパティに値を割り当てます。

これはどのように行うべきかの大まかな概算に過ぎないことは承知していますが、何らかの助けと、おそらくこれがどのように行われたか、または行われたかの例をいただければ幸いです。

ModelBinder の以下のコード行の例に出くわしました

var typeValue = bindingContext.ValueProvider.GetValue("ModelType");

値を読み取ることになっています。ただし、私のモデルバインダーでは機能せず、typeValue は常に以下のようなものです

typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable

私も気づいたことがあります

bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}

それはおそらく、このままでは体から何かを読み取る機会がないことを意味します.

望ましい結果を得るには、おそらくミックスに「フォーマッター」が必要ですか?

同様のカスタム モデル バインダーの参照実装が既にどこかに存在するので、おそらくいくつかの単純な mod で簡単に使用できますか?

ありがとうございました。

4

0 に答える 0