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


9

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

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

深入研究Source之后,我看到了一些有前途的东西ModelBinderFactoryControllerActionInvoker这些类在管道中用于模型绑定,

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

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

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

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


2
我看不出有什么用。此外,即使您可以基于字符串参数绑定模型...。您也无法使用GetValueFor <T>之类的通用方法,因为T必须在编译时解析。这意味着调用方必须知道T的类型在编译时将无法动态绑定该类型。这意味着DynamicControllerBase的继承者必须知道TDTO的类型...。您可以尝试的一件事是在参数中接收JSON,并使DynamicControllerBase的每个实现将字符串反序列化为模型,反之亦然。
乔纳森·阿尔法罗

@Darkonekt,如果您查看“ AddFilter”方法,则将拥有键入的通用参数,这些通用参数将在注册功能时存储在闭包中。这有点令人困惑,但我向您保证它是可行的并且可以工作
johnny19年5

我不想插入json,因为我不想更改webapi自然解析参数的方式
johnny

如果您需要更多有关用例和现实生活中需要这种功能的场景的解释,那将会很有帮助。可能甚至有更简单的解决方案可用。至于我自己,我有时喜欢复杂的东西..只是说..
金发先生

@ Mr.Blond我有一个通用的rest服务,提供了crud和get list功能。有时我的服务需要从获取列表中过滤数据,但是我不想写所有我需要提供过滤器的全部服务
johnny

Answers:


2

我同意你的想法

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

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

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


我一直在考虑使用GraphQL,但我与当前的实现紧密相连
johnny

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)


感谢您的帮助,目前,我不使用MVC,例如(输入参数可以超过1个)我需要按名称解析参数。另外,我使用的是.Net-Core,我认为这是为旧版本的.net编写的。请完成方法存根:才能接受答案:this.Resolve<MyCustomType>("MyParamName");
johnny

由于我没有最小化此环境的环境,因此我将无法做到这一点-对于缺少dotnetcore要求,我深表歉意。
布赖恩说莫妮卡(

我可以将其转换为.Net-Core,如果您向我展示如何通过参数名称解​​析,我会接受
johnny

这是我想要的答案的最接近的内容,所以我将给予您赏金
johnny

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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.