使用 Unity 进行依赖注入

在面向对象的编程模式中,使用对象模型协同工作。这会在对象和组件之间产生依赖关系,在项目复杂性增加时这种依赖关系就变得难以管理。通常我们会使用接口或工厂模式来实现解耦,但这只能解决部分问题,有时候反而会使问题变得更糟(本来直接依赖的两个组件现在又依赖新增的接口)

依赖注入

依赖注入 (Dependency Injection, DI) 是控制反转的实现。控制反转 (Inversion of Control, IoC) 意味着对象不会创建他们依赖的其他组件,而是从外部源获取所需的对象。一般通过构造器或属性进行依赖注入。

使用依赖注入和控制反转的优点有:

  1. 减少类耦合
  2. 增加代码重用
  3. 提高代码可维护性
  4. 改进了应用程序测试

演示

创建项目

为了演示如何使用 Unity 进行依赖注入,这里创建一个名为 BookStore.API 的空项目,并添加 ASP.NET WebApi 核心引用。为了保持数模型实体与存储接收逻辑之间的分离,我又创建了一个名为 BookStore.Domain 的类库项目,并在 BookStore.API 项目中添加了对 BookStore.Domain 的引用

添加 NuGet 包

为 BookStore.API 和 BookStore.Domain 添加 EntityFramework 包

1
Install-Package EntityFramework

因为创建的是 ASP.NET WebApi 项目,所以我们需要添加 Unity.AspNet.WebApi 包到项目中以使用 Unity 进行依赖注入:

1
Install-Package Unity.AspNet.WebApi

包管理器会自动添加相应的文件和引用

*如果创建的是 ASP.NET MVC 项目,则需要添加 Install-Package Unity.Mvc 包到项目中

依赖注入

首先,我在 BookStore.Domain 项目中使用了 代码先行 (Code-First) 模式,这样做的好处是 EntityFramework 会在应用程序运行的时候根据模型类生成数据库。然后,我又在 BookStore.Domain 中实现了 存储库 (Repository) 模式。接下来在 BookStore.API 中添加一个名为 Home 的 Web API 控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using BookStore.Domain.Models;
using BookStore.Domain.Abstract;
using BookStore.Domain.Concrete;

namespace BookStore.API.Controllers
{
public class HomeController : ApiController
{
private IBookInfoRepository bookInfoRepository = new BookInfoRepository();

public List<BookInfo> GetBookInfos()
{
return bookInfoRepository.GetBookInfos().ToList();
}
}
}

虽然这里使用了 IBookInfoRepository 接口来建立松耦合,但是还是必须在 HomeController 中实例化 BookInfoRepository。接下来我们改为使用 Unity 通过构造函数将 BookInfoRepository 注入进来,这样就能使 IBookInfoRepository 接口和 BookInfoRepository 实现之间完全解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using BookStore.Domain.Models;
using BookStore.Domain.Abstract;
using BookStore.Domain.Concrete;

namespace BookStore.API.Controllers
{
public class HomeController : ApiController
{
private IBookInfoRepository bookInfoRepository;

public HomeController(IBookInfoRepository bookInfoRepositoryP)
{
this.bookInfoRepository = bookInfoRepositoryP;
}

public List<BookInfo> GetBookInfos()
{
return bookInfoRepository.GetBookInfos().ToList();
}
}
}

接下来在 App_Start/UnityConfig.cs 文件的 RegisterTypes 方法中配置注入规则

1
2
3
4
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IBookInfoRepository, BookInfoRepository>();
}

然后在浏览器中访问地址 http://localhost:7529/api/Home/GetBookInfos 如果一切正常你将得到接口的返回值

1
2
3
4
5
6
7
8
9
<ArrayOfBookInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/BookStore.Domain.Models">
<BookInfo>
<AuthorName>Adam Freeman</AuthorName>
<BookCode>B0001</BookCode>
<BookInfoID>1</BookInfoID>
<BookName>Expert ASP.NET Web API 2 for MVC Developers</BookName>
<Price>59.50</Price>
</BookInfo>
</ArrayOfBookInfo>

创建 Help Page

因为这是一个 ASP.NET WebApi 项目,所以我们顺带着创建 API 帮助页

先添加 Microsoft.AspNet.WebApi.HelpPage 包到 BookStore.API 项目中

1
Install-Package Microsoft.AspNet.WebApi.HelpPage

包管理器会在项目中创建 Areas/HelpPage 文件夹,找到 HelpPage/App_Start/HelpPageConfig.cs 文件中的 Register 方法,将以下语句的注释取消

1
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/BookStore.API.xml")));

设置 BookStore.API 项目属性,修改 生成 → 输出 → XML 文档文件 的值为 bin\BookStore.API.xml 以和 Register 方法中设置的值对应
最后在项目根目录的 Global.asax 文件中的 Application_Start 方法中注册区域

1
2
3
4
5
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
}

注意事项

  1. Code-First 模式会自动创建数据库,创建的数据库默认存储在 %USERPROFILE% 文件夹下,要删除数据库以重新创建务必从 SQL Server 对象资源管理器 中操作
  2. 如果创建了一个同时包含 ASP.NET WebApi 和 ASP.NET MVC 核心引用的项目,则会自动在 Application_Start 方法中注册 MVC 项目的路由规则
1
2
3
4
5
6
void Application_Start(object sender, EventArgs e)
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}

若在这个项目中又同时使用了 Unity 进行依赖注入则 Help Page 将不能正常生成,解决方案是在 App_Start/UnityConfig.cs 文件的 RegisterTypes 方法中添加一条注入规则

1
2
3
4
5
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IBookInfoRepository, BookInfoRepository>();
container.RegisterType<Areas.HelpPage.Controllers.HelpController>(new InjectionConstructor(System.Web.Http.GlobalConfiguration.Configuration));
}

Source code

BookStore