简体   繁体   English

在 ASP.NET Core MVC 中在运行时动态绑定模型

[英]Dynamically binding models at runtime in ASP.NET Core MVC

I'm developing a web application using ASP.NET Core MVC 3.1.我正在使用 ASP.NET Core MVC 3.1 开发 Web 应用程序。 I'm implementing a system where people can request things, which then goes into a generic 'request' flow.我正在实施一个系统,人们可以在其中请求事物,然后进入通用的“请求”流程。 Because I don't want to create dozens of controllers and views which are 80% similar, I am looking at dynamically binding the model and using partial views for the things which are different.因为我不想创建几十个 80% 相似的控制器和视图,所以我正在考虑动态绑定模型并使用部分视图来处理不同的事情。

For this purpose, I want to override the model binder behaviour so that it can bind from the correct type at run-time.为此,我想覆盖模型绑定器行为,以便它可以在运行时从正确的类型进行绑定。 I have found some guidance[1] on how to do this with 'classic' ASP.NET MVC, but this doesn't seem to work in ASP.NET Core MVC as this has all been reengineered.我找到了一些关于如何使用“经典”ASP.NET MVC 执行此操作的指南 [1],但这在 ASP.NET Core MVC 中似乎不起作用,因为这已全部重新设计。

I've found out the ComplexModelTypeBinder is probably what I need, but inheriting and overriding from this doesn't get me much further as quite a number of properties on the BindingContext are now read-only.我发现ComplexModelTypeBinder可能是我需要的,但是继承和覆盖它并没有让我更进一步,因为 BindingContext 上的很多属性现在都是只读的。

How can I achieve the same goal in ASP.NET Core MVC?如何在 ASP.NET Core MVC 中实现相同的目标?

[1] ASP.NET MVC - How To Dynamically Bind Models At Run-time [1] ASP.NET MVC - 如何在运行时动态绑定模型

I can give you a starting point to get going.我可以给你一个出发点。

In the same spirit as the article you linked, let's define some pet-related types:本着与您链接的文章相同的精神,让我们定义一些与宠物相关的类型:

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

And in a view, define the following form that we can play around with:在一个视图中,定义以下我们可以使用的表单:

<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>

And, finally, a simple controller action that takes an IPet instance as an argument:最后,一个简单的控制器动作,它将一个IPet实例作为参数:

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

Now, there are 3 parts to creating a polymorphic binder like this:现在,创建一个像这样的多态绑定器有 3 个部分:

  1. Creating a model binder, implementing IModelBinder创建模型绑定器,实现IModelBinder
  2. Creating a type that implements IModelBinderProvider , which will be used to create instances of our IModelBinder创建一个实现IModelBinderProvider的类型,它将用于创建我们的IModelBinder实例
  3. Registering our IModelBinderProvider type so it can be used注册我们的IModelBinderProvider类型以便它可以使用

An implementation of our binder could look as follows (I've added comments, as it's doing a fair bit):我们的活页夹的实现可能如下所示(我已经添加了注释,因为它做得很好):

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

Next, let's implement IModelBinderProvider :接下来,让我们实现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);
    }
}

As you can see, that is much simpler than the binder itself, and is little more than a glorified factory.如您所见,这比活页夹本身简单得多,只不过是一个光荣的工厂。 It queries the metadata for each concrete type, and also creates a binder that could handle each of those types, and passes those to our binder.它查询每个具体类型的元数据,并创建一个可以处理每种类型的绑定器,并将它们传递给我们的绑定器。

Finally, in Startup , we need to register the IModelBinderProvider for use:最后,在Startup ,我们需要注册IModelBinderProvider以供使用:

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

The 0 indicates the priority the model binder has. 0表示模型绑定器的优先级。 This ensures our binder will be checked first.这确保我们的活页夹将首先被检查。 If we didn't do this, another binder would attempt, and fail, to bind the type.如果我们不这样做,另一个绑定器会尝试绑定类型,但失败了。

Now that that's done, start the debugger, place a breakpoint in the action method we created, and try submitting the form.现在已经完成了,启动调试器,在我们创建的 action 方法中放置一个断点,然后尝试提交表单。 Inspect the instance of IPet and you should see the HasFins property being set for Fish .检查IPet的实例,您应该会看到为Fish设置的HasFins属性。 Edit the PetType element to be Dog , repeat the above, and you should see HasTail being set.PetType元素编辑为Dog ,重复上述操作,您应该会看到HasTail被设置。

狗模型绑定 鱼模型绑定

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM