Associazione tardiva Risolve dinamicamente i modelli dopo aver inserito il controller


9

Sto cercando un modo per risolvere un modello dopo essere entrato in un'azione in un controller, il modo più semplice per descrivere il problema sarebbe:

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

Se stai cercando maggiori informazioni sul motivo per cui sto cercando di farlo, puoi continuare a leggere per ottenere il quadro completo

TL; DR

Sto cercando un modo per risolvere un modello di una richiesta, dato un nome di parametro che sarà sempre risolto dalla stringa di query Come posso registrare dinamicamente i filtri dall'avvio. Ho una classe che gestirà la registrazione dei miei filtri.

Nella mia classe di avvio voglio essere in grado di registrare dinamicamente i filtri con i miei servizi rest. Ho un'opzione che sto usando per passare al mio ControllerFeatureProvider personalizzato che assomiglia più o meno così:

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

Il mio controller terrà traccia delle opzioni e le utilizzerà per fornire filtri per endpoint di paginazione e 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>();
    }
}

Sto avendo problemi a capire come risolvere dinamicamente un modello dato HttpContext, penso di fare qualcosa del genere per ottenere il modello ma questo è pseudo-codice che non funziona

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

Dopo aver scavato nella sorgente, ho visto alcune cose promettenti ModelBinderFactory e ControllerActionInvoker Queste classi sono utilizzate nella pipeline per l'associazione dei modelli,

Mi aspetto che esponga una semplice interfaccia per risolvere un nome di parametro da QueryString, qualcosa del genere:

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

Tuttavia, l'unico modo in cui vedo per risolvere un modello dal raccoglitore di modelli è creare falsi descrittori di controller e deridere un sacco di cose.

Come posso accettare parametri associati in ritardo nel mio controller?


2
Non vedo un uso per questo. Inoltre, anche se è possibile associare il modello in base a un parametro stringa .... non si sarebbe in grado di utilizzare un metodo generico come GetValueFor <T> perché T deve essere risolto in fase di compilazione .... ciò significa che il chiamante deve sapere il tipo di T in fase di compilazione che vanificherebbe lo scopo di legare dinamicamente il tipo. Ciò significa che l'erede di DynamicControllerBase deve conoscere il tipo di TDTO .... Una cosa che puoi provare è ricevere JSON nel parametro e avere ogni implementazione di DynamicControllerBase deserializza la stringa in un modello e viceversa.
Jonathan Alfaro,

@Darkonekt se guardi il metodo 'AddFilter', hai i parametri generici digitati che vengono archiviati in una chiusura quando registri le funzioni. È un po 'confuso ma ti assicuro che è fattibile e può funzionare
johnny 5

Non voglio collegarmi a json, perché non voglio cambiare il modo in cui webapi risolva naturalmente i parametri
johnny 5

Se spiegassi un po 'di più sul caso d'uso e sullo scenario di vita reale in cui questo tipo di funzionalità è necessaria, ciò aiuterebbe molto. Probabilmente c'è anche una soluzione più semplice disponibile ... chissà. Per quanto mi riguarda, a volte mi piace complicare le cose .. Dico solo ..
Mr. Blond,

@ Mr.Blond Ho un servizio di riposo generico che fornisce funzionalità roud e get list. A volte i miei servizi devono filtrare i dati dall'elenco get, ma non voglio scrivere un intero servizio di tutto ciò di cui ho bisogno per fornire un filtro
johnny 5

Risposte:


2

Sono d'accordo con il tuo pensiero

i servizi devono filtrare i dati dall'elenco get, ma non voglio scrivere un intero servizio di tutto ciò che serve per fornire un filtro

Perché scrivere un widget / filtro / endpoint per ogni possibile combinazione?

Basta fornire le operazioni di base per ottenere tutti i dati / proprietà. Quindi utilizzare GraphQL per consentire all'utente finale di filtrarlo ( modello ) in base alle proprie esigenze.

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


Ho preso in considerazione l'utilizzo di GraphQL ma sono troppo legato alla mia attuale implementazione
johnny 5

2

Abbiamo fatto questo, il nostro codice fa riferimento a questo sito: https://prideparrot.com/blog/archive/2012/6/gotchas_in_explicit_model_binding

In particolare, guardando il nostro codice, il trucco è accettare un FormCollection nel metodo del controller e quindi utilizzare il raccoglitore, il modello e i dati del modulo:

Esempio preso dal collegamento:

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

(Nota: il sito sembra essere inattivo, il collegamento è a archive.org)


Grazie per il vostro aiuto, Attualmente, non sto usando MVC, ad es. (Possono esserci più di 1 parametro di input) Devo risolvere i parametri con il nome. Inoltre sto usando .Net-Core Penso che questo sia scritto per la versione precedente .net. Completa il metodo stub: affinché la risposta sia accettata:this.Resolve<MyCustomType>("MyParamName");
johnny 5

Dal momento che non ho un ambiente per riprodurlo minimamente, non sarò in grado di farlo - mi scuso per aver mancato il requisito dotnetcore.
Brian dice di reintegrare Monica il

Posso tradurlo in .Net-Core va bene, se mi mostri come risolvere con il nome del parametro accetterò
johnny 5

Questa è la cosa più vicina alla risposta che volevo, quindi ti darò la generosità
johnny 5

0

Ho finito per scrivere controller dinamici. Per risolvere il problema come soluzione.

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

Sto codificando il metodo nel metodo per ora, ma sono sicuro che puoi capire come trasmetterlo, se necessario.

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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.