65.9K
CodeProject 正在变化。 阅读更多。
Home

基于 Autofac 构建 Asp.net 依赖注入、IoC 插件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (5投票s)

2013年3月24日

CPOL

5分钟阅读

viewsIcon

21353

介绍如何将 Autofac 引入 Asp.net 插件框架

引言

关于 Asp.net 依赖注入和 IoC,有很多优秀的文章,大多数解决方案看起来都很吸引人,但你几乎需要从头开始开发一切。   

幸运的是,有一些优雅的 IoC 框架可以解决这些痛点,例如 Autofac  和 Spring.net. Autofac 更进一步,开发了一些库来 与 Asp.net 集成,这为我们节省了很多时间。

那么这篇文章是关于什么的呢?我不是在试图 reinvent 轮子,而是 **通过集成 Autofac,将 IoC & DI 功能引入 Asp.net 插件框架**,此解决方案也适用于集成其他库。 

背景  

我发布了一篇文章 **Asp.net MVC 插件框架** ,展示了构建模块化 Asp.net MVC 插件的解决方案。面向插件框架的设计应该能够轻松集成第三方库,这就是插件框架的作用。所以我决定基于 Autofac 构建一个 IoC 插件。  

使用代码

在我们深入之前,让我们开始开发一个使用 IoC 插件的演示。完整的源代码可以 **在此下载**,请选择

OSGi.NET 集成 Asp.NET MVC 3 

OSGi.NET 集成 Asp.NET MVC 4

两者皆可。

以 **MediaPlugin** 为例,需求是从数据存储(数据库或其他地方)加载所有热门电影,然后显示在页面上。所以我创建了一个电影数据访问接口,其定义如下:

   public interface IMoviceManager
    {
        List<Movie> GetMovies();
    }

	

其次,我创建了一个 **MediaManagement** 插件,它实际上是数据访问层,下面是模拟实现: 

public class MovieManager:IMoviceManager
    {
        private static List<Movie> _movies;
        static MovieManager()
        {
            _movies = new List<Movie>();
            _movies.Add(new Movie() { Name = "The Breaking Bad", Rating = 5 });
            _movies.Add(new Movie() { Name = "The Avatar", Rating = 5 });
            _movies.Add(new Movie() { Name = "The Walking Dead", Rating = 4 });
        }

        public List<Movie> GetMovies()
        {
            return _movies;
        }
    } 

在插件的激活器中,我像这样将数据访问层注册到 Autofac: 

public class Activator:IBundleActivator
    {
        public void Start(IBundleContext context)
        {
            var builder = context.GetFirstOrDefaultService<ContainerBuilder>();
            builder.RegisterType<MovieManager>().AsImplementedInterfaces();
        }

        public void Stop(IBundleContext context)
        {
        }
    }

OSGi.NET 中的 Activator 是插件的入口。当插件激活时,会调用 **Start** 方法;如果变为非活动状态,则调用 **Stop** 方法,这使用户有机会做一些事情,例如准备资源和释放资源。激活器是可选的,**点击此处 **了解一个简单的激活器演示。

目前,数据访问插件已准备就绪,下一步是创建一个 **MediaPlugin** 来在网页上显示电影。

页面的控制器定义如下:

 public class PopularTVShowController : Controller
    {
        private readonly IMoviceManager _moviceManager;

        /// <summary>
        /// Create instance of HelloController。
        /// </summary>
        /// <param name="moviceManager">MoviceManager business layer implementation, which is injected by IoC.</param>
        public PopularTVShowController(IMoviceManager moviceManager)
        {
            _moviceManager = moviceManager;
        }
        
        public ActionResult Index()
        {
            //
            return View(_moviceManager.GetMovies());
        }
    } 

访问其视图时,控制器会自动构造。运行模式截图如下:

 它是如何工作的?  

为了帮助我们更好地理解自动注入机制,我在此发布调试截图:

 

从调用堆栈可以看到,我们自定义了一个插件框架中的 ControllerFactory,它负责创建控制器实例。以下是其主要工作步骤:

  1. 假设用户访问一个插件页面,URL 为 https:///MediaPlugin/PopularTVShow/Index;
  2. 我们的自定义 ControllerFactory 从 URL 中识别出插件名称是 **MediaPlugin**,控制器名称是 **PopularTVShow**;
  3. Customized ControllerFactory 启动 **MediaPlugin**(如果它处于非活动状态),然后从插件程序集中解析 Controller 类型;
  4. ControllerFactory 尝试加载 **ControllerResolver** 服务来构造控制器实例。这是 **IocPlugin** 的关键,因为 **ControllerResolver** 服务是由 **IocPlugin** 提供的。如果 ControllerFactory 找不到可用的 **ControllerResolver** 服务,它将调用 System.Activator.CreateInstance 替代。

如何创建 IoC 插件?

这里没有技巧,只需先创建一个空的插件。当插件激活时,构造 Autofac ContainerBuilder,然后将其注册到你的插件框架。此项目中的插件框架由 **OSGi.NET** 支持,注册如下: 

public static ContainerBuilder Initialize(this BundleRuntime runtime)
        {
            //provide the container builder so that each plugin can register the dependancy when starting.
            var containerBuilder = new ContainerBuilder();
            runtime.AddService(typeof(ContainerBuilder), containerBuilder);
            return containerBuilder;
        }

然后唯一需要做的是监控任何插件更改,并维护 **ContainerBuilder** 的程序集。注册程序集的代码如下:

  public static void SafeRegisterControllers(this ContainerBuilder containerBuilder, Assembly[] assmblies)
        {
            //ContainerBuilder is not thread safe。
            lock (containerBuilder)
            {
                var container = BundleRuntime.Instance.GetFirstOrDefaultService<IContainer>();
                if (container == null)
                {
                    //If container is newly created,it can accept assmblies right now.
                    containerBuilder.RegisterControllers(assmblies);
                }
                else
                {
                    ContainerBuilder anotherBuilder = new ContainerBuilder();
                    anotherBuilder.RegisterControllers(assmblies);

                    anotherBuilder.Update(container);
                }
            }
        } 

兴趣点 

Aotufac 完成了实际工作

我在这里创建的 IoC 插件实际上重用了 Autofac 集成库来处理控制器依赖注入。Autofac ContainerBuilder 不是线程安全的,因此在更新之前必须锁定它。

此解决方案独立于插件框架

此插件框架基于 ASP.NET MVC 插件框架 但不限于该框架。此解决方案应适用于所有插件框架,如 MEF、Mono Addin,但你可能需要实现监控插件启动/停止的逻辑。

插件依赖与解析

插件框架的一个良好特性是任何插件都可以随时移除/停止/启动。让我们回顾一下我们创建的插件,你会发现 **MediaPlugin** 依赖于 **IocPlugin**,所以 **MediaPlugin** 只有在 IoCPlugin 激活时才能工作。在实际情况中,也许出于任何原因,IocPlugin 暂时不可用,在这种情况下,MediaPlugin 中的所有视图都应该对最终用户不可见,否则,用户将看到一个错误页面,抱怨“对象没有无参数构造函数”。因此,我们应该明确地告诉插件框架 **MediaPlugin** 依赖于 **IoCPlugin**,所以一旦 IocPlugin 不可用,IoCPlugin 也不可用,这就是插件框架中的依赖与解析。

在 OSGi.NET 中,依赖项在插件清单文件中定义如下:

 <?xml version="1.0" encoding="utf-8"?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="MediaPlugin" SymbolicName="MediaPlugin" Version="1.0.0.0" InitializedState="Active">
  <Activator Type="MediaPlugin.Activator" Policy="Immediate" />
  <Runtime>
    <Assembly Path="bin\MediaPlugin.dll" Share="false" />
    <Dependency BundleSymbolicName="UIShell.IoCPlugin" Resolution="Mandatory" />
  </Runtime>
</Bundle> 

 如果你使用其他插件框架,最好考虑这种情况。

 

启动插件的顺序

对于企业应用程序,通常有数百个插件,因此启动插件的顺序至关重要,像 DataAccessPlugin、AuthenticationPlugin 这样的核心插件通常比其他插件更早启动。我建议开发者不要让他们自己的插件依赖于启动顺序。IocPlugin 对大多数应用程序来说不是必需的,因此不能保证它总是比其他插件更早启动。考虑以下场景: 

  1. Plugin1 开始启动,其控制器与 **MediaPlugin** 一样依赖于 **IocPlugin**;
  2. IocPlugin 启动,它将监控任何插件的激活/非激活状态;
  3. MediaPlugin 启动,IocPlugin 收到插件激活通知,然后将 MediaPlugin 程序集加载到 ContainerBuilder;

你看出这里有什么问题了吗?**Plugin1** 中的程序集被 **IocPlugin** 忽略了!**IocPlugin** 的一个好的设计应该能够处理这种情况,即加载比它更早启动的程序集。

开发者请记住良好的实践: 

尽可能不要让你的插件依赖于启动顺序。

将更多库迁移到插件中

让我们总结一下迁移的关键步骤:

  1. 创建一个带有 Activator 类的空插件项目,并添加对第三方程序集的引用;
  2. 在插件 Activator 中构造你的服务,并将其注册到插件框架服务容器。对于 **IocPlugin**,我们创建 ContainerBuilder,然后通过调用将其放入 bundle 服务容器
    BundleRuntime.AddService(typeof(ContainerBuilder), containerBuilder);
    

所有其他插件都可以从服务容器获取 ContainerBuilder,并直接使用它。

有了这个解决方案,我们可以根据需要将尽可能多的库迁移到插件中。例如,我们可以 

  1. 通过集成 ActiveMQ 或 ESB 来实现一个 消息代理 插件;
  2. 通过集成 log4net 或企业库来创建日志插件;
  3. 按需实现其他插件。 

 

历史  

无。

© . All rights reserved.