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:
- dynamically set which service object(s) we are using to perform our application logic
- dynamically set which view we are returning
- 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:

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" %>
<p>Non-Profit Tax Code: <input type="text" name="taxinfo" /></p>
TotalWithTax.ascx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<p>Tax Amount: <input type="text" id="taxinfo" name="taxinfo" /></p>
<script type="text/javascript">
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"]; %>
<h3>Purchase Orders</h3>
... etc ...
<p><% 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.