C# ASP.NET核心MVC中运行时动态绑定模型

C# ASP.NET核心MVC中运行时动态绑定模型,c#,asp.net,asp.net-mvc,asp.net-core,C#,Asp.net,Asp.net Mvc,Asp.net Core,我正在使用ASP.NET核心MVC 3.1开发一个web应用程序。我正在实现一个系统,人们可以在其中请求东西,然后进入一个通用的“请求”流。因为我不想创建几十个80%相似的控制器和视图,所以我考虑动态绑定模型,并使用部分视图处理不同的内容 为此,我希望重写模型绑定器行为,以便它可以在运行时从正确的类型绑定。我找到了一些关于如何使用“经典”ASP.NET MVC实现这一点的指导[1],但这在ASP.NET核心MVC中似乎不起作用,因为这些都经过了重新设计 我发现,ComplexModelTypeB

我正在使用ASP.NET核心MVC 3.1开发一个web应用程序。我正在实现一个系统,人们可以在其中请求东西,然后进入一个通用的“请求”流。因为我不想创建几十个80%相似的控制器和视图,所以我考虑动态绑定模型,并使用部分视图处理不同的内容

为此,我希望重写模型绑定器行为,以便它可以在运行时从正确的类型绑定。我找到了一些关于如何使用“经典”ASP.NET MVC实现这一点的指导[1],但这在ASP.NET核心MVC中似乎不起作用,因为这些都经过了重新设计

我发现,
ComplexModelTypeBinder
可能是我所需要的,但是继承和重写它并不能让我走得更远,因为BindingContext上的许多属性现在都是只读的

如何在ASP.NET核心MVC中实现相同的目标


[1] 我可以给你一个出发点

本着与您链接的文章相同的精神,让我们定义一些与宠物相关的类型:

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>
现在,有3个部分可以创建这样的多态绑定器:

  • 创建模型绑定器,实现
    IModelBinder
  • 创建实现
    IModelBinderProvider
    的类型,该类型将用于创建我们的
    IModelBinder
  • 正在注册我们的
    IModelBinderProvider
    类型以便可以使用
  • 我们的活页夹的实现可以如下所示(我添加了一些注释,因为它做得比较好):

    正如你所看到的,这比活页夹本身简单得多,只不过是一个美化的工厂。它查询每个具体类型的元数据,还创建一个可以处理这些类型的绑定器,并将它们传递给我们的绑定器

    最后,在
    启动
    中,我们需要注册
    IModelBinderProvider
    以供使用:

    services.AddControllersWithViews(options =>
    {
        options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
    });
    
    0
    表示模型绑定器的优先级。这确保我们的活页夹将首先被检查。如果我们不这样做,另一个绑定器将尝试绑定该类型,但失败

    完成后,启动调试器,在我们创建的操作方法中放置一个断点,然后尝试提交表单。检查
    IPet
    的实例,您应该会看到正在为
    Fish
    设置
    HasFins
    属性。将
    PetType
    元素编辑为
    Dog
    ,重复上述操作,您将看到设置了
    HasTail


    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
            };
        }
    }
    
    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);
        }
    }
    
    services.AddControllersWithViews(options =>
    {
        options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
    });