进入控制器后的后期绑定动态解析模型

问题描述 投票:9回答:3

我正在寻找一种在控制器中执行动作后解析模型的方法,描述问题的最简单方法是:

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

但是,我看到的从模型绑定器解析模型的唯一方法是创建伪造的控制器描述符并模拟大量内容。

我如何在控制器中接受后期绑定参数?

c# rest .net-core model-binding
3个回答
2
投票

我同意你的想法

服务需要从获取列表中过滤数据,但我不想写所有我需要提供过滤器的全部服务

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

仅提供基本操作即可获取所有数据/属性。然后使用GraphQL允许最终用户过滤(model)以满足their的需要。

来自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
投票

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


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();
}
© www.soinside.com 2019 - 2024. All rights reserved.