代码之家  ›  专栏  ›  技术社区  ›  johnny 5

从HttpContext动态解析模型

  •  0
  • johnny 5  · 技术社区  · 6 年前

    我正在寻找一种在控制器中输入操作后解决模型的方法,描述问题的最简单方法是:

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

    如果你想了解更多关于我为什么要这么做的信息,你可以继续阅读以了解完整情况

    TL;博士

    我正在寻找一种方法来解析模型a请求,给定一个参数名,该参数名将始终从查询字符串解析。如何从启动时动态注册过滤器。我有一个类将处理注册我的过滤器。

    在我的startup类中,我希望能够在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);
    }
    

    在深入了解消息来源后,我看到了一些有希望的事情 ModelBinderFactory 还有 ControllerActionInvoker 这些类用于管道中的绑定,

    我希望公开一个简单的接口来解析QueryString中的参数名,如下所示:

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

    然而,我看到的从model binder解析模型的唯一方法是创建假控制器描述符并模拟大量内容。

    我如何将延迟绑定的参数接受到我的控制器中?

    0 回复  |  直到 6 年前
        1
  •  2
  •   ΩmegaMan    6 年前

    我同意你的想法

    服务需要从get list中过滤数据,但我不想编写一个完整的服务来提供过滤器

    为什么要为每个可能的组合编写一个小部件/过滤器/端点?

    只需提供获取所有数据/属性的基本操作。然后使用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 .

        2
  •  2
  •   Community Mohan Dere    6 年前

    我们已经做到了,我们的代码引用了这个网站: 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)

        3
  •  0
  •   johnny 5    6 年前

    最后我写了动态控制器。把解决问题作为一种变通方法。

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