Thursday, 20 April 2017

ASP.NET MVC 4 pluggable application modules

History
This article is an updated version of the “ASP.NET MVC - Pluggable application modules/components/area/features” article written by Nilesh Hirapra. I found the original article it to be incredibly useful. However, ASP.NET MVC 4 was released, and I found following the original article (which relied on ASP.NET MVC 3) required some adjustments to make it work correctly with the updated framework. This article is an updated version of Nilesh’s original work with ASP.NET MVC 4 specific changes, as well as some extra screenshots, inline code, and some readability and grammatical improvements. I would like to acknowledge and extend a big thanks to Nilesh for providing the original text and the guidance for this useful modularization technique.

Before you begin

This article assumes you are using:
  • Visual Studio 2012
  • ASP.NET MVC 4
  • ASP.NET and Web Tools 2012.2 update
  • C#

Scenario

When a web application has many modules (or sub-applications, features, or whatever your business calls them) and gets larger the more modules you plug in, the harder the code base is to partition and maintain.

Solution

It becomes apparent that these modules should be developed independently without any direct dependencies (other than CSS, layouts, and JavaScript libraries) on the main application code. The development team desires a modular solution, one that is easily maintained over time and separates these larger modules from each other. Ideally, once each module is ready for integration, they could be then plugged into the main application with little to no adjustment of the main application.
This article discusses how to develop ASP.NET MVC 4 applications requiring these loosely coupled modules, but aren’t part of the main application code.

Business Value

This is specifically useful for product development. Using this approach, each module may be developed and deployed/shipped for the product separately. This helps in building different versions of the product, like basic, professional, premium, and enterprise versions. This loosely coupled modular approach enables product developers to create the separation needed to either build separate installers with the required features for each different version, a master setup which can only install defined features as per license key used during installation, or some other configuration requiring the modular separation technique.

Building the application

Suppose we want to build a product which has following modules:
1.        Marketing
2.        Sales
3.        Billing
4.        Inventory
5.        Warehouse
We want to build a product which allows selling/distribution/deployment of each above modules as a separate feature. Typically such products will have some basic features/infrastructure and these modules will fit on top of that. Let’s assume we have the basic application with a master page, landing page, authentication, security, logging and related resources. We will build an application with the pluggable modules as listed above.

Setting up the main application project

Open Visual Studio 2012 and create an ASP.NET MVC 4 project with a Basic template. (I have named the project/solution as ProductDemo in this example):
image
Once it’s created, your solution should look like this:
image
Next, right-click on the Controllers folder and select “Add -> Controller...”. Give it the name HomeController, and select “Empty MVC Controller” for the template:
image
Right-click on the Index method of the controller and select “Add View…”. Using the defaults, create the Index view for HomeController to create the landing page for the application:
image

Pluggable modules menu items and expected output

To prepare the application with its menu items pointing to the expected modules, edit _Layout.cshtml (in the Views\Shared folder) like so:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div>
        <div>
            <h2>Product Demo - Pluggable Modules</h2>
        </div>
        <div id="nav">
            @Html.ActionLink("Home","Index","Home"new {Area=""}, null) |
            @Html.ActionLink("Marketing","Index","Marketing"new {Area="Marketing"}, null) |
            @Html.ActionLink("Billing","Index","Billing"new {Area="Billing"}, null) |
            @Html.ActionLink("Inventory","Index","Inventory"new {Area="Inventory"}, null) |
            @Html.ActionLink("Warehouse","Index","Warehouse"new  {Area="Warehouse"}, null)
        </div>
        <hr />
        <div>
                @RenderBody()
        </div>
    </div>
    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>
Running the application, the output now looks like this:
image

Creating Areas folder structure and get sample of XXXXAreaRegistration file

Now right-click on the main application project title in the Solution Explorer and select “Add->Area…” from the context menu. This step will create the basic folder structure for the Area framework. Specify “Marketing” as Area name and click “Add”:
image
You should have a folder solution structure that looks like this:
image
Now take a backup of the MarketingAreaRegistration.cs file since we are going to delete the Marketing folder in the next step (copy it to the root of your application for now, Desktop, or a temporary folder). This file will be needed when we add the Marketing module to the solution as a separate project. (This file can also be created manually once you know how to make one yourself).
Now remove the Marketing subfolder from the Areas folder, keeping the Areas folder in place.

Adding pluggable module/area to main application

Next, to add the new pluggable module for “Marketing”, right-click on the solution and choose “Add-> New Project…”. In the dialog box do following:
image
  • Select ASP.NET MVC 4 Application
  • Name project as ‘Marketing’
  • Set Location as “…\ProductDemo\Areas\”
A new project should be created in the Areas folder we created. Create the project using the Basic template:
image
Note: there appears to be a bug that may cause an error when creating this project using the Visual Basic MVC4 templates. Ignoring the error doesn’t seem to be a problem however, but I can’t guarantee that it won’t have some impact. YMMV.
In the newly created Marketing project, remove the following:
  • App_Data
  • App_Start
  • Content folder
  • Shared sub-folder under View
  • global.asax file
These are added as part of the Basic MVC 4 project template but not needed for us since we are creating this as a pluggable module.  After this step, the solution should look like following:
image
Since we saved new project marketing under the Areas folder, it is appearing as hidden folder under the ProductDemo/Areas folder in the Solution Explorer:
image
Adjust the ProductDemo’s RouteConfig.cs file to properly add the correct namespace overload to the context.MapRoute() method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace ProductDemo
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default",
                "{controller}/{action}/{id}",
                new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                new string[] { "ProductDemo.Controllers" }
            );
        }
    }
}
Add your backed up MarketingAreaRegistration.cs file to the root in the Marketing project and change its namespace to use the ProductDemo namespace in the ProductDemo project, like below:
using System.Web.Mvc;

namespace ProductDemo
{
    public class MarketingAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "Marketing";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Marketing_default",
                "Marketing/{controller}/{action}/{id}",
                new { controller = "Marketing", action = "Index", id = UrlParameter.Optional },
                new string[] { "Marketing.Controllers" });
        }
    }
}
Also note above that the context.MapRoute() method is also using the overloaded version of the method which accepts the namespace of the controller to use.
Now set the output directory of Marketing project to ..\..\bin\ so that its compiled DLLs are placed in the bin directory of the ProductDemo application:
image
In the Marketing project, modify web.config file to remove the connection strings, authentication, membership, rolemanager, profile, and session state, sections.
Now create the Marketing controller (you can have make any other controller as well) in the Marketing project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Marketing.Controllers
{
    public class MarketingController : Controller
    {
        //
        // GET: /Marketing/

        public ActionResult Index()
        {
            return View();
        }

    }
}
Create an Index view and place the content “Welcome to the Marketing Module” in its header:
@{
    ViewBag.Title = "Index";
}

<h2>Welcome to the Marketing Module</h2>

The ProductDemo application is now ready to use with the Marketing module. Build the solution. After building, take a look at the ProductDemo\bin folder and you will see the compiled DLLs for the Marketing module located in there:
image
Next, run the Product Demo application to see that it is working. In the browser, click the Home and Marketing links to see how the application is switching back and forth between the ProductDemo application shell page and the Marketing module page, each using their respective controllers.
ProductDemo index using the ProductDemo.Controllers.Home controller:
image
Marketing index using the Marketing.Controllers.Marketing controller:
image
Repeat the process to create the rest the modules.
That’s it!

How does this work?

The ASP.NET MVC Areas structure enables creating separate logical modules and those still resides in same project and binary. Notice xxxAreaRegistration.cs in the Marketing project which is inherited from AreaRegistration. This file tells ASP.NET MVC 4 and the Routing framework the Area name and Route for accessing the Marketing Area and its controller actions. The Global.asax.cs in the ProductDemo main application registers Route information for the application, by way of RouteConfig.cs. This registraton also registers all Areas defined in all assemblies present in application bin directory. Since we have directed the output of the Marketing module to the ProductDemo main application bin directory, the Marketing module Area gets picked up and is “automagically” included.
From a rendering perspective, the main application ProductDemo has a master page (_Layout.cshtml) which is in the Views/Shared directory in the ASP.NET application. As per ASP.NET and the MVC 4 Framework, this layout is inherited to its all sub directories. This way even if the Marketing module is a separate project, it inherits the master page of the main application. The same applies to all other resources like CSS files, images, JavaScript libraries, and other static resources or pages.

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...