Tallan's Technology Blog

Tallan's Top Technologists Share Their Thoughts on Today's Technology Challenges

Multi Tenant Architecture via Dependency Injection: Part 3

In Part 1 we discussed web application Multi Tenant Architecture, and briefly discussed how dependency injection can allow us to manage our client customizations. In Part 2, we explored how to wire up Ninject to our ASP.Net MVC application and begin to use its dependency injection features.

In Part 3 we will look at how to utilize Ninject’s features within our application, along with what kind of functionality can be delivered. At this point, let’s revisit our goals from Part #1:

  1. dynamically set which service object(s) we are using to perform our application logic
  2. dynamically set which view we are returning
  3. dynamically set which partial views we are using

Hypothetically, let our application be a standard transactional system, with header records at the top level and child records below it. Furthermore, let it be an order entry system, so we will need to know about quantities, prices, have subtotals, etc. Our standard interface can be for Work Orders, and look something like the following:

WorkOrders.aspx:
Sample Work Orders Screenshot
We have some header information (Work Order #, etc.) and child records, and the ability to interact with the application to modify child records and add new ones. At this point, a large customer likes what you’ve done so far and wants to use your application to manage their shop. But instead of work orders, they need to use Purchase Orders. You need to make some major modifications without affecting your existing functionality, so you create a new page and use Ninject to inject the right functionality into the application. Because we implemented Goal #2 in Part 2, we merely need to modify our source repository to provide the PurchaseOrders.aspx page instead of WorkOrders.aspx.

From CompanyConfigProvider.cs:

                // construct our list of Views to be used throughout the application
                // for Goals #2 and #3
                foreach (KeyValuePair<string, string> item in r.getViews())
                {
                    cc.Views.Add(item.Key, item.Value);
                }

In addition, because these Purchase Orders are between your client and their own customers and not intra-company Work Orders, there are potential tax implications — as such, you will need to introduce a method of keeping track of tax when necessary. It will utilize the existing Purchase Orders functionality, but will need to augment it — the perfect usage of partial views.

TotalNoTax.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

Non-Profit Tax Code: <input name="taxinfo" type="text" />

TotalWithTax.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

Tax Amount: <input id="taxinfo" name="taxinfo" type="text" />

<script type="text/javascript">// <![CDATA[
    function updateTax() {
        var qty = parseFloat($("#quantity").val());
        $("#taxinfo").val(qty * 150 * .06);
    }

    $(document).ready(function() {
        $("#quantity").blur(updateTax);
        $("#itemType").change(updateTax);
    });
// ]]></script>

Very simply, we will use jQuery to manage a new form element that will include tax when necessary. Let’s start putting it all together. First, let’s take a look at our controller:

From OrdersController.cs:

using {etc}

namespace MultiTenantNinject.Controllers
{
    [HandleError]
    [ProvideCompanyConfig]
    public class OrdersController : Controller
    {

As you can see, we inherit from Controller like normal and utilize our ProvideCompanyConfig attribute so that we can be assured we have access to our company config upon rendering. Let’s take a look at an Action:

        public ActionResult Index()
        {
            ViewResult v = View(_companyConfig.Views["Order Management"]);

			// add view data items for the header and children;

			// add item to be used for determining if it's a taxable or non-taxable transaction:
			v.ViewData.add("taxState",repository.TaxState);

            return v;

        }

The first step in rendering the View to the user is ensuring we have an “Order Management” entry in our Views collection that establishes what type of major functionality to use. Our repository would load the config file with a text entry like “WorkOrders” or “PurchaseOrders” to correspond with “WorkOrders.aspx” or “PurchaseOrders.aspx”, respectively. We can set our ViewData at this point, perhaps using a service object (which we will explore soon) and then return the view. At this point, we have a solid understanding of achieving Goal #2, so let’s take a look at the few lines necessary to achieve Goal #3:

From PurchaseOrders.aspx:

    <% var configSettings = (MultiTenantNinject.Models.ICompanyConfig)ViewData["companyConfigSettings"];  %></pre>
<h3>Purchase Orders</h3>
<pre>
	... etc ...

<% Html.RenderPartial(configSettings.Views[string.Format("Taxes_{0}",ViewData["taxState"])]); %>

By utilzing the correct entry in our repository for the user’s state, we can provide the correct tax functionality. If the company’s location was in Florida, the repository could provide “TotalWithTax” for the “Taxes_FL” binding, and “TotalNoTax” for the other states. Obviously we can take it up a level of abstraction and determine whether or not this transaction is taxable in the controller or service object, but this serves as an adequate example of Goal #3. Lastly, let’s take a look at our solution for Goal #1 — it’s all well and good when we can provide custom front-end functionality for our users, but at some point we will need to implement logic and manipulate data. Let’s revisit the module where we inject our services:

From OrderModule.cs:

        public override void Load()
        {
            // use reflection to bind a list of types (from our Config's "Bindings" collection)
            // to be used when called upon during injection
            string serviceLocationPrefix = "MultiTenantNinject.Services.";
            foreach (KeyValuePair<string,string> item in _config.Bindings)
            {
                Bind(Type.GetType(serviceLocationPrefix + item.Key)).To(Type.GetType(serviceLocationPrefix + item.Value));
            }
        }

We will need to provide an interface and an implementation for our service(s). Firstly, the interface:

IOrderService.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Web.Mvc;

namespace MultiTenantNinject.Services
{
    public interface IOrderService
    {
        string Process(ViewDataDictionary viewdata);
        DataTable Orders { get; set; }
    }
}

and one of the implementations:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Web.Mvc;

namespace MultiTenantNinject.Services
{
    public class VideoCardOrderService: IOrderService
    {

        #region IService Members

        public string Process(ViewDataDictionary viewdata)
        {
            // here we do transactions pertaining to video cards
            return "Thanks for buying a video card!";
        }

        #region Orders
        private DataTable _Orders;
        public DataTable Orders
        {
            get
            {
                return _Orders;
            }
            set
            {
                if (_Orders == value) return;
                _Orders = value;
            }
        }
        #endregion

        #endregion

    }
}

Service objects can perform most of the functionality of your application, and do not need to be gone over in great detail, as they are as varied as anything else can be. Suffice it to say that it’s a standard interface/implementation pattern of development. Ninject allows you to set it up for injection, as we did in Part 2, so that whenever you call a particular interface for this user a particular implementation is used. “VideoCardOrderService” could easily be “MotherboardOrderService” or “CarOrderService”:

From OrdersController.cs:

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Save()
        {
            _orderService.Process(ViewData);
            return new RedirectResult("Index");
        }

Our controller, in the end, is completely agnostic of our major functionality, our minor (partial view) functionality, and our domain logic:

OrdersController.cs:

using {etc}
using Ninject;

namespace MultiTenantNinject.Controllers
{
    [HandleError]
    [ProvideCompanyConfig]
    public class OrdersController : Controller
    {
        ICompanyConfig _companyConfig;
        IOrderService _orderService;

        public OrdersController(ICompanyConfig pCompanyConfig, IOrderService pOrderService)
        {
            _companyConfig = pCompanyConfig;
            _orderService = pOrderService;
        }

        public OrdersController()
        {
        }

        public ActionResult Index()
        {
            ViewResult v = View(_companyConfig.Views["Order Management"]);

			// add view data items for the header and children;

			// add item to be used for determining if it's a taxable or non-taxable transaction:
			v.ViewData.add("taxState",repository.TaxState);

            return v;
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Save()
        {
            _orderService.Process(ViewData);
            return new RedirectResult("Index");
        }
    }
}

Thus, the conclusion to our series about utilizing dependency injection within ASP.Net MVC to enhance a Multi-Tenant Architecture. We can provide service objects without our controller (or anything else) performing logic switches, we can provide major changes to functionality without creating a new codebase, and we can provide in-place customizations truly in-place. In addition, we’ve created our application such that we can utilize any kind of data layer or repository and we can utilize testing frameworks to test our code without having to rewrite anything. And lastly, although it’s taken a moderate level of effort to get to this point, we can be secure in the knowledge that additional changes can be implemented without affecting existing functionality, and we can easily swap out functionality when the situation arises with very little additional effort.

Related Articles

2 Comments. Leave new

Well, there’s a stumbling block to this otherwise lovely bit of code.. if you use the HttpContext provided at startup, there is no Request object available to read a url, at least in Mvc 4. I’m scratching my head now as to what to do to be able to read a URL (subdomain etc), and set up CompanyConfig accordingly.. suggestions please..

Please don’t delete posts which show an inadequacy in your code, this is important for developers who will be using what you’ve posted. There is a workaround, but it is not simple, and requires a separate intiialization stage at the Application_BeginRequest phase. If your code presents problems, be good enough to post these drawbacks, as people may invest time in implementing what you’ve suggested, only to find it has caused further problems.

On a side note, you shouldn’t be using HttpContext in the App Start phase at all, as many of its features have yet to be initialized..

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\