.NET ASP.NET MVC 插件架构与嵌入式视图 - 第 2 部分,插件服务器端逻辑






4.84/5 (14投票s)
如何在插件程序集中处理数据库操作或任何业务特定逻辑。
引言
在文章的第一部分中,我解释了如何快速构建 MVC 插件架构。从那时起,我收到了许多关于如何进行数据库调用、嵌入式资源管理以及在设计此类应用程序时需要了解的其他事项的问题。在第 2 部分中,我将主要提出一个关于如何在插件程序集中处理数据库操作或任何业务特定逻辑的建议。
背景
通常,任何小型、中型或大型应用程序的宏观结构都分为三个层次:数据访问层、业务逻辑层和用户界面层。为了可维护性和单元测试,每个层次都应尽可能解耦。我们大多数人都会使用一些模式和架构原则,如 ORM、存储库、业务实体、业务服务、控制反转等。这些大多是每个架构师即使在梦中也知道的古老而成熟的原则。我们中的一些人有时还会尝试做更多,例如 DDD 或 CQRS,如果时间、资源和复杂性允许的话。
在这种 MVC 插件方法中,我尝试使插件对其他程序集的引用非常轻量,这就是为什么我倾向于将业务逻辑置于 Web 服务之后并选择 SOA 架构的原因。这也是一个业务决策,因为向客户交付更少的程序集会更方便。在这里,我偏爱的依赖注入框架 Castle Windsor 在 Windsor WcfFacility 方面提供了很大帮助,该设施基本上允许将服务接口注册到 DI 容器中,作为实际 Web 服务的代理;这非常有用,因为服务引用始终是最新的,并且代码不了解实现是服务代理。
对于代码示例,我选择将第一部分中的 Agenda 插件扩展为 Get+CRUD 功能,几乎就像一个普通的日程应用程序。所有对象都保留在内存中,没有数据库以保持应用程序的简单性,存储库实现将所有持久化都在内存中完成。让我们看一些代码示例。
不同的文件夹结构
首先,第一部分中的示例已重命名为 Calendar 并分为以下层:
- Infrastructure - 非常通用的接口和应用程序范围内的基础设施组件。
- DataAccess - 持久化层,在此示例中使用了 MemoryCache,通常在此处进行数据库访问。
- BusinessLogic - 域模型和用于服务契约和实现的程序集。
- Web - Web 服务托管、插件和 Web 应用程序。
以下是一个快速视图:
插件系统所需的更改
IModule 接口和插件模块入口
首先,需要修改 IModule
接口,并添加一个新方法 Install()
。每个模块都将在此处进行 DI 注册和其他初始化代码。
public interface IModule
{
/// <summary>
/// Title of the plugin, can be used as a property to display on the user interface
/// </summary>
string Title { get; }
/// <summary>
/// Name of the plugin, should be an unique name
/// </summary>
string Name { get; }
/// <summary>
/// Version of the loaded plugin
/// </summary>
Version Version { get; }
/// <summary>
/// Entry controller name
/// </summary>
string EntryControllerName { get; }
/// <summary>
/// Installs the plugin with all the scripts, css and DI
/// </summary>
void Install();
}
AgendaModule
的安装方法实现如下所示:
public class AgendaModule : IModule
{
....
public void Install()
{
var container = ServiceLocator.Current.GetInstance<IWindsorContainer>();
container.Kernel.AddFacility<WcfFacility>();
container.Register(Component.For<IAgendaService>()
.ActAs(new DefaultClientModel
{
Endpoint = WcfEndpoint.BoundTo(new BasicHttpBinding())
.At("https://:32610/AgendaService.svc")
}));
container.Register(Component.For<AgendaPrefill>());
container.Resolve<AgendaPrefill>().Prefill();
}
}
如上例所示,IAgendaService
接口被注册为正确 WCF 端点的代理。我想强调的是,这是一个实现细节和一种提议,您也可以在此处注册存储库和其他业务特定服务,如果您愿意,您可以采取的方法高度取决于需求。
插件引导程序
在代码中,PluginBootstrapper
位于 Calendar.Infrastructure.PluginManager
命名空间下,它负责确保所有插件在 ApplicationStart
时都正确安装。如第一篇文章中所述,在 BeforeApplicationStart
中,所有插件都存储在一个静态集合中,PluginBootstrapper
将枚举并注册这些插件。这是代码:
public static class PluginBootstrapper
{
public static void Initialize()
{
foreach (var module in PluginManager.Current.Modules.Keys)
{
var assembly = PluginManager.Current.Modules[module];
ApplicationPartRegistry.Register(PluginManager.Current.Modules[module]);
//Calling install on module, to register dependencies
module.Install();
// Controllers registration from the plugin assembly
var container = Microsoft.Practices.ServiceLocation.ServiceLocator.Current
.GetInstance<Castle.Windsor.IWindsorContainer>();
container.Register(Castle.MicroKernel.Registration.AllTypes
.Of<System.Web.Mvc.IController>()
.FromAssembly(assembly).LifestyleTransient());
}
}
}
如上所示,这是一个标准的 DI 注册,并为每个注册的插件调用 Install()
方法。然后,在 ApplicationStart
时调用 PluginBootstrapper.Initialize()
方法,此时应用程序已设置并准备就绪。
protected void Application_Start()
{
//Install web app
Bootstrapper.Install();
//Install all plugins
PluginBootstrapper.Initialize();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
AgendaPlugin
还包含更多代码,主要是所需的 AgendaController
、模型和视图。控制器(基本上是最重要的)现在依赖于 IAgendaService
,并且主要执行虚拟的 Web 服务调用。简化后的代码:
public class AgendaController : Controller
{
IAgendaService _service;
public AgendaController(IAgendaService service)
{
_service = service;
}
[HttpGet]
public ActionResult Index(DateTime? startDate, int dayCount = 1)
{
var agendas = _service.GetAgendaItems(...)
...
}
[HttpDelete]
public ActionResult Delete(Guid id)
{
_service.DeleteAgenda(...)
}
[HttpPost]
public ActionResult Update(UpdateAgendaDto agenda)
{
_service.UpdateAgenda(...)
}
[HttpPut]
public ActionResult Insert(CreateAgendaDto agenda)
{
_service.CreateAgenda(...)
}
}
基本上,这就是大部分内容,您到目前为止所阅读的内容应该能让每个人都理解事物的工作原理。如果您到目前为止还没有感到无聊,接下来的章节将展示 Web 服务的结构。
10,000 英尺概览:插件初始化图
Web 服务托管和业务层
正如我之前所说,这只是一种实现方式,完全取决于开发主管。在某些情况下,这对于一些较小的项目来说可能有点过于复杂。
服务托管
服务托管项目将只包含 *.svc 文件。契约和实现是在业务逻辑层定义的。托管项目是 Calendar.WebServices.Hosting,其结构如下:
AgendaService.svc 使用 Windsor Castle 配置如下:
<%@ ServiceHost Language="C#" Debug="true"
Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory, Castle.Facilities.WcfIntegration"
Service="AgendaService" %>
AgendaService 也通过 WcfFacility
在 Web 服务引导程序中配置。
服务接口和实现
服务接口包含日程所需的所有业务操作。
namespace Calendar.BusinessLogic.Agenda
{
[ServiceContract(Namespace = "http://www.calendarservieces.com/agendaservice/")]
public interface IAgendaService
{
[OperationContract]
GetAgendaResponse GetAgenda(GetAgendaRequest request);
[OperationContract]
GetAgendaItemsResponse GetAgendaItems(GetAgendaItemsRequest request);
[OperationContract]
void CompleteAgendaItem(CompleteAgendaItemRequest request);
[OperationContract]
void UncompleteAgendaItem(UncompleteAgendaItemRequest request);
[OperationContract]
CreateAgendaResponse CreateAgenda(CreateAgendaRequest request);
[OperationContract]
void UpdateAgenda(UpdateAgendaRequest request);
[OperationContract]
void DeleteAgenda(DeleteAgendaRequest request);
}
}
然后,每个操作都由服务实现中的特定处理程序处理。每个操作都有一个处理程序,并通过 IHandlerExecuter
路由到所需的处理程序。这样,服务的实现保持简单、解耦且易于测试:
public class AgendaService : IAgendaService
{
private readonly IHandlerExecuter _executer;
public AgendaService(IHandlerExecuter executer)
{
_executer = executer;
}
public GetAgendaItemsResponse GetAgendaItems(GetAgendaItemsRequest request)
{
return _executer.Execute<GetAgendaItemsRequest, GetAgendaItemsResponse>(request);
}
....
public void UpdateAgenda(UpdateAgendaRequest request)
{
_executer.Execute(request);
}
}
处理程序是处理业务请求的组件。例如,UpdateAgendaRequest
由 UpdateAgendaHandler
处理并进行所需的业务更改,它可以处理一个或多个存储库或一个或多个业务服务,这完全取决于所需的业务操作。这是代码:
namespace Calendar.BusinessLogic.Agenda.Impl.Handlers
{
public class UpdateAgendaHandler: IHandler<UpdateAgendaRequest>
{
private readonly IAgendaRepository _agendaRepository;
public UpdateAgendaHandler(IAgendaRepository agendaRepository)
{
_agendaRepository = agendaRepository;
}
public void Execute(UpdateAgendaRequest request)
{
Domain.Entities.Agenda agenda = _agendaRepository.GetById(request.Id);
agenda.Title = request.Title;
....
_agendaRepository.SaveOrUpdate(agenda);
}
}
}
领域和数据层
领域是系统的核心。在这里,所有业务都被转化为代码。我不会深入探讨这个话题,因为关于领域驱动设计已经有很多可用的材料。正如我在上一章中所说,处理程序进行业务更改,这些更改通常通过领域,所有业务规则都在领域中实现。以下示例展示了小型 Calendar 领域和数据层的结构:
领域实体通过存储库持久化。存储库的实现在数据访问层完成,在此示例中,它是 MemoryCache 实现。在实际应用程序中,存储库的实现最可能使用数据库。
使用代码
代码应该很容易使用。首先,确保您已经安装了 Visual Studio 2012 并安装了 RazorSingleFileGenerator Visual Studio 2012 扩展。您可以在归档文件中的 $/ThirdPartyLibraries/MVC/RazorSingleFileGenerator.vsix 下找到它。下一步是打开解决方案并重新构建它,解决方案位于 $/main/VisualStudioSolutions 下。所有引用都应该已经解析,如果不是,第三方引用应该位于 $/ThirdPartyLibraries/ 下。
所有插件程序集都设置为直接编译到 $/WebHosting/Web/Calendar.Web/plugins,因此如果您想测试可插件功能,您应该将所有插件程序集编译到不同的文件夹,然后从插件文件夹中删除一些程序集。