Terlambat mengikat Model Penyelesaian Dinamis setelah memasuki pengontrol

9

Saya sedang mencari cara untuk menyelesaikan model setelah masuk ke dalam aksi di controller, cara paling sederhana untuk menggambarkan masalah adalah:

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

Jika Anda mencari informasi lebih lanjut tentang mengapa saya mencoba melakukan itu, Anda dapat terus membaca untuk mendapatkan gambaran lengkap

TL; DR

Saya sedang mencari cara untuk menyelesaikan permintaan model, mengingat nama parameter yang akan selalu diselesaikan dari string kueri Bagaimana saya bisa mendaftarkan filter secara dinamis dari startup. Saya memiliki kelas yang akan menangani pendaftaran filter saya.

Di kelas startup saya, saya ingin dapat mendaftarkan filter secara dinamis dengan restServices saya. Saya memiliki opsi yang saya gunakan untuk meneruskan ke ControllerFeatureProvider kustom saya yang kira-kira seperti ini:

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

Kontroler saya akan melacak opsi dan menggunakannya untuk menyediakan filter untuk paging titik akhir dan 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>();
    }
}

Saya mengalami kesulitan mencari tahu bagaimana menyelesaikan model secara dinamis mengingat HttpContext, saya akan berpikir untuk melakukan sesuatu seperti ini untuk mendapatkan model tetapi ini adalah pseudo-code yang tidak berfungsi

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

Setelah Menggali Sumber, saya melihat beberapa hal yang menjanjikan ModelBinderFactory dan ControllerActionInvoker Kelas-kelas ini digunakan dalam pipa untuk model mengikat,

Saya berharap untuk mengekspos antarmuka sederhana untuk menyelesaikan nama parameter dari QueryString, sesuatu seperti ini:

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

Namun, satu-satunya cara saya melihat untuk menyelesaikan model dari pengikat model adalah membuat deskriptor pengendali palsu dan mengejek banyak hal.

Bagaimana saya bisa menerima parameter terikat terlambat ke contoller saya?

johnny 5
sumber
2
Saya tidak melihat ada gunanya untuk ini. Lebih jauh lagi bahkan jika Anda dapat mengikat model berdasarkan parameter string .... Anda tidak akan dapat menggunakan metode generik seperti GetValueFor <T> karena T harus diselesaikan waktu kompilasi .... ini berarti pemanggil harus tahu tipe T pada waktu kompilasi yang akan mengalahkan tujuan pengikatan tipe secara dinamis. Ini berarti bahwa pewaris DynamicControllerBase harus mengetahui jenis TDTO .... Satu hal yang dapat Anda coba adalah menerima JSON dalam parameter dan meminta setiap implementasi DynamicControllerBase membatalkan deserialisasi string ke model dan sebaliknya.
Jonathan Alfaro
@Arkonekt jika Anda melihat metode 'AddFilter', Anda memiliki parameter generik yang diketik yang bisa disimpan dalam penutupan ketika Anda mendaftarkan funcs. Agak membingungkan tapi saya yakinkan Anda itu layak dan bisa bekerja
johnny 5
Saya tidak ingin menghubungkan ke json, karena saya tidak ingin harus mengubah cara untuk webapi secara alami menyelesaikan parameter
johnny 5
Jika Anda akan menjelaskan sedikit lebih banyak tentang kasus penggunaan dan skenario kehidupan nyata di mana fungsi semacam ini diperlukan, itu akan banyak membantu. Mungkin bahkan ada solusi sederhana yang tersedia .. siapa tahu. Sedangkan untuk diri saya sendiri, saya kadang suka menyulitkan hal-hal .. Hanya mengatakan ..
Tn. Blond
@ Mr.Blond Saya memiliki layanan istirahat generik yang menyediakan fungsi kasar dan mendapatkan daftar. Terkadang layanan saya perlu memfilter data dari daftar get, tetapi saya tidak ingin harus menulis seluruh layanan dari semua yang saya perlukan untuk menyediakan filter
johnny 5

Jawaban:

2

Saya setuju dengan pemikiran Anda

layanan perlu memfilter data dari daftar get, tetapi saya tidak ingin harus menulis seluruh layanan dari semua yang saya perlukan untuk menyediakan filter

Mengapa menulis widget / filter / endpoint untuk setiap kombinasi yang memungkinkan?

Cukup sediakan operasi dasar untuk mendapatkan semua data / properti. Kemudian gunakan GraphQL untuk memungkinkan pengguna akhir memfilter ( model ) sesuai kebutuhan mereka .

Dari 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.

GamegaMan
sumber
Saya telah mempertimbangkan menggunakan GraphQL tapi saya terlalu terikat dengan implementasi saya saat ini
johnny 5
2

Kami telah melakukan ini, kode kami merujuk situs ini: https://prideparrot.com/blog/archive/2012/6/gotchas_in_explicit_model_binding

Secara khusus, melihat kode kami, triknya adalah menerima FormCollection dalam metode pengontrol Anda dan kemudian menggunakan pengikat model, model, dan formulir data:

Contoh diambil dari tautan:

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

(Catatan: situs tampaknya down, tautannya adalah ke archive.org)

Brian berkata Reinstate Monica
sumber
Terima kasih atas bantuan Anda, Saat ini, saya tidak menggunakan MVC, misalnya (Mungkin ada lebih dari 1 parameter input) Saya perlu menyelesaikan parameter dengan nama. Selain itu saya menggunakan Net-Core. Saya pikir ini ditulis untuk versi yang lebih lama. Net. Silakan lengkapi metode rintisan: untuk jawaban yang akan diterima:this.Resolve<MyCustomType>("MyParamName");
johnny 5
Karena saya tidak memiliki lingkungan untuk mereproduksi ini secara minimal, saya tidak akan bisa melakukan itu - saya minta maaf karena melewatkan persyaratan dotnetcore.
Brian berkata Reinstate Monica
Saya dapat menerjemahkannya ke .Net-Core tidak apa-apa, Jika Anda menunjukkan kepada saya bagaimana menyelesaikannya dengan nama parameter, saya akan menerimanya
johnny 5
Ini adalah hal yang paling dekat dengan jawaban yang saya inginkan jadi saya akan memberi Anda hadiah
johnny 5
0

Saya akhirnya menulis pengendali dinamis. Untuk mengatasi masalah tersebut sebagai pekerjaan di sekitar.

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

Saya sulit mengkodekan func dalam metode untuk saat ini, tetapi saya yakin Anda bisa mencari cara untuk meneruskannya jika Anda perlu.

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();
}
johnny 5
sumber