HtmlHelper 扩展方法中的依赖注入?

Kon*_*ski 5 asp.net-mvc dependency-injection autofac asp.net-mvc-5

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Nig*_*888 6

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

MVC Core 现在使用DI 友好的视图组件,因此您不必跳过这么多圈套。

根据Mark Seemann的文章DI Friendly Framework,您可以为您的 HTML 助手创建一个工厂接口,该接口可用于实例化它的依赖项。

默认渲染器工厂

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

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可能希望使这个默认工厂更智能,或者甚至使用 fluent builder 来提供其依赖项,如另一篇文章DI Friendly Library那样,它在不使用 DI 容器的情况下更加灵活。

渲染器

然后我们对 使用抽象RendererIRenderer因此它可以轻松交换和/或通过 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

工厂注册

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

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

AutofacRendererFactory

现在所有的部分都已就位,这就是您将 Autofac 放入等式的方法。您将需要一个自定义IRendererFactory,您将使其成为特定于 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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