Динамическое разрешение моделей с поздним связыванием после ввода контроллера

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, я увидел многообещающие вещи ModelBinderFactory и ControllerActionInvoker. Эти классы используются в конвейере для привязки модели,

Я ожидаю, что будет представлен простой интерфейс для разрешения имени параметра из QueryString, что-то вроде этого:

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

Тем не менее, единственный способ разрешить модель из связывателя модели - создать поддельные дескрипторы контроллера и высмеивать массу вещей.

Как я могу принять параметры с поздней привязкой в ​​мой контроллер?

Джонни 5
источник
2
Я не вижу смысла в этом. Более того, даже если вы можете связать модель на основе строкового параметра .... вы не сможете использовать универсальный метод, такой как GetValueFor <T>, потому что T должен быть разрешен во время компиляции .... это означает, что вызывающая программа должна знать тип T во время компиляции, который побеждал бы цель динамического связывания типа. Это означает, что наследник DynamicControllerBase должен знать тип TDTO .... Вы можете попробовать получить JSON в параметре и заставить каждую реализацию DynamicControllerBase десериализовать строку в модель и наоборот.
Джонатан Альфаро
@Darkonekt Если вы посмотрите на метод AddFilter, у вас есть типизированные общие параметры, которые сохраняются в замыкании при регистрации функций. Это немного сбивает с толку, но я уверяю вас, это жизнеспособно и может работать
Джонни 5
Я не хочу подключаться к json, потому что я не хочу менять способ, которым webapi естественным образом разрешает параметры
Джонни 5
Если бы вы объяснили немного больше о сценарии использования и сценарии реальной жизни, где такая функциональность необходима, это бы очень помогло. Вероятно, есть даже более простое доступное решение .. кто знает. Что касается меня, я иногда люблю усложнять вещи .. Просто говорю ..
Мистер Блонд
@ Mr.Blond У меня есть общая служба отдыха, которая предоставляет функции списка и поиска. Иногда мои сервисы должны фильтровать данные из списка получения, но я не хочу писать весь сервис из всего, что мне нужно, чтобы предоставить фильтр
Джонни 5

Ответы:

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,

ΩmegaMan
источник
Я думал об использовании GraphQL, но я слишком привязан к своей текущей реализации
Джонни 5
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");
Джонни 5
Поскольку у меня нет среды для минимального воспроизведения, я не смогу этого сделать - извиняюсь за отсутствие требования dotnetcore.
Брайан говорит «Восстановить Монику»
Я могу перевести его на .Net-Core, это нормально, если вы покажете мне, как разрешить по имени параметра, я приму
Джонни 5
Это самый близкий к ответу вопрос, который я хотел, поэтому я дам вам награду
Джонни 5
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();
}
Джонни 5
источник