Introduction
This article presents a simple example on dependency injection with StructureMap
in ASP.NET MVC.
Background
"Dependency injection" (DI) in object-oriented computer programming is a technique that indicates to a part of a program which other parts it can use, i.e. to supply an external dependency (i.e. a reference) to a software component. In technical terms, it is a design pattern that separates behavior from dependency resolution, thus decoupling highly dependent components. "StructureMap" is a "Dependency Injection"/"Inversion of Control" tool for .NET that can be used to improve the architectural qualities of an object oriented system by reducing the mechanical costs of good design techniques. StructureMap
can enable looser coupling between classes and their dependencies, improve the testability of a class structure, and provide generic flexibility mechanisms. Used judiciously, StructureMap
can greatly enhance the opportunities for code reuse by minimizing direct coupling between classes and configuration mechanisms.
This article is to present a simple example on dependency injection with StructureMap
in ASP.NET MVC.
The "ASP.NET MVC 2" web application project "StructureMapMVC
" in the attached Visual Studio solution has the following major components:
- The "ApplicationModel.cs" implements the application's data model.
- The "IStudentsGenerator.cs" implements an "interface" which has two different "concrete" implementations implemented in "BadStudentsGenerator.cs" and "GoodStudentsGenerator.cs". The purpose of "StructureMap" is to provide us a framework that we can use to easily "inject" one of the "concrete" classes into the application. "StructureMap" also provides us a simple method to configure which "concrete" class to "inject". The configuration can be done in a simple "xml" file.
- The "HomeController.cs" implements the "MVC" application's "Controller".
- The application's main web page is implemented in the "MVC View" "Index.aspx", which will access the "ViewUserControl" "LoadStudents.ascx" through "jQuery" "ajax" calls.
- The "Bootstrapper.cs" implements some initialization code required by "StructureMap" in the "ASP.NET MVC" environment, which will be called in the "
Application_Start
" event in the "Global.asax".
- The "StructureMap.xml" is the place where we can configure which "concrete" class to "inject" into the application.
This "ASP.NET MVC 2" web application is developed in Visual Studio 2010. The version of the "jQuery" is "1.4.4". In order to use "StructureMap", you need to download the "StructureMap.dll" and add a reference to this "DLL" from your project. The version of the "StructureMap.dll" used in this article is "2.6.1". In this article, I will first introduce the data model of this application and then introduce the "IStudentsGenerator
" "interface" as well as the two "concrete" classes that implement this "interface". After that, I will introduce the "Controller" class "HomeController
", the "View" "Index.aspx", and the "ViewUserControl" "LoadStudents.ascx". At last, I will introduce how we can set up and configure "StructureMap" for "Dependency injection".
This article assumes that the readers have some basic knowledge on "Dependency injection" and "StructureMap". If you are not familiar with these topics, I will recommend you to take a look at the web site of "StructureMap" first. Except adding "Dependency injection" with "StructureMap", the attached web project is exactly the same as the one in my earlier article "Load Clean HTML Fragments using jQuery and MVC". If you take a look at that article, it will make your reading of this article much easier. If you do not have the time, it is OK. I have made my effort to make this article self-contained.
Now let us take a look at the data model of the attached "MVC" project.
The Data Model
The data model in this application is implemented in the "ApplicationModel.cs" file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace StructureMapMVC.Models
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Score { get; set; }
public string Activity { get; set; }
}
}
This is probably the simplest data model for any applications. It has only one class "Student
". This web application will be using this class to build a "List" of "Student
" objects to be displayed in the web browser.
The "interface' "IStudentsGenerator" and the "concrete" Implementations
The "interface" "IStudentsGenerator
" is implemented in the "IStudentsGenerator.cs" file:
using System;
using System.Collections.Generic;
using StructureMapMVC.Models;
namespace StructureMapMVC.Modules.Interface
{
public interface IStudentsGenerator
{
List<Student> GenerateStudents(int NoOfStudents);
}
}
This "interface" defines a method "GenerateStudents
", which takes an integer input "NoOfStudents
" and returns a "List" of "Student
" objects. The "concrete" class "GoodStudentsGenerator
" is one of the implementations of this "interface":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using StructureMapMVC.Modules.Interface;
using StructureMapMVC.Models;
namespace StructureMapMVC.Modules.Concrete
{
public class GoodStudentsGenerator : IStudentsGenerator
{
public List<Student> GenerateStudents(int NoOfStudents)
{
List<Student> students = new List<Student>();
Random rd = new Random();
for (int i = 1; i <= NoOfStudents; i++)
{
Student student = new Student();
student.ID = i;
student.Name = "Name No." + i.ToString();
student.Score = Convert.ToInt16(80 + rd.NextDouble() * 20);
student.Activity = "Working - Good";
students.Add(student);
}
return students;
}
}
}
As what the class name "GoodStudentsGenerator
" indicates, the "List" of the students generated by the "GenerateStudents
" method in this class have scores between 80 and 100 and their "Activity
" is "Working
". Another "concrete" implementation of the "interface" "IStudentsGenerator
" is the "BadStudentsGenerator
" class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using StructureMapMVC.Modules.Interface;
using StructureMapMVC.Models;
namespace StructureMapMVC.Modules.Concrete
{
public class BadStudentsGenerator : IStudentsGenerator
{
public List<Student> GenerateStudents(int NoOfStudents)
{
List<Student> students = new List<Student>();
Random rd = new Random();
for (int i = 1; i <= NoOfStudents; i++)
{
Student student = new Student();
student.ID = i;
student.Name = "Name No." + i.ToString();
student.Score = Convert.ToInt16(40 * rd.NextDouble());
student.Activity = "Gambling - Bad";
students.Add(student);
}
return students;
}
}
}
The "List" of the students generated by this class have scores between 0
and 40
and their "Activity
" is "Gambling
". In this web application, the "Controller" "HomeController
" will only hold a reference to the "interface" "IStudentsGenerator
". The responsibility of "StructureMap" is to create a "concrete" instance of either the "GoodStudentsGenerator
" class or the "BadStudentsGenerator
" class according to the configurations in the "StructureMap.xml" file and "inject" them into the "HomeController
".
Let us now take a look at the "HomeController
", the "View" "Index.aspx", and the "ViewUserControl" "LoadStudents.ascx".
The Controller and the UI of the Web Application
The "HomeController
" is the only "Controller" in this "MVC" application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using StructureMap;
using StructureMapMVC.Models;
using StructureMapMVC.Modules.Interface;
namespace StructureMapMVC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private IStudentsGenerator studentGenerator;
public HomeController(IStudentsGenerator studentGenerator)
{
this.studentGenerator = studentGenerator;
}
[HttpGet]
public ActionResult Index() { return View(); }
[HttpGet]
public ActionResult LoadStudents(int NoOfStudents)
{
return View(studentGenerator.GenerateStudents(NoOfStudents));
}
}
}
Two "ActionResult" methods are implemented in this controller. The "Index
" method is used to load the "Index.aspx" page and the "LoadStudents
" method is used to load the "ViewUserControl" "LoadStudents.ascx". This controller class has a private
member variable "studentGenerator
" of type "IStudentsGenerator
" and a "constructor" that takes an input parameter of type "IStudentsGenerator
". This "constructor" is the place where "StructureMap" "injects" a "concrete" instance of either the "GoodStudentsGenerator
" class or the "BadStudentsGenerator
" class. Once a "concrete" "IStudentsGenerator
" instance is "injected", the "LoadStudents
" method can then use it to generate a "List
" of "Student
" objects and pass it over to the strong typed "ViewUserControl" "LoadStudents.ascx", which is implemented as the following:
<%@ Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Student>>" %>
<table cellspacing="0px" cellpadding="4px">
<tr>
<th>ID</th><th>Name</th><th>Score</th><th>Activity</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td><%: item.ID %></td>
<td><%: item.Name %></td>
<td><%: item.Score %></td>
<td><%: item.Activity %></td>
</tr>
<% } %>
</table>
The purpose of "LoadStudents.ascx" is to generate a clean HTML "table" fragment to display the information of the students passed from the "LoadStudents
" method in the "HomeController
". This HTML "table" fragment is loaded and displayed by "Index.aspx".
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Use StructureMap in MVC</title>
<link rel="stylesheet"
href="<%: Url.Content("~/Content/Styles/Site.css") %>"
type="text/css" />
<script src='<%: Url.Content("~/Content/Scripts/jquery-1.4.4.min.js") %>'
type="text/javascript">
</script>
</head>
<body>
<div id="MainContainer">
<div id="InputTrigger">
<span>Please select the number of the students to retrieve</span>
<span>
<select id="selNoOfStudents">
<option>***</option>
<option>1</option>
<option>5</option>
<option>10</option>
<option>20</option>
<option>100</option>
</select>
</span>
<img id="imgGetStudents"
src="<%: Url.Content("~/Content/Images/arrow.png") %>"
alt="" />
</div>
<div id="HTMLContent"></div>
</div>
</body>
</html>
<script type="text/javascript">
var aquireStudentsURL = var ajaxLoadImgURL = $(document).ready(function () {
var $MainContent = $("#HTMLContent");
var $selNumberOfStudents = $("#selNoOfStudents");
var ajaxLoadImg = "<img src='" + ajaxLoadImgURL + "' alt='Loading ...'/>";
$("#imgGetStudents").click(function (e) {
e.preventDefault();
var numberOfStudents = $selNumberOfStudents.val();
if (numberOfStudents == "***") {
alert("Please select the number of the students to retrieve");
return;
}
var resourceURL = aquireStudentsURL
+ "?NoOfStudents=" + numberOfStudents;
$MainContent.html(ajaxLoadImg);
$.ajax({
cache: false,
type: "GET",
async: false,
url: resourceURL,
dataType: "text/html",
success: function (htmlFragment) {
$MainContent.html(htmlFragment);
},
error: function (xhr) {
alert(xhr.responseText);
}
});
});
$selNumberOfStudents.change(function () {
$MainContent.html("");
});
});
</script>
The "Index.aspx" page is a simple "view" page. The major HTML components are the following:
- The "<select>" control "
selNoOfStudents
" allows the user to select the number of the students to retrieve from the "ViewUserControl" "LoadStudents.ascx".
- The "<img>" control "
imgGetStudents
" displays the picture "arrow.png". This picture is a green arrowhead, which is used as a "button" in this application. Clicking on this picture will trigger an "ajax" call to load the HTML fragment generated from "LoadStudents.ascx" according to the number of the students from the "selNoOfStudents
" dropdown box.
- If the "ajax" call is successful, the received HTML fragment is inserted into the "<div>" "
HTMLContent
".
The "javascript
" section of "Index.aspx" is also very simple. In the "$(document).load()
" event, the click event of the "<img>" "imgGetStudents
" is registered. When the "imgGetStudents
" is clicked, the JavaScript code first obtains the number of the students to retrieve from the "<select>" control "selNoOfStudents
" and then issues the "ajax" call to the server. Upon receiving of the HTML fragment, the content in the "<div>" "HTMLContent
" is updated to show the information of the received students.
Configure "StructureMap" for "Dependency Injection"
Finally, we come to the place where we can talk about the "Dependency injection". In order to set up the "Dependency injection" using "StructureMap" in this "ASP.NET MVC" application, I wrote the following code in the "Bootstrapper.cs" file:
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;
using System.Collections.Specialized;
namespace StructureMapMVC
{
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController
GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
try
{
if ((requestContext == null) || (controllerType == null))
return null;
return (Controller) ObjectFactory.GetInstance(controllerType);
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());
throw new Exception(ObjectFactory.WhatDoIHave());
}
}
}
public static class Bootstrapper
{
public static void Run()
{
ControllerBuilder.Current
.SetControllerFactory(new StructureMapControllerFactory());
ObjectFactory.Initialize(x =>
{
x.AddConfigurationFromXmlFile("StructureMap.xml");
});
}
}
}
The "StructureMapControllerFactory
" class overrides the "DefaultControllerFactory", so the "Controllers" in this "MVC" application is instantiated by the "StructureMap's" "ObjectFactory.GetInstance()
" method. The "Run
" method in the static
class "Bootstrapper
" sets the "MVC" application's "ControllerFactory
" to "StructureMapControllerFactory
" and tells "StructureMap" that it should go for the "StructureMap.xml" file for the configuration information.
The "Run
" method is called in the application's "Application_Start
" event in the "Global.asax.cs" file:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
Bootstrapper.Run();
}
The configuration for "StructureMap" is written in the "StructureMap.xml" file:
="1.0" ="utf-8"
<StructureMap>
<DefaultInstance
PluginType="StructureMapMVC.Modules.Interface.IStudentsGenerator,
StructureMapMVC"
PluggedType="StructureMapMVC.Modules.Concrete.GoodStudentsGenerator,
StructureMapMVC" />
</StructureMap>
According to the document on the "StructureMap" web site, the "PluginType
" is the fully qualified name of the "interface" and the "PluggedType
" is the fully qualified name of the "concrete" class to be "injected". In the above configuration, we will "inject" the "GoodStudentsGenerator
".
Now, we finish the development of the example application and we can test run it.
Run the Application
When the application first runs, the "Index.aspx" page is loaded. We can select the number of the students to retrieve and click the green arrow head to load the students.
Since we have configured to use "GoodStudentsGenerator
" to create the students, we get a list of "good" students.
We can make chance to the "StructureMap.xml" file to use the "BadStudentsGenerator
":
="1.0" ="utf-8"
<StructureMap>
<DefaultInstance
PluginType="StructureMapMVC.Modules.Interface.IStudentsGenerator,
StructureMapMVC"
PluggedType="StructureMapMVC.Modules.Concrete.BadStudentsGenerator,
StructureMapMVC" />
</StructureMap>
Restart the application, select the number of the students to retrieve and click the green arrow head again, we get a list of "bad" students.
Points of Interest
- This article presents a simple example on dependency injection with
StructureMap
in ASP.NET MVC
- According to "StructureMap" web site, it is not always a good idea to use "Dependency injection" in your application. When the application is small, it is a good idea to keep it simple to avoid the complexity of "Dependency injection". When the application is large, it is even more important to keep the building blocks simple. When and how to use "Dependency injection" is your judicious judgment.
- The ideal place to use "Dependency injection" is when certain building blocks of the application naturally have multiple implementations and you need to choose which implementation to use. "StructureMap" is a good framework to help you make this configuration.
- If you take a look at the "
HomeController
", you may notice that a concrete implementation of the "IStudentsGenerator
" interface is "injected" through the constructor. You can actually call the "GetInstance
" method to obtain the desired concrete object directly. If you choose this way, you do not need to use the "StructureMapControllerFactory
" to instantiate your "MVC" "Controllers".
- The configuration of the "Dependency injection" can be done both with code and in an XML file. Each way has its pros and cons. In this article, I chose to use an XML file. The reason is primarily because configuring "Dependency injection" in code is straightforward and well documented, but putting the configuration in an XML file is a little fuzzy. But using an XML file does have some advantages in some sense, since it clearly separates the concerns between coding and configuration. In this article, I used an XML file for the purpose to keep a record for future reference.
History
- This is the first revision of this article.