HtmlHelper 扩展方法中的依赖注入? [英] Dependency Injection in HtmlHelper extension method?

查看:26
本文介绍了HtmlHelper 扩展方法中的依赖注入?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将属性渲染器实现为处理程序.我在应用程序中使用 Autofac 作为 DI 容器.如何在不使用全局可访问容器(服务位置)的情况下获取在 HtmlHelper 扩展中实现 IPropertyHandler 的对象?是否可以在 Autofac 中注册自己的 HtmlHelper?也许 MVC 框架提供了另一种方式?

I want to implement property renderers as handlers. I am using Autofac as a DI container in the app. How can I get objects implementing IPropertyHandler in HtmlHelper extension without using globally accessible container (service location)? Is it a way to register own HtmlHelper in Autofac? Maybe MVC framework provide another way?

public static class HtmlHelperExtensions {
    public static MvcHtmlString Editor(this HtmlHelper html, object model) {
        return new Renderer(new List<IPropertyHandler>() /*Where to get these objects?*/ ).Render(html, model);
    }
}

public class Renderer {
    private readonly ICollection<IPropertyHandler> _propertyRenderers;

    public Renderer(ICollection<IPropertyHandler> propertyRenderers) {
        _propertyRenderers = propertyRenderers;
    }

    public MvcHtmlString Render(HtmlHelper html, object model) {
        var result = "";
        foreach(var prop in model.GetType().GetProperties()) {
            var renderers = _propertyRenderers.OrderBy(b => b.Order);
            //impl
        }
        return new MvcHtmlString(result);
    }
}

推荐答案

AFAIK,MVC 5 没有提供一种方法来做到这一点.但这并不意味着您不能建立自己的解决方案.

AFAIK, MVC 5 doesn't provide a way to do this. But that doesn't mean you can't wire up your own solution.

MVC Core 现在使用对 DI 友好的视图组件,因此您不必跳过这么多障碍.

MVC Core now uses view components that are DI friendly, so you don't have to jump through so many hoops.

根据文章 Mark Seemann 的 DI 友好框架,你可以为你的 HTML 助手创建一个工厂接口,可以用来实例化它的依赖项.

As per the article DI Friendly Framework by Mark Seemann, you can make a factory interface for your HTML helper that can be used to instantiate it with its dependencies.

首先有一个默认工厂,它提供逻辑默认行为(无论是什么).

First there is a default factory that provides the logical default behavior (whatever that is).

public interface IRendererFactory
{
    IRenderer Create();
    void Release(IRenderer renderer);
}

public class DefaultRendererFactory : IRendererFactory
{
    public virtual IRenderer Create()
    {
        return new Renderer(new IPropertyHandler[] { new DefaultPropertyHandler1(), DefaultPropertyHandler2() });
    }
    
    public virtual void Release(IRenderer renderer)
    {
        if (renderer is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

您可能希望使这个默认工厂更智能,或者甚至使用流畅的构建器来提供其依赖项,如另一篇文章 DI 友好库,因此无需使用 DI 容器即可更加灵活.

You may wish to make this default factory smarter or even use a fluent builder to supply its dependencies as per the other article DI Friendly Library so it is more flexible without using a DI container.

然后我们对 RendererIRenderer 使用抽象,以便它可以轻松交换和/或通过 DI 提供.

Then we use an abstraction for Renderer, IRenderer so it can be swapped easily and/or provided via DI.

public interface IRenderer
{
    MvcHtmlString Render(HtmlHelper html, object model);
}

public class Renderer : IRenderer
{
    private readonly ICollection<IPropertyHandler> _propertyRenderers;

    public Renderer(ICollection<IPropertyHandler> propertyRenderers) 
    {
        _propertyRenderers = propertyRenderers;
    }

    public MvcHtmlString Render(HtmlHelper html, object model) 
    {
        var result = "";
        foreach(var prop in model.GetType().GetProperties()) 
        {
            var renderers = _propertyRenderers.OrderBy(b => b.Order);
            //impl
        }
        return new MvcHtmlString(result);
    }
}

工厂注册

接下来,我们提供一个钩子来注册工厂.由于 HTML helper 是一个静态扩展方法,唯一的选择是创建一个带有静态属性或方法的静态字段来设置它.如果需要在工厂中使用装饰器模式,那么制作 getter 总是一个好习惯.

Factory Registration

Next, we provide a hook to register the factory. Since the HTML helper is a static extension method, the only option is to make a static field with a static property or method to set it. Its always good practice to make a getter as well in case there is a need to use a decorator pattern on the factory.

public interface IRendererFactory
{
    IRenderer Create();
    void Release(IRenderer renderer);
}

public static class HtmlHelperExtensions {
    private static IRendererFactory rendererFactory = new DefaultRendererFactory();
    
    public static IRendererFactory RendererFactory
    {
        get => rendererFactory;
        set => rendererFactory = value;
    }

    public static MvcHtmlString Editor(this HtmlHelper html, object model) {
        var renderer = rendererFactory.Create();
        try
        {
            return renderer.Render(html, model);
        }
        finally
        {
            rendererFactory.Release(renderer);
        }
    }
}

如果这对应用程序更有意义,您可以提供一些合乎逻辑的位置来静态注册您的所有工厂.但是基本上每个 HTML 助手都需要一个工厂来遵守 SRP.如果您尝试概括,您基本上会回到服务定位器.

You could provide some logical place to register all of your factories statically, if that makes more sense for the app. But there will basically need to be a factory per HTML helper to adhere to the SRP. If you try to generalize, you are basically back to a service locator.

既然所有部件都已就位,这就是您将 Autofac 放入方程式的方法.您将需要一个自定义的 IRendererFactory,您将使其成为 Autofac 特定的组合根的一部分.

Now that all of the pieces are in place, this is how you would slip Autofac into the equation. You will need a custom IRendererFactory that you will make part of your composition root that is specific to Autofac.

public class AutofacRendererFactory : IRendererFactory
{
    private readonly Autofac.IContainer container;

    public AutofacRendererFactory(Autofac.IContainer container)
    {
        this.container = container ?? new ArgumentNullException(nameof(container));
    }

    public IRenderer Create()
    {
        return this.container.Resolve(typeof(IRenderer));
    }
    
    public void Release(IRenderer renderer)
    {
        // allow autofac to release dependencies using lifetime management
    }
}

接下来,您需要将 IRenderer 的类型映射及其依赖项添加到 Autofac.

Next, you need to add the type mappings for IRenderer and its dependencies to Autofac.

最后但并非最不重要的一点是,您需要在创建 Autofac 容器后在应用程序启动中添加一行,以在应用程序需要时解析渲染器.

Last but not least, you will need to add a line to your application startup after creating the Autofac container to resolve the renderer when it is needed by the application.

// Register all of your types with the builder
// ...
// ...
Autofac.IContainer container = builder.Build();
HtmlHelperExtensions.RendererFactory = new AutofacRendererFactory(container);

这篇关于HtmlHelper 扩展方法中的依赖注入?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆