Моделі пізнього зв’язування динамічно вирішальних моделей після введення контролера


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

Після копання у джерело я побачив деякі перспективні речі ModelBinderFactory та ControllerActionInvoker Ці класи використовуються в конвеєрі для прив'язки моделі,

Я б очікував, що відкриється простий інтерфейс для вирішення імені параметра з QueryString, приблизно так:

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

Однак єдиним способом, яким я бачу вирішити модель із в'яжучої моделі, є створення фальшивих дескрипторів контролера та знущання над великою кількістю речей.

Як я можу прийняти пізні параметри в моїй контурлер?


2
Я не бачу в цьому користі. Крім того, навіть якщо ви можете зв'язати модель на основі рядкового параметра .... ви не зможете використовувати такий загальний метод, як GetValueFor <T>, оскільки T повинен бути вирішений час компіляції .... це означає, що абонент повинен знати тип T під час компіляції, який би перемогли мету динамічного зв’язування типу. Це означає, що спадкодавець DynamicControllerBase повинен знати тип TDTO .... Одне, що ви можете спробувати, це отримати JSON в параметрі і кожна реалізація DynamicControllerBase деріаріалізувати рядок до моделі та viceversa.
Джонатан Альфаро

@Darkonekt, якщо ви дивитесь на метод "AddFilter", у вас є введені загальні параметри, які зберігаються в закритій частині при реєстрації функцій. Це трохи заплутано, але я запевняю, що його життєздатність може працювати
Johnny 5

Я не хочу підключатися до json, тому що я не хочу, щоб змінити спосіб webapi, природно вирішуючи параметри
johnny 5

Якщо ви б пояснили трохи більше про те, як використовувати та реальний сценарій, коли такий функціонал необхідний, це дуже допоможе. Напевно, є навіть більш просте рішення .. хто знає. Що стосується свого самоврядування, то мені іноді подобається ускладнювати речі .. Просто кажу ..
Містер Блонд

@ Mr.Blond У мене є загальна послуга відпочинку, яка забезпечує функціональність грубого списку та отримання списку. Іноді мої служби потребують фільтрації даних із списку отримання, але мені не хочеться писати цілу службу всього, що мені потрібно, щоб забезпечити фільтр
johnny 5

Відповіді:


2

Я згоден з вашою думкою

сервісам потрібно фільтрувати дані зі списку get, але я не хочу, щоб написати цілу службу всього, що мені потрібно, щоб забезпечити фільтр

Навіщо писати віджет / фільтр / кінцеву точку для кожної можливої ​​комбінації?

Просто наведіть основні операції, щоб отримати всі дані / властивості. Потім використовуйте 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 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");
johnny 5

Оскільки у мене немає середовища для мінімального відтворення цього, я не зможу це зробити - вибачаюся за відсутність вимоги dotnetcore.
Брайан каже, що

Я можу перевести це на .Net-Core, це добре, якщо ви покажете мені, як розв’язати ім'я параметра, я прийму
johnny 5

Це найближча річ до відповіді, яку я хотів, тож я дам вам нагороду
johnny 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;
}

Я зараз важко кодую функцію в методі, але я впевнений, що ви можете зрозуміти, як передати його, якщо вам потрібно.

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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.