在 ASP.NET MVC 中使用 StructureMap 进行依赖注入
本文介绍在 ASP.NET MVC 中使用 StructureMap 进行依赖注入的简单示例。
引言
StructureMap
进行依赖注入的简单示例。背景
"依赖注入"(DI)在面向对象计算机编程中是一种技术,它指示程序的一部分可以使用哪些其他部分,即向软件组件提供外部依赖(即引用)。从技术上讲,它是一种将行为与依赖解析分离的设计模式,从而解耦高度依赖的组件。"StructureMap" 是一个用于 .NET 的"依赖注入" / "控制反转"工具,可用于通过降低良好设计技术的机械成本来改进面向对象系统的架构质量。StructureMap
可以实现类与其依赖之间的松耦合,提高类结构的测试能力,并提供通用的灵活性机制。谨慎使用 StructureMap
可以通过最小化类与配置机制之间的直接耦合来大大增加代码重用的机会。
StructureMap
进行依赖注入的简单示例。附带的 Visual Studio 解决方案中的 "ASP.NET MVC 2" Web 应用程序项目 "StructureMapMVC
" 包含以下主要组件:
- "ApplicationModel.cs" 实现应用程序的数据模型。
- "IStudentsGenerator.cs" 实现了一个"接口",该接口有两个不同的"具体"实现,分别在 "BadStudentsGenerator.cs" 和 "GoodStudentsGenerator.cs" 中实现。"StructureMap" 的目的是为我们提供一个框架,我们可以使用它轻松地将其中一个"具体"类"注入"到应用程序中。"StructureMap" 还为我们提供了一个简单的方法来配置要"注入"的"具体"类。配置可以在一个简单的 "xml" 文件中完成。
- "HomeController.cs" 实现 "MVC" 应用程序的"控制器"。
- 应用程序的主网页在 "MVC 视图" "Index.aspx" 中实现,该视图将通过 "jQuery" "ajax" 调用访问 "ViewUserControl" "LoadStudents.ascx"。
- "Bootstrapper.cs" 实现 "StructureMap" 在 "ASP.NET MVC" 环境中所需的某些初始化代码,这些代码将在 "Global.asax" 的 "
Application_Start
" 事件中调用。 - "StructureMap.xml" 是我们可以配置要"注入"到应用程序中的"具体"类的位置。
此 "ASP.NET MVC 2" Web 应用程序是在 Visual Studio 2010 中开发的。 "jQuery" 的版本是 "1.4.4"。为了使用 "StructureMap",您需要下载 "StructureMap.dll" 并从您的项目中添加对该 "DLL" 的引用。本文使用的 "StructureMap.dll" 版本是 "2.6.1"。在本文中,我将首先介绍此应用程序的数据模型,然后介绍 "IStudentsGenerator
" "接口" 以及实现此 "接口" 的两个"具体"类。之后,我将介绍"控制器"类 "HomeController
"、"视图" "Index.aspx" 以及"ViewUserControl" "LoadStudents.ascx"。最后,我将介绍如何为"依赖注入"设置和配置"StructureMap"。
本文假设读者对"依赖注入"和"StructureMap"有一定的基本了解。如果您不熟悉这些主题,我建议您先查看"StructureMap"的网站。除了使用"StructureMap"进行"依赖注入"外,附带的 Web 项目与我之前文章 "使用 jQuery 和 MVC 加载干净的 HTML 片段" 中的项目完全相同。如果您阅读了那篇文章,将更容易理解本文。如果您没时间,也没关系。我已经尽力使本文具有独立性。
现在让我们来看看附带的 "MVC" 项目的数据模型。
数据模型
此应用程序中的数据模型在 "ApplicationModel.cs" 文件中实现。
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; }
}
}
这可能是任何应用程序中最简单的数据模型。它只有一个类 "Student
"。此 Web 应用程序将使用此类来构建一个 "Student
" 对象"列表",以在 Web 浏览器中显示。
"IStudentsGenerator" 接口及其“具体”实现
"接口" "IStudentsGenerator
" 在 "IStudentsGenerator.cs" 文件中实现。
using System;
using System.Collections.Generic;
using StructureMapMVC.Models;
namespace StructureMapMVC.Modules.Interface
{
public interface IStudentsGenerator
{
List<Student> GenerateStudents(int NoOfStudents);
}
}
此"接口"定义了一个名为 "GenerateStudents
" 的方法,该方法接受一个整数输入 "NoOfStudents
" 并返回一个 "Student
" 对象"列表"。"具体"类 "GoodStudentsGenerator
" 是此"接口"的实现之一。
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();
// This generates integer scores between 80 - 100
student.Score = Convert.ToInt16(80 + rd.NextDouble() * 20);
student.Activity = "Working - Good";
students.Add(student);
}
return students;
}
}
}
正如类名 "GoodStudentsGenerator
" 所表明的,此类的 "GenerateStudents
" 方法生成的学生"列表"的得分在 80 到 100 之间,其 "Activity
" 为 "Working
"。"接口" "IStudentsGenerator
" 的另一个"具体"实现是 "BadStudentsGenerator
" 类。
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();
// This generates integer scores between 0 - 40
student.Score = Convert.ToInt16(40 * rd.NextDouble());
student.Activity = "Gambling - Bad";
students.Add(student);
}
return students;
}
}
}
此类生成的学生"列表"的得分在 0
到 40
之间,其 "Activity
" 为 "Gambling
"。在此 Web 应用程序中,"控制器" "HomeController
" 将只持有对"接口" "IStudentsGenerator
" 的引用。 "StructureMap" 的职责是根据 "StructureMap.xml" 文件中的配置,创建一个 "GoodStudentsGenerator
" 类或 "BadStudentsGenerator
" 类的"具体"实例,并将它们"注入"到 "HomeController
" 中。
现在让我们来看看 "HomeController
"、"视图" "Index.aspx" 和"ViewUserControl" "LoadStudents.ascx"。
Web 应用程序的控制器和 UI
"HomeController
" 是此 "MVC" 应用程序中唯一的"控制器"。
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)
{
//studentGenerator
// = ObjectFactory.GetInstance<IStudentsGenerator>();
return View(studentGenerator.GenerateStudents(NoOfStudents));
}
}
}
此控制器实现了两个"ActionResult"方法。 "Index
" 方法用于加载 "Index.aspx" 页面,而 "LoadStudents
" 方法用于加载"ViewUserControl" "LoadStudents.ascx"。此类包含一个类型为 "IStudentsGenerator
" 的私有成员变量 "studentGenerator
" 和一个接受类型为 "IStudentsGenerator
" 的输入参数的"构造函数"。此"构造函数"是 "StructureMap" "注入" "GoodStudentsGenerator
" 类或 "BadStudentsGenerator
" 类的"具体"实例的地方。一旦"注入"了一个"具体"的 "IStudentsGenerator
" 实例,"LoadStudents
" 方法就可以使用它来生成一个 "Student
" 对象"列表",并将其传递给强类型"ViewUserControl" "LoadStudents.ascx",该控件实现如下:
<%@ 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>
"LoadStudents.ascx" 的目的是生成一个干净的 HTML "表"片段,以显示从 "HomeController
" 的 "LoadStudents
" 方法传递过来的学生信息。此 HTML "表"片段由 "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 = '<%: Url.Action("LoadStudents", "Home") %>';
var ajaxLoadImgURL = '<%: Url.Content("~/Content/Images/ajax-load.gif") %>';
$(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>
"Index.aspx" 页面是一个简单的“视图”页面。主要的 HTML 组件如下:
- "<select>" 控件 "
selNoOfStudents
" 允许用户从"ViewUserControl" "LoadStudents.ascx" 中选择要检索的学生数量。 - "<img>" 控件 "
imgGetStudents
" 显示图片 "arrow.png"。这张图片是一个绿色箭头,在本应用程序中用作“按钮”。单击此图片将触发一个"ajax"调用,以根据 "selNoOfStudents
" 下拉框中的学生数量加载从 "LoadStudents.ascx" 生成的 HTML 片段。 - 如果"ajax"调用成功,则接收到的 HTML 片段将插入到"<div>" "
HTMLContent
" 中。
"Index.aspx" 的 "javascript
" 部分也非常简单。在 "$(document).load()
" 事件中,注册了"<img>" "imgGetStudents
" 的点击事件。当 "imgGetStudents
" 被点击时,JavaScript 代码首先从"<select>" 控件 "selNoOfStudents
" 获取要检索的学生数量,然后向服务器发出"ajax"调用。收到 HTML 片段后,"<div>" "HTMLContent
" 中的内容将被更新以显示收到的学生信息。
配置 StructureMap 进行依赖注入
最后,我们来到了可以讨论"依赖注入"的地方。为了在此 "ASP.NET MVC" 应用程序中设置使用 "StructureMap" 的"依赖注入",我在 "Bootstrapper.cs" 文件中编写了以下代码:
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;
using System.Collections.Specialized;
namespace StructureMapMVC
{
// StructureMap ContollerFactory
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");
});
}
}
}
"StructureMapControllerFactory
" 类重写了"DefaultControllerFactory",因此此 "MVC" 应用程序中的"控制器"是通过 "StructureMap" 的 "ObjectFactory.GetInstance()
" 方法实例化的。"Bootstrapper
" 静态类中的 "Run
" 方法将 "MVC" 应用程序的 "ControllerFactory
" 设置为 "StructureMapControllerFactory
",并告知 "StructureMap" 它应该查阅 "StructureMap.xml" 文件以获取配置信息。
"Run
" 方法在 "Global.asax.cs" 文件中的应用程序 "Application_Start
" 事件中被调用。
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
Bootstrapper.Run();
}
针对 "StructureMap" 的配置写在 "StructureMap.xml" 文件中。
<?xml version="1.0" encoding="utf-8" ?>
<StructureMap>
<DefaultInstance
PluginType="StructureMapMVC.Modules.Interface.IStudentsGenerator,
StructureMapMVC"
PluggedType="StructureMapMVC.Modules.Concrete.GoodStudentsGenerator,
StructureMapMVC" />
</StructureMap>
根据 "StructureMap" 网站上的文档,"PluginType
" 是"接口"的完全限定名,而 "PluggedType
" 是要"注入"的"具体"类的完全限定名。在上面的配置中,我们将"注入" "GoodStudentsGenerator
"。
现在,我们完成了示例应用程序的开发,可以进行测试运行了。
运行应用程序
当应用程序首次运行时,将加载 "Index.aspx" 页面。我们可以选择要检索的学生数量,然后单击绿色箭头加载学生。
由于我们已配置为使用 "GoodStudentsGenerator
" 来创建学生,因此我们得到了一份“好”学生的列表。
我们可以修改 "StructureMap.xml" 文件以使用 "BadStudentsGenerator
"。
<?xml version="1.0" encoding="utf-8" ?>
<StructureMap>
<DefaultInstance
PluginType="StructureMapMVC.Modules.Interface.IStudentsGenerator,
StructureMapMVC"
PluggedType="StructureMapMVC.Modules.Concrete.BadStudentsGenerator,
StructureMapMVC" />
</StructureMap>
重新启动应用程序,选择要检索的学生数量,然后再次单击绿色箭头,我们将得到一份“坏”学生的列表。
关注点
- 本文介绍在 ASP.NET MVC 中使用
StructureMap
进行依赖注入的简单示例。 - 根据 "StructureMap" 网站,在应用程序中使用"依赖注入"并不总是个好主意。当应用程序很小时,最好保持简单以避免"依赖注入"的复杂性。当应用程序很大时,保持构建块简单就更加重要。何时以及如何使用"依赖注入"取决于您的明智判断。
- 使用"依赖注入"的理想场所是当应用程序的某些构建块自然有多种实现方式,而您需要选择使用哪种实现时。 "StructureMap" 是帮助您进行这种配置的好框架。
- 如果您查看 "
HomeController
",您可能会注意到 "IStudentsGenerator
" 接口的具体实现是通过构造函数"注入"的。您实际上可以直接调用 "GetInstance
" 方法来获取所需的具体对象。如果您选择这种方式,您就不需要使用 "StructureMapControllerFactory
" 来实例化您的 "MVC" "控制器"。 - 依赖注入的配置可以通过代码和 XML 文件两种方式进行。每种方式都有其优缺点。在本文中,我选择使用 XML 文件。原因主要是因为在代码中配置"依赖注入"很简单并且有详细的文档记录,但是将配置放在 XML 文件中有点模糊。但使用 XML 文件在某些方面确实有一些优势,因为它清晰地分离了编码和配置之间的关注点。在本文中,我使用 XML 文件是为了保留记录供将来参考。
历史
- 这是本文的第一个修订版。