ASP.NET Core MVC에서 런타임에 동적으로 모델 바인딩

Sep 25 2020

ASP.NET Core MVC 3.1을 사용하여 웹 애플리케이션을 개발 중입니다. 저는 사람들이 물건을 요청할 수있는 시스템을 구현하고 있으며, 그런 다음 일반적인 '요청'흐름으로 이동합니다. 80 % 유사한 컨트롤러와 뷰를 수십 개 생성하고 싶지 않기 때문에 모델을 동적으로 바인딩하고 다른 것에 부분 뷰를 사용하는 방법을 검토하고 있습니다.

이를 위해 모델 바인더 동작을 재정 의하여 런타임에 올바른 형식에서 바인딩 할 수 있도록합니다. '클래식'ASP.NET MVC를 사용하여이 작업을 수행하는 방법에 대한 지침 [1]을 찾았지만 모두 재 설계되었으므로 ASP.NET Core MVC에서는 작동하지 않는 것 같습니다.

나는 ComplexModelTypeBinder아마도 내가 필요한 것임을 알았지 만 BindingContext의 많은 속성이 이제 읽기 전용이기 때문에 이것을 상속하고 재정의해도 더 이상 얻을 수 없습니다.

ASP.NET Core MVC에서 동일한 목표를 어떻게 달성 할 수 있습니까?

[1] ASP.NET MVC-런타임에 모델을 동적으로 바인딩하는 방법

답변

1 JohnH Sep 25 2020 at 23:43

출발점을 알려 드릴 수 있습니다.

링크 한 기사와 같은 정신으로 몇 가지 애완 동물 관련 유형을 정의 해 보겠습니다.

public interface IPet
{
    string Name { get; }
}

public class Cat : IPet
{
    public string Name => "Cat";
    public bool HasTail { get; set; }
}

public class Dog : IPet
{
    public string Name => "Dog";
    public bool HasTail { get; set; }
}

public class Fish : IPet
{
    public string Name => "Fish";
    public bool HasFins { get; set; }
}

그리고보기에서 우리가 가지고 놀 수있는 다음 양식을 정의하십시오.

<form asp-action="BindPet" method="post">
    <input type="hidden" name="PetType" value="Fish" />
    <input type="hidden" name="pet.HasTail" value="true" />
    <input type="hidden" name="pet.HasFins" value="true" />
    <input type="submit" />
</form>

마지막으로 IPet인스턴스를 인수로 취하는 간단한 컨트롤러 작업 :

public IActionResult BindPet(IPet pet)
{
    return RedirectToAction("Index");
}

이제 다음과 같은 다형성 바인더를 만드는 데 세 부분이 있습니다.

  1. 모델 바인더 만들기, 구현 IModelBinder
  2. 을 구현하는 유형 IModelBinderProvider을 생성합니다.IModelBinder
  3. IModelBinderProvider사용할 수 있도록 유형 등록

우리 바인더의 구현은 다음과 같을 수 있습니다 (나는 꽤 잘하고 있기 때문에 주석을 추가했습니다) :

public class PetModelBinder : IModelBinder
{
    private readonly IDictionary<Type, (ModelMetadata, IModelBinder)> _binders;

    public PetModelBinder(IDictionary<Type, (ModelMetadata, IModelBinder)> binders) 
    {
        _binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // Read our expected type from a form,
        // and convert to its .NET type.
        var petType = bindingContext.ActionContext.HttpContext.Request.Form["PetType"];
        var actualType = TypeFrom(petType);

        // No point continuing if a valid type isn't found.
        if (actualType == null)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }

        // This will become clearer once we see how _binders
        // is populated in our IModelBinderProvider.
        var (modelMetadata, modelBinder) = _binders[actualType];

        // Create a new binding context, as we have provided
        // type information the framework didn't know was available.
        var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
            bindingContext.ActionContext,
            bindingContext.ValueProvider,
            modelMetadata,
            bindingInfo: null,
            bindingContext.ModelName);

        // This tries to bind the actual model using our
        // context, setting its Result property to the bound model.
        await modelBinder.BindModelAsync(newBindingContext);
        bindingContext.Result = newBindingContext.Result;

        // Sets up model validation.
        if (newBindingContext.Result.IsModelSet)
        {
            bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }

    private static Type? TypeFrom(string name)
    {
        return name switch
        {
            "Cat" => typeof(Cat),
            "Dog" => typeof(Dog),
            "Fish" => typeof(Fish),
            _ => null
        };
    }
}

다음으로 구현해 보겠습니다 IModelBinderProvider.

public class PetModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(IPet))
        {
            return null;
        }

        var pets = new[] { typeof(Cat), typeof(Dog), typeof(Fish) };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in pets)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new PetModelBinder(binders);
    }
}

보시다시피 바인더 자체보다 훨씬 간단하며 영광스러운 공장에 지나지 않습니다. 각 구체적인 유형에 대한 메타 데이터를 쿼리하고 각 유형을 처리 할 수있는 바인더를 생성하여 바인더에 전달합니다.

마지막으로에서 사용을 위해 Startup를 등록해야 IModelBinderProvider합니다.

services.AddControllersWithViews(options =>
{
    options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
});

는 0모델 바인더가 우선 순위를 나타냅니다. 이렇게하면 바인더가 먼저 확인됩니다. 이 작업을 수행하지 않으면 다른 바인더가 유형 바인딩을 시도했지만 실패합니다.

이제 완료되었으므로 디버거를 시작하고 우리가 만든 작업 메서드에 중단 점을 배치 한 다음 양식을 제출해보십시오. 의 인스턴스를 검사하면에 대해 설정 IPet되는 HasFins속성이 표시됩니다 Fish. PetType요소를로 편집하고 Dog위를 반복하면 HasTail설정되는 것을 볼 수 있습니다 .