컨트롤러에 입력 한 후 늦게 바인딩 동적으로 모델 해결


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

My Controller는 옵션을 추적하고이를 사용하여 페이징 엔드 포인트 및 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");

그러나 모델 바인더에서 모델을 확인하는 유일한 방법은 가짜 컨트롤러 설명자를 만들고 많은 것을 조롱하는 것입니다.

관리자에게 늦게 바인딩 된 매개 변수를 어떻게 받아 들일 수 있습니까?


2
나는 이것에 대한 사용을 보지 못했다. 또한 문자열 매개 변수를 기반으로 모델을 바인딩 할 수 있더라도 T는 컴파일 시간을 해결해야하기 때문에 GetValueFor <T>와 같은 일반 메서드를 사용할 수 없습니다. 이는 호출자가 알아야 함을 의미합니다. 컴파일 타임에 T 타입으로 타입을 동적으로 바인딩하는 목적을 상실합니다. 이는 DynamicControllerBase의 상속자가 TDTO의 유형을 알아야한다는 것을 의미합니다. 시도 할 수있는 한 가지는 매개 변수에서 JSON을 수신하고 DynamicControllerBase의 각 구현에서 문자열을 모델로 직렬화하고 그 반대의 경우도 마찬가지입니다.
Jonathan Alfaro

@Darkonekt 'AddFilter'메서드를 보면 funcs를 등록 할 때 클로저에 저장되는 형식화 된 일반 매개 변수가 있습니다. 약간 혼동 스럽지만 실행 가능하고 작동 할 수 있음을 보증합니다.
Johnny 5

나는 webapi로 자연스럽게 매개 변수를 해결하는 방법을 변경하고 싶지 않기 때문에 json에 연결하고 싶지 않습니다
johnny 5

이런 종류의 기능이 필요한 사용 사례와 실제 시나리오에 대해 조금 더 설명한다면 많은 도움이 될 것입니다. 아마도 더 간단한 해결책도있을 것입니다. 내 자신에 관해서는, 나는 때때로 복잡한 것들처럼 .. 그냥 말 ..
씨 금발

@ Mr.Blond 나는 crud 및 get list 기능을 제공하는 일반 휴식 서비스를 제공합니다. 때로는 내 서비스가 가져 오기 목록에서 데이터를 필터링해야하지만 필터를 제공하는 데 필요한 모든 서비스를 작성하고 싶지는 않습니다.
johnny 5

답변:


2

나는 당신의 생각에 동의합니다

서비스는 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.


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를 사용하고 있지 않습니다. 예를 들어 (입력 매개 변수가 두 개 이상일 수 있습니다) 이름으로 매개 변수를 해결해야합니다. 또한 .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;
}

지금은 메소드에서 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();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.