web-dev-qa-db-fra.com

Liaison tardive des modèles à résolution dynamique après entrée dans le contrôleur

Je cherche un moyen de résoudre un modèle après avoir entré une action dans un contrôleur, la manière la plus simple de décrire le problème serait:

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

Si vous cherchez plus d'informations sur les raisons pour lesquelles j'essaie de le faire, vous pouvez continuer à lire pour obtenir l'image complète

TL; DR

Je cherche un moyen de résoudre un modèle d'une demande, étant donné un nom de paramètre qui sera toujours résolu à partir de la chaîne de requête Comment puis-je enregistrer dynamiquement des filtres à partir du démarrage. J'ai une classe qui va gérer l'enregistrement de mes filtres.

Dans ma classe de démarrage, je veux pouvoir enregistrer dynamiquement des filtres avec mes restServices. J'ai une option que j'utilise pour passer à mon ControllerFeatureProvider personnalisé qui ressemble à peu près à ceci:

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

Mon contrôleur gardera une trace des options et les utilisera pour fournir des filtres pour les points finaux de pagination et 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>();
    }
}

J'ai du mal à trouver comment résoudre dynamiquement un modèle étant donné le HttpContext, je penserais à faire quelque chose comme ça pour obtenir le modèle mais c'est un pseudo-code qui ne fonctionne pas

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

Après avoir creusé dans la source, j'ai vu des choses prometteuses ModelBinderFactory et ControllerActionInvoker Ces classes sont utilisées dans le pipeline pour la liaison de modèle,

Je m'attendrais à ce que l'expose une interface simple pour résoudre un nom de paramètre à partir de QueryString, quelque chose comme ceci:

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

Cependant, la seule façon que je vois pour résoudre un modèle à partir du classeur de modèles est de créer de faux descripteurs de contrôleur et de se moquer d'une tonne de choses.

Comment puis-je accepter des paramètres liés tardivement dans mon contrôleur?

9
johnny 5

Nous l'avons fait, notre code fait référence à ce site: https://prideparrot.com/blog/archive/2012/6/gotchas_in_explicit_model_binding

Plus précisément, en examinant notre code, qu'est-ce que l'astuce consiste à accepter une FormCollection dans votre méthode de contrôleur, puis à utiliser le classeur de modèle, le modèle et les données de formulaire:

Exemple tiré du lien:

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

(Remarque: le site semble être en panne, le lien est vers archive.org)

Je suis d'accord avec votre idée

les services doivent filtrer les données de la liste d'obtention, mais je ne veux pas avoir à écrire un service complet de tout ce dont j'ai besoin pour fournir un filtre

Pourquoi écrire un widget/filtre/point de terminaison pour chaque combinaison possible?

Fournissez simplement des opérations de base pour obtenir toutes les données/propriétés. Utilisez ensuite GraphQL pour permettre à l'utilisateur final de le filtrer ( modèle) pour ses besoins.

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

2
ΩmegaMan

J'ai fini par écrire des contrôleurs dynamiques. Pour résoudre le problème en guise de solution.

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

Je suis en train de coder durement la fonction dans la méthode, mais je suis sûr que vous pouvez comprendre comment la transmettre si vous en avez besoin.

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