ASP.NET MVC插件框架与Autofac集成

在构建模块化的ASP.NET MVC插件时,经常面临如何高效地整合第三方库的问题。幸运的是,有一些优雅的IoC框架,如Autofac,可以帮助减轻这种痛苦。Autofac甚至进一步开发了一些库来与ASP.NET集成,为节省了大量的时间。本文的目的是将IoC和DI特性通过Autofac集成到ASP.NET MVC插件框架中,这种解决方案同样适用于其他库的集成。

之前发表了一篇名为《ASP.NET MVC插件框架》的文章,展示了如何构建模块化的ASP.NET MVC插件。插件框架的设计初衷是能够轻松地整合第三方库,这正是插件框架的目的。因此,决定基于Autofac构建一个IoC插件。

使用代码

在深入之前,让先开发一个使用IoC插件的示例。完整的源代码可以在这里下载,请选择“OSGi.NET Integration with Asp.NET MVC 3”或“OSGi.NET Integration with 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中的激活器是插件的入口。当插件处于活动状态时,将调用Start方法,如果变为非活动状态,则调用Stop方法,这使用户有机会进行资源准备和释放。激活器是可选的,点击这里可以学习一个简单的激活器示例。

现在数据访问插件已经准备好了,下一步是创建一个“MediaPlugin”来在网页上显示电影。

页面的控制器定义如下: public class PopularTVShowController : Controller { private readonly IMoviceManager _moviceManager; public PopularTVShowController(IMoviceManager moviceManager) { _moviceManager = moviceManager; } public ActionResult Index() { return View(_moviceManager.GetMovies()); } }

当访问其视图时,控制器会自动构建。运行模式的屏幕截图如下:

它是如何工作的?

为了帮助更好地理解自动注入机制,发布了调试屏幕截图如下:

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

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

如何创建IoC插件?

这里没有花招,首先简单地创建一个空插件。当插件处于活动状态时,构建Autofac ContainerBuilder,然后将实例注册到插件框架中。这个项目中的插件框架是由OSGi.NET支持的,注册如下: public static ContainerBuilder Initialize(this BundleRuntime runtime) { var containerBuilder = new ContainerBuilder(); runtime.AddService(typeof(ContainerBuilder), containerBuilder); return containerBuilder; }

然后唯一需要做的事情是监控任何插件变化,并维护ContainerBuilder的程序集。注册程序集的代码片段如下: public static void SafeRegisterControllers(this ContainerBuilder containerBuilder, Assembly[] assmblies) { lock (containerBuilder) { var container = BundleRuntime.Instance.GetFirstOrDefaultService<IContainer>(); if (container == null) { containerBuilder.RegisterControllers(assmblies); } else { ContainerBuilder anotherBuilder = new ContainerBuilder(); anotherBuilder.RegisterControllers(assmblies); anotherBuilder.Update(container); } } }

有趣的点

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

解决方案是插件框架独立的。这个插件框架是基于ASP.NET MVC插件框架的,但不限于该框架。这个解决方案应该适用于所有插件框架,如MEF、Mono Addin,但可能需要实现监控插件Start/Stop的逻辑。

插件依赖性 & 解析。插件框架的一个好特性是任何插件都可以在任何时候被移除/停止/启动。让回顾一下创建的插件,会发现MediaPlugin依赖于IocPlugin,所以MediaPlugin只有在IocPlugin处于活动状态时才有效。在现实生活中,可能由于任何原因,IocPlugin暂时不可用,在这种情况下,所有MediaPlugin中的视图应该对最终用户不可见,否则用户将看到错误页面抱怨“No parameterless constructor defined for the object”。因此,应该明确告诉插件框架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"/> </Runtime> <Dependency BundleSymbolicName="UIShell.IoCPlugin" Resolution="Mandatory"/> </Bundle>

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

启动插件的顺序

对于企业应用程序,通常有数百个插件,所以启动插件的顺序至关重要,一些核心插件如DataAccessPlugin、AuthenticationPlugin通常比其他插件更早启动。建议开发者不要让他们插件依赖于启动顺序。IocPlugin对大多数应用程序来说并不是必需的,所以没有保证它总是先于其他插件启动。考虑以下场景: Plugin1启动了,它的控制器依赖于IocPlugin,就像MediaPlugin一样;IocPlugin启动了,它将监控任何插件活动/非活动;MediaPlugin启动了,所以IocPlugin收到了插件活动通知,然后加载MediaPlugin程序集到ContainerBuilder;看到哪里错了吗?Plugin1中的程序集被IocPlugin忽略了!一个好的IocPlugin设计应该能够处理这种情况,即加载在它之前启动的程序集。

请开发者记住一个好的实践:尽可能不要让插件依赖于启动顺序。

将更多库迁移到插件中

让总结一下迁移的关键步骤,创建一个带有Activator类的空插件项目,添加对第三方程序集的引用;在插件Activator中构建服务,然后将其注册到插件框架服务容器中。对于IocPlugin,创建ContainerBuilder,然后通过调用BundleRuntime.AddService(typeof(ContainerBuilder), containerBuilder)将其放入bundle服务容器中。所有其他插件都可以从服务容器中获取ContainerBuilder,并直接使用它。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485