Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET ASP.NET MVC plug-in architecture with embedded views - Part 2, Plugins server-side logic

0.00/5 (No votes)
8 Jul 2013 2  
How to handle database operations or any kind of business specific logic inside plugin assemblies.

Introduction

In the first part of the article I explained how to quickly build an MVC plugin architecture. Since then I had a lot of questions from people about how to proceed with database calls, or embedded resources management, or other things necessary to know when you plan to design this kind of an application. In part 2, I will describe mostly a proposition about how to handle database operations or any kind of business specific logic inside plugin assemblies. 

Background

Usually every small, medium, or large application is structured, as a big picture, in three tiers: Data Access, Business Logic, and User Interface. Every tier should be decoupled as much as possible for maintainability and unit testing reasons. Most of us use some patterns and architecture principles like ORM, Repositories, Business Entities, Business Services, Inversion of control, etc. Mostly these are old and proven principles that every architect knows even in his dreams. Some of us sometimes also try to do more like DDD, or maybe CQRS, when time, resources, and complexity allows it.   

In the case of this MVC plugin approach I tried to keep the plugins very light in reference to other assemblies, this is why I preferred to keep the business logic behind web-services and opted for a SOA architecture. This was also a business decision, because it is much more comfortable to deliver fewer assemblies to clients. Here is where my preferred Dependency Injection framework, Castle Windsor, helped a lot with the Windsor WcfFacility, which basically allowed to register the service interfaces to the DI container as proxies to the actual web-service; this was extremely helpful because the service references were always up to date and the code is not aware that the implementation is a service proxy.

For the code samples, I chose to extend the Agenda plugin from part 1 with Get+CRUD functionality, almost as a normal agenda application. All the objects are kept in memory, there is no database to keep the application simple, the repository implementation does all the persistence in memory. Let's see some code samples. 

Different folder structure 

Firstly the example from part 1 was renamed to Calendar and split in the following layers: 

  • Infrastructure - Very generic interfaces and application wide infrastructure components.
  • DataAccess -  Persistence layer, in this case MemoryCache is used, usually database access is done here.
  • BusinessLogic -  Domain Model and Assemblies for service contracts and implementation.
  • Web - web-service hosting, plugins, and web application.

Here is a quick view: 

Changes required to the plug-in system 

IModule interface and plug-in Module entry 

First of all it is required to do a modification to the IModule interface and a new method Install() is added. Every module will have here DI registration and other initialization code.

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();
}

The AgendaModule install method implementation looks like this:

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("http://localhost:32610/AgendaService.svc")
                               }));

        container.Register(Component.For<AgendaPrefill>());

        container.Resolve<AgendaPrefill>().Prefill();
    }
}

As you can see in the example above, the IAgendaService interface is registered as a proxy to the correct WCF end-point. I would like to insist that this is an implementation detail and a proposition, here you can also register repositories and other business specific services if you wish, the approach you can take is highly based on requirements.  

Plugins BootStrapper

In the code PluginBootstrapper is located under the Calendar.Infrastructure.PluginManager namespace and it takes care that all plugins are installed correctly on ApplicationStart. As explained in the first article, in BeforeApplicationStart, all the plugins are stored in a static collection, which PluginBootstrapper will enumerate and register. Here is the code:

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());
        }
    }
} 

As seen above, that is a standard DI registration and a call to the Install() method for each registered plugin. The PluginBootstrapper.Initialize() method is then called on ApplicationStart and at this point we have the application set up and ready to run.

protected void Application_Start()
{
    //Install web app
    Bootstrapper.Install();

    //Install all plugins
    PluginBootstrapper.Initialize();

    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

AgendaPlugin also contains more code, basically the AgendaController, Models, and Views required. The Controller, basically the most important, now has a dependency on IAgendaService and does mostly dummy web-service calls. The simplified code:

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(...)
    }
}  

Basically this is the most of it and what you have read until now should give everybody an understanding about how things work. If you didn't get bored until now, the next chapters will show how the web-service is structured. 

10,000 ft overview: Plugins initialization diagram 

Web-service hosting and Business Layer

As I said before this is just one way of implementing it, it is totally up to the development lead. In some cases this might prove a little over-complicated for some smaller projects. 

Service hosting  

The service hosting project will contain only the *.svc files. The contracts and the implementation are defined in the Business Logic layer. The hosting project is Calendar.WebServices.Hosting and this is the structure: 

The AgendaService.svc is configured using Windsor Castle like this:

<%@ ServiceHost Language="C#" Debug="true"
     Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory, Castle.Facilities.WcfIntegration"
     Service="AgendaService" %> 

AgendaService is also configured in the web-service Bootstrapper via WcfFacility.  

Service interface and implementation 

The service interface contains all the business operations that are necessary for the agenda:

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);
    }
}

Each operation is then handled by a specific Handler, in the service implementation. Each operation has a handler and it is routed by an IHandlerExecuter to the required handler. This way the implementation of the service is kept simple, decoupled, and easy to test: 

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);
    }
} 

A handler is a component that handles a business request. For example, UpdateAgendaRequest is handled by UpdateAgendaHandler and makes the required business changes, it can handle one or more repositories or one or more business service, it all depends on the business operation that is required. This is the code: 

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);
        }
    }
}

Domain and Data Layer

The Domain is the core of the system. Here all the business is translated into code. I am not going deeper into this topic because there is a lot of material already available about Domain Driven Design. As I said in the previous chapter, a Handler makes business changes, these changes usually go through the Domain, where all business rules are implemented. The following example shows how the small Calendar domain and Data Layer is structured:  

The Domain Entities are persisted through the repository. The repository implementation is done in the Data Access Layer and in this example, it is a MemoryCache implementation. In a real application the implementation of the repository will use, most probably, a database. 

Using the code

The code should be quite easy to use.  First make sure you already have Visual Studio 2012 and install the RazorSingleFileGenerator Visual Studio 2012 extension. You can find it in the archive under $/ThirdPartyLibraries/MVC/RazorSingleFileGenerator.vsix. The next step is to open the solution and rebuild it, the solution is under $/main/VisualStudioSolutions. All references should be already resolved, if not, third party references should be under $/ThirdPartyLibraries/

All the plugin assemblies are set to compile directly to $/WebHosting/Web/Calendar.Web/plugins, so if you want to test pluginable functionality, you should set all the plugin assemblies to compile to a different folder and then remove some assemblies from the plug-ins folder. 

Useful links  

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here