コントローラに入った後のモデルの動的バインディングの遅延バインディング


9

コントローラでアクションを開始した後にモデルを解決する方法を探しています。問題を説明する最も簡単な方法は次のとおりです。

public DTO[] Get(string filterName)
{
    //How can I do this
    this.Resolve<MyCustomType>("MyParamName");
}

あなたがなぜ私がそうしようとしているのかについての詳細を探しているなら、あなたは全体像を取得するために読み続けることができます

TL; DR

クエリ文字列から常に解決されるパラメーター名を指定して、モデルをリクエストを解決する方法を探しています。スタートアップからフィルターを動的に登録するにはどうすればよいですか。フィルターの登録を処理するクラスがあります。

私のスタートアップクラスでは、restServicesにフィルターを動的に登録できるようにしたいと考えています。大体次のようなカスタムControllerFeatureProviderに渡すために使用するオプションがあります。

public class DynamicControllerOptions<TEntity, TDTO>
{
    Dictionary<string, Func<HttpContext, Expression<Func<TEntity, bool>>>> _funcNameToEndpointResolverMap
        = new Dictionary<string, Func<HttpContext, Expression<Func<TEntity, bool>>>>();
    Dictionary<string, List<ParameterOptions>> _filterParamsMap = new Dictionary<string, List<ParameterOptions>>();

    public void AddFilter(string filterName, Expression<Func<TEntity, bool>> filter)
    {
        this._funcNameToEndpointResolverMap.Add(filterName, (httpContext) =>  filter);
    }
    public void AddFilter<T1>(string filterName, Func<T1, Expression<Func<TEntity, bool>>> filterResolver,
        string param1Name = "param1")
    {
        var parameters = new List<ParameterOptions> { new ParameterOptions { Name = param1Name, Type = typeof(T1) } };
        this._filterParamsMap.Add(filterName, parameters);
        this._funcNameToEndpointResolverMap.Add(filterName, (httpContext) => {
            T1 parameter = this.ResolveParameterFromContext<T1>(httpContext, param1Name);
            var filter = filterResolver(parameter);
            return filter;
        });
    }
}

私のコントローラーはオプションを追跡し、それらを使用してページングエンドポイントとODataのフィルターを提供します。

public class DynamicControllerBase<TEntity, TDTO> : ControllerBase
{
    protected DynamicControllerOptions<TEntity, TDTO> _options;
    //...

    public TDTO[] GetList(string filterName = "")
    {
        Expression<Func<TEntity, bool>> filter = 
            this.Options.ResolveFilter(filterName, this.HttpContext);
        var entities = this._context.DbSet<TEntity>().Where(filter).ToList();
        return entities.ToDTO<TDTO>();
    }
}

HttpContextを指定してモデルを動的に解決する方法を理解するのに問題があります。モデルを取得するためにこのようなことを行うと思いますが、これは機能しない疑似コードです

private Task<T> ResolveParameterFromContext<T>(HttpContext httpContext, string parameterName)
{
    //var modelBindingContext = httpContext.ToModelBindingContext();
    //var modelBinder = httpContext.Features.OfType<IModelBinder>().Single();
    //return modelBinder.BindModelAsync<T>(parameterName);
}

ソースを掘り下げた後ModelBinderFactoryControllerActionInvokerのいくつかの有望なものを見ましたこれらのクラスは、モデルバインディングのパイプラインで使用されます。

私は、QueryStringからパラメータ名を解決するために、次のような簡単なインターフェースを公開することを期待します。

ModelBindingContext context = new ModelBindingContext();
return context.GetValueFor<T>("MyParamName");

ただし、モデルバインダーからモデルを解決するために私が見る唯一の方法は、偽のコントローラー記述子を作成し、大量のものを模擬することです。

遅延バインドされたパラメーターをコントローラーに受け入れるにはどうすればよいですか?


2
これの用途はわかりません。さらに、文字列パラメーターに基づいてモデルをバインドできる場合でも、Tはコンパイル時間を解決する必要があるため、GetValueFor <T>のようなジェネリックメソッドを使用することはできません。動的に型をバインドする目的を無効にするコンパイル時のTの型。これは、DynamicControllerBaseの継承者がTDTOのタイプを知っている必要があることを意味します。...試行できることの1つは、パラメーターでJSONを受け取り、DynamicControllerBaseの各実装で文字列をモデルに逆シリアル化し、その逆を行うことです。
ジョナサンアルファロ

@Darkonekt 'AddFilter'メソッドを見ると、型を指定した汎用パラメーターがあり、関数を登録するときにクロージャーに格納されます。少しわかりにくいですが、実行可能で機能することを保証します
ジョニー5

jsonにプラグインしたくないのは、パラメーターをwebapiが自然に解決する方法を変更する必要がないためです
johnny 5

この種の機能が必要なユースケースと実際のシナリオについてもう少し詳しく説明すると、非常に役立ちます。おそらくもっと簡単な解決策もあるでしょう。私自身に関しては、物事を複雑にすることが好きな場合があります。..ただ言って..
ブロンド氏、

@ Mr.Blond私はクラッドとリストの取得機能を提供する一般的なレストサービスを持っています。私のサービスは取得リストからデータをフィルターする必要がある場合がありますが、フィルターを提供するために必要なすべてのサービス全体を記述する必要はありません
johnny 5

回答:


2

あなたの考えに同意します

サービスは取得リストからデータをフィルタリングする必要がありますが、フィルターを提供するために必要なすべてのサービス全体を記述する必要はありません

可能なすべての組み合わせに対してウィジェット/フィルター/エンドポイントを作成する理由

基本的な操作を提供するだけで、すべてのデータ/プロパティを取得できます。次に、GraphQLを使用して、エンドユーザー必要に応じてフィルター(モデル)できるようにます。

GraphQLから

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools


私はGraphQLの使用を検討してきたが、私はあまりにも私の現在の実装に縛られてる
ジョニー5

2

これを実行しました。コードは次のサイトを参照しています:https : //prideparrot.com/blog/archive/2012/6/gotchas_in_explicit_model_binding

具体的には、コードを見て、コントローラーのメソッドでFormCollectionを受け入れ、モデルバインダー、モデル、およびフォームデータを使用するのがコツです。

リンクからの例:

public ActionResult Save(FormCollection form)
{
var empType = Type.GetType("Example.Models.Employee");
var emp = Activator.CreateInstance(empType);

var binder = Binders.GetBinder(empType);

  var bindingContext = new ModelBindingContext()
  {
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => emp, empType),
    ModelState = ModelState,
    ValueProvider = form
  };      

  binder.BindModel(ControllerContext, bindingContext);

  if (ModelState.IsValid)
  {
   _empRepo.Save(emp);

    return RedirectToAction("Index");
  }

return View();
}

(注:サイトはダウンしているようです。リンクはarchive.orgへのリンクです)


ご協力ありがとうございます。現在、私はMVCを使用していません。例(複数の入力パラメーターがある場合があります)名前でパラメーターを解決する必要があります。さらに、私は.Net-Coreを使用しています。これは古いバージョンの.net用に書かれていると思います。メソッドスタブを完了してください:回答を受け入れるには:this.Resolve<MyCustomType>("MyParamName");
johnny 5

私はこれを最小限に再現する環境を持っていないので、それを行うことができません-dotnetcore要件がないことをお詫びします。
ブライアンはモニカを復活させる

私はそれを.Net-Coreに翻訳できます。パラメータ名で解決する方法を教えてくれれば受け入れます
johnny 5

これは私が欲しかった答えに最も近いものなので、賞金を差し上げます
ジョニー5

0

結局、動的コントローラーを作成してしまいました。回避策として問題を解決する。

private static TypeBuilder GetTypeBuilder(string assemblyName)
{
    var assemName = new AssemblyName(assemblyName);
    var assemBuilder = AssemblyBuilder.DefineDynamicAssembly(assemName, AssemblyBuilderAccess.Run);
    // Create a dynamic module in Dynamic Assembly.
    var moduleBuilder = assemBuilder.DefineDynamicModule("DynamicModule");
    var tb = moduleBuilder.DefineType(assemblyName,
            TypeAttributes.Public |
            TypeAttributes.Class |
            TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass |
            TypeAttributes.BeforeFieldInit |
            TypeAttributes.AutoLayout,
            null);

    return tb;
}

今のところ、関数にfuncをハードコーディングしていますが、必要に応じてそれを渡す方法を理解できると確信しています。

public static Type CompileResultType(string typeSignature)
{
    TypeBuilder tb = GetTypeBuilder(typeSignature);

    tb.SetParent(typeof(DynamicControllerBase));

    ConstructorBuilder ctor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

    // For this controller, I only want a Get method to server Get request
    MethodBuilder myGetMethod =
        tb.DefineMethod("Get",
            MethodAttributes.Public,
            typeof(String), new Type[] { typeof(Test), typeof(String) });

    // Define parameters
    var parameterBuilder = myGetMethod.DefineParameter(
        position: 1, // 0 is the return value, 1 is the 1st param, 2 is 2nd, etc.
        attributes: ParameterAttributes.None,
        strParamName: "test"
    );
    var attributeBuilder
        = new CustomAttributeBuilder(typeof(FromServicesAttribute).GetConstructor(Type.EmptyTypes), Type.EmptyTypes);
    parameterBuilder.SetCustomAttribute(attributeBuilder);

    // Define parameters
    myGetMethod.DefineParameter(
        position: 2, // 0 is the return value, 1 is the 1st param, 2 is 2nd, etc.
        attributes: ParameterAttributes.None,
        strParamName: "stringParam"
    );

    // Generate IL for method.
    ILGenerator myMethodIL = myGetMethod.GetILGenerator();
    Func<string, string> method = (v) => "Poop";

    Func<Test, string, string> method1 = (v, s) => v.Name + s;

    myMethodIL.Emit(OpCodes.Jmp, method1.Method);
    myMethodIL.Emit(OpCodes.Ret);

    return tb.CreateType();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.