在开发基于ASP.NET 5的跨平台CMS系统时,一个挑战是如何实现项目的模块化结构。希望项目能够通过简单地复制文件到扩展文件夹来添加或移除扩展。扩展可能包含控制器、视图模型、视图、存储模型和仓库等。为了解决这个问题,决定将这个功能提取到一个单独的ExtCore框架项目中,以便在其他项目中重用。
ExtCore框架由两个必需的NuGet包组成:
ExtCore.WebApplication包含用于发现和加载扩展、控制器、视图、资源等的类。ExtCore.Infrastructure定义了一个扩展,所有扩展必须实现IExtension接口才能被发现。此外,ExtCore还有一个可选的扩展用于存储(目前只支持SQLite和MS SQL Server,但添加其他存储支持非常简单)。
还有五个更多的NuGet包:
ExtCore.Data.Models.Abstractions定义了一个存储实体,所有实体必须实现IEntity接口。ExtCore.Data.Abstractions描述了基本的存储上下文、存储和仓库,以及IStorageContext、IStorage和IRepository接口。ExtCore.Data.EntityFramework.Sqlite和ExtCore.Data.EntityFramework.SqlServer实现了IStorageContext、IStorage和IRepository接口,适用于相应的存储。IStorage接口的实现将搜索所有程序集以找到给定的IRepository实现。如果找到了实现,它将实例化一个仓库实例并返回。
ExtCore.Data是ExtCore.Data扩展的主要部分。它包含了IExtension的实现,自动搜索可用的IStorage实现,并使用内置的ASP.NET 5DI注入。因此,每个控制器都能够获取IStorage实现的实例来与存储交互。
当创建自己的扩展时,可能(而且应该,如果想拥有统一的架构),遵循下一个扩展结构(其中X是扩展的名称):
YourApplication.X
YourApplication.X.Data.Models
YourApplication.X.Data.Abstractions
YourApplication.X.Data.SpecificStorageA
YourApplication.X.Data.SpecificStorageB
YourApplication.X.Data.SpecificStorageC
YourApplication.X.Frontend
YourApplication.X.Backend
等等。
正如看到的,这个结构与ExtCore.Data扩展的结构非常相似,但也可以在这里看到一些YourApplication.X.Frontend和YourApplication.X.Backend。YourApplication.X扩展的这些部分包含了它的UI(控制器、视图、js、CSS等)用于前端和后端(但可以在应用程序中有不同层次或层次)。
重要的是要知道如何存储视图和静态资源(如js、CSS、图片等),以及如何稍后使用它们。
有两种存储视图的选项:
存储静态资源只有一个选项。它是通过添加类似下面的内容到project.json中,将静态资源编译为程序集资源:
"resource": "Your/Static/Content/Path/**"
之后,可以使用像这样的URL通过HTTP使用ExtCore获取资源:
/resource?name=your.static.content.path.someimagename.png
(在未来的版本中,将使其能够通过名称像常规文件一样获取资源)。
请查看ExtCore.WebApplication.Startup类。首先,在ConfigureServices方法中,从应用程序的扩展文件夹加载所有程序集:
IEnumerable assemblies = AssemblyManager.LoadAssemblies(
this.applicationBasePath.Substring(
0,
this.applicationBasePath.LastIndexOf("src")) +
"artifacts\\bin\\Extensions",
this.assemblyLoaderContainer,
this.assemblyLoadContextAccessor
);
程序集加载后,将它们存储到全局缓存中:
ExtensionManager.SetAssemblies(assemblies);
现在使用ExtensionManager类,可以从任何地方访问可用程序集和扩展的数组,所以所有扩展都可以彼此了解。
接下来,必须允许Razor解析存储在扩展中的视图。正如上面描述的,有两种存储扩展中视图的选项。ExtCore支持它们两者。
这是包括编译为资源的视图:
.AddPrecompiledRazorViews(ExtensionManager.Assemblies.ToArray());
这是包括预编译视图:
services.Configure(options =>
{
options.FileProvider = this.GetFileProvider(this.applicationBasePath);
});
之后,必须调用所有扩展的SetConfigurationRoot和ConfigureServices方法:
foreach (IExtension extension in ExtensionManager.Extensions)
{
extension.SetConfigurationRoot(this.configurationRoot);
extension.ConfigureServices(services);
}
最后,应该告诉MVC如何在扩展中发现控制器:
services.AddTransient();
services.AddTransient();
ExtensionAssemblyProvider将复制DefaultAssemblyProvider找到的所有程序集,并添加存储在ExtensionManager中的程序集。
在Configure方法中,调用所有扩展的Configure和RegisterRoutes方法:
foreach (IExtension extension in ExtensionManager.Extensions)
extension.Configure(applicationBuilder);
applicationBuilder.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(name: "Resource", template: "resource", defaults: new { controller = "Resource", action = "Index" });
foreach (IExtension extension in ExtensionManager.Extensions)
extension.RegisterRoutes(routeBuilder);
});
要使用ExtCore,只需将ExtCore.WebApplication的引用添加到主应用程序的project.json中,使主应用程序的Startup类继承自ExtCore.WebApplication.Startup,并将ExtCore.Infrastructure的引用添加到扩展的project.json中。之后,可以将扩展的DLL文件放到/artifacts/bin/extensions文件夹中,它将在下次应用程序启动时自动被发现。
准备了一个示例应用程序,可以使用:
。它包含2个扩展:
还可以下载准备好的示例项目。它包含从Visual Studio 2015运行ExtCore-based Web应用程序所需的一切,包括带有测试数据的SQLite数据库。
不同的AspNet5项目使用不同的System.Xxx版本,决定在所有项目中将Microsoft.AspNet.Mvc作为依赖项,以便在所有项目中拥有相同的依赖集,但这是错误的。所以Microsoft.AspNet.Mvc应该被替换为,例如,System.Linq等的某个版本,但在这种情况下,因为不同项目中System.Xxx的不同版本而得到了编译错误。稍后会修复这个问题,并感谢任何帮助。
因为主Web应用程序没有一些模块需要的依赖项,不得不将System.Reflection.dll和System.Reflection.TypeExtensions.dll放到扩展文件夹中。真的不喜欢它,必须解决它。