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

基于ASP.NET 5和ExtCore框架的模块化可扩展应用程序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2015 年 12 月 16 日

CPOL

6分钟阅读

viewsIcon

16085

downloadIcon

301

使用ExtCore框架创建模块化可扩展的ASP.NET 5应用程序

引言

在开发Platformus项目(一个基于ASP.NET 5的免费、开源、跨平台CMS)时,对我来说最困难的任务之一就是项目的模块化结构。我希望能够通过将扩展复制到扩展文件夹中来添加/删除它们。此外,我的扩展可能由控制器、视图模型、视图、存储模型和仓储等组成。在某个时候,我决定将此功能提取到一个单独的ExtCore框架项目中,以便以后在其他项目中重用。

什么是ExtCore框架

ExtCore框架由2个必需的NuGet包组成

  • ExtCore.WebApplication
  • ExtCore.Infrastructure

ExtCore.WebApplication

包含用于发现和加载扩展、控制器、视图、资源等的类。

ExtCore.Infrastructure

使用IExtension接口描述扩展。所有扩展都必须实现此接口才能被发现。

此外,ExtCore还有一个可选的扩展来处理存储(目前仅支持SQLite和MS SQL Server,但很容易添加其他存储支持)。还有5个NuGet包

  • ExtCore.Data
  • ExtCore.Data.Models.Abstractions
  • ExtCore.Data.Abstractions
  • ExtCore.Data.EntityFramework.Sqlite
  • ExtCore.Data.EntityFramework.SqlServer

ExtCore.Data.Models.Abstractions

使用IEntity接口描述存储实体。所有实体都必须实现此接口。

ExtCore.Data.Abstractions

使用IStorageContextIStorageIRepository接口描述基本存储上下文、存储和仓储。

ExtCore.Data.EntityFramework.Sqlite和ExtCore.Data.EntityFramework.SqlServer

为相应的存储实现IStorageContextIStorageIRepositoryIStorage接口的实现将遍历所有程序集以查找给定的IRepository实现。如果找到实现,它将实例化一个仓储实例并返回。

ExtCore.Data

ExtCore.Data扩展的主要部分。它包含IExtension的实现,该实现自动搜索可用的IStorage实现并使用内置的ASP.NET 5 DI注入它。因此,每个控制器都能够获取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.FrontendYourApplication.X.BackendYourApplication.X扩展的这些部分包含其用于前端和后端的UI(控制器、视图、js、CSS等)(但您的应用程序可以有不同的层)。

了解如何将视图和静态资源(如js、CSS、图像等)存储在扩展中以及如何以后使用它们非常重要。

存储视图有两种选择

  1. 您可以将视图作为编译后的资源存储。在这种情况下,如果类型是在主Web应用程序没有依赖的任何内容中定义的,则视图不能是强类型的。换句话说,您只能使用标准类型(如stringIEnumerable)作为视图模型,否则将无法在运行时编译视图。
  2. 您可以将视图作为预编译的视图存储。在这种情况下,您可以对视图模型使用任何现有类型。此外,在运行时编译视图不会花费时间。这是我首选的选项。

存储静态资源只有一种选择。通过在您的project.json中添加类似以下内容,将静态资源编译为程序集资源

"resource": "Your/Static/Content/Path/**"

之后,您可以使用URL(如/resource?name=your.static.content.path.someimagename.png)通过HTTP使用ExtCore获取资源(在将来的版本中,我将使其能够像常规文件一样通过名称获取资源)。

工作原理

请查看ExtCore.WebApplication.Startup类

首先,在ConfigureServices方法中,我们加载应用程序扩展文件夹中的所有程序集

IEnumerable<Assembly> 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<RazorViewEngineOptions>(options =>
  {
    options.FileProvider = this.GetFileProvider(this.applicationBasePath);
  }
);

之后,我们必须调用所有扩展的SetConfigurationRootConfigureServices方法

foreach (IExtension extension in ExtensionManager.Extensions)
{
  extension.SetConfigurationRoot(this.configurationRoot);
  extension.ConfigureServices(services);
}

最后,我们应该告诉MVC如何发现扩展中的控制器

services.AddTransient<DefaultAssemblyProvider>();
services.AddTransient<IAssemblyProvider, ExtensionAssemblyProvider>();

ExtensionAssemblyProvider将复制DefaultAssemblyProvider找到的所有程序集,并添加存储在我们的ExtensionManager中的程序集。

Configure方法中,我们调用所有扩展的ConfigureRegisterRoutes方法

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,只需在主应用程序的project.json中添加对ExtCore.WebApplication的引用,使主应用程序的Startup类继承自ExtCore.WebApplication.Startup,并在扩展的project.json中添加对ExtCore.Infrastructure的引用。之后,您可以将扩展的DLL文件放入/artifacts/bin/extensions文件夹中,应用程序下次启动时它将自动被发现。

我准备了示例应用程序,您可以使用:https://github.com/ExtCore/ExtCore-Sample。它包含2个扩展

  1. 扩展A。它展示了如何将视图作为资源添加到程序集(并且,它还显示主Web应用程序将找到这些视图)。此外,它还展示了如何获取和显示所有可用扩展的名称。
  2. 扩展B。它展示了如何使用预编译的强类型视图以及在扩展内部定义的自定义视图模型类。此外,它还展示了如何处理存储(在这种情况下是Sqlite)。

您还可以下载我准备好的示例项目。它包含运行基于ExtCore的Web应用程序从Visual Studio 2015所需的一切,包括带测试数据的SQLite数据库。

已知问题

  1. 不同的AspNet5项目使用不同版本的System.Xxx,我决定只将Microsoft.AspNet.Mvc作为所有项目的依赖项,以使所有项目具有相同的依赖项集,但这是错误的。所以Microsoft.AspNet.Mvc应该替换为,例如,某个版本的System.Linq等,但在这种情况下,我由于不同项目中System.Xxx的不同版本而遇到编译错误。我稍后会修复这个问题,并感谢任何帮助。
  2. 因为主Web应用程序没有模块所需的一些依赖项,我不得不将例如System.Reflection.dllSystem.Reflection.TypeExtensions.dll放到扩展文件夹中。我真的不喜欢它,也必须解决它。
  3. 最大的问题是我找不到EntityFramework.Sqlite运行所需的正确程序集集(我尝试将很多程序集复制到扩展文件夹中但没有成功),所以我决定只将EntityFramework.Sqlite作为主Web应用程序的依赖项,但我真的不太喜欢它,但它现在可以工作了。所以这也是我必须解决的问题。

后记

如果我的工作对您有用,我将很高兴,并随时与我联系提出问题或想法。此外,您可以在Gitter上给我留言:https://gitter.im/ExtCore/ExtCore

© . All rights reserved.