问题在于您的 AccountController 类没有无参数构造函数,您在堆栈跟踪中看到的 DefaultHttpControllerActivator 不知道如何实例化它。 p>
幸运的是,ASP.NET WebAPI 具有将控制器实例化任务委托给 DI 容器的内置方式。本文完全涵盖了您的用例,甚至还有源代码可供下载:https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection
为防止链接失效,我已将以下文章存档(尽我所能从 html 转换格式,请随时改进):
ASP.NET Web API 2 中的依赖注入
迈克·沃森
在这篇文章中
- 教程中使用的软件版本
- 什么是依赖注入?
- Web API 依赖解析器
- 使用 Unity 容器解决依赖关系
- 配置依赖关系解析器
- 依赖范围和控制器生命周期
Download Completed Project
本教程展示了如何将依赖项注入到您的 ASP.NET Web API 控制器中。
教程中使用的软件版本
- Web API 2
- Unity 应用程序块
- Entity Framework 6(版本 5 也适用)
什么是依赖注入?
依赖是另一个对象需要的任何对象。例如,通常定义处理数据访问的存储库。让我们用一个例子来说明。首先,我们将定义一个领域模型:
C#
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
这是一个简单的存储库类,它使用实体框架将项目存储在数据库中。
C#
public class ProductsContext : DbContext
{
public ProductsContext()
: base("name=ProductsContext")
{
}
public DbSet<Product> Products { get; set; }
}
public class ProductRepository : IDisposable
{
private ProductsContext db = new ProductsContext();
public IEnumerable<Product> GetAll()
{
return db.Products;
}
public Product GetByID(int id)
{
return db.Products.FirstOrDefault(p => p.Id == id);
}
public void Add(Product product)
{
db.Products.Add(product);
db.SaveChanges();
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (db != null)
{
db.Dispose();
db = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
现在让我们定义一个支持对Product 实体的GET 请求的Web API 控制器。 (为简单起见,我省略了 POST 和其他方法。)这是第一次尝试:
C#
public class ProductsController : ApiController
{
// This line of code is a problem!
ProductRepository _repository = new ProductRepository();
public IEnumerable<Product> Get()
{
return _repository.GetAll();
}
public IHttpActionResult Get(int id)
{
var product = _repository.GetByID(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
请注意,控制器类依赖于ProductRepository,我们让控制器创建ProductRepository 实例。但是,以这种方式对依赖项进行硬编码并不是一个好主意,原因有很多。
- 如果你想用不同的实现替换
ProductRepository,你还需要修改控制器类。
- 如果
ProductRepository 有依赖项,您必须在控制器内部配置这些。对于具有多个控制器的大型项目,您的配置代码会分散在您的项目中。
- 很难进行单元测试,因为控制器是硬编码来查询数据库的。对于单元测试,您应该使用模拟或存根存储库,这在当前设计中是不可能的。
我们可以通过将存储库注入到控制器中来解决这些问题。首先,将ProductRepository类重构为接口:
C#
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product GetById(int id);
void Add(Product product);
}
public class ProductRepository : IProductRepository
{
// Implementation not shown.
}
然后提供IProductRepository作为构造函数参数:
C#
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
// Other controller methods not shown.
}
此示例使用构造函数注入。您还可以使用 setter 注入,通过 setter 方法或属性设置依赖关系。
但是现在有一个问题,因为您的应用程序没有直接创建控制器。 Web API 在路由请求时会创建控制器,而 Web API 对IProductRepository 一无所知。这就是 Web API 依赖解析器的用武之地。
Web API 依赖解析器
Web API 定义了 IDependencyResolver 接口来解决依赖关系。下面是接口的定义:
C#
public interface IDependencyResolver : IDependencyScope, IDisposable
{
IDependencyScope BeginScope();
}
public interface IDependencyScope : IDisposable
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}
IDependencyScope 接口有两种方法:
-
GetService 创建一个类型的一个实例。
-
GetServices 创建指定类型的对象集合。
IDependencyResolver 方法继承IDependencyScope 并添加了BeginScope 方法。我将在本教程后面讨论范围。
Web API 创建控制器实例时,首先调用IDependencyResolver.GetService,传入控制器类型。您可以使用此可扩展性挂钩来创建控制器,解决任何依赖关系。如果 GetService 返回 null,Web API 会在控制器类上查找无参数构造函数。
使用 Unity 容器解决依赖关系
虽然您可以从头开始编写一个完整的 IDependencyResolver 实现,但该接口实际上旨在充当 Web API 和现有 IoC 容器之间的桥梁。
IoC 容器是负责管理依赖项的软件组件。您向容器注册类型,然后使用容器创建对象。容器自动计算出依赖关系。许多 IoC 容器还允许您控制对象生存期和范围等内容。
笔记
“IoC”代表“控制反转”,这是一种框架调用应用程序代码的通用模式。 IoC 容器会为您构建对象,从而“反转”通常的控制流程。
在本教程中,我们将使用 Microsoft Patterns & Practices 中的 Unity。 (其他流行的库包括 Castle Windsor、Spring.Net、Autofac、Ninject 和 StructureMap。)您可以使用 NuGet 包管理器来安装 Unity。从 Visual Studio 的 工具 菜单中,选择 Library Package Manager,然后选择 Package Manager Console。在包管理器控制台窗口中,键入以下命令:
控制台
Install-Package Unity
这是一个包装 Unity 容器的 IDependencyResolver 实现。
C#
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer container;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
container.Dispose();
}
}
笔记
如果 GetService 方法无法解析类型,它应该返回 null。如果 GetServices 方法无法解析类型,它应该返回一个空集合对象。不要为未知类型抛出异常。
配置依赖解析器
在全局 HttpConfiguration 对象的 DependencyResolver 属性上设置依赖解析器。
以下代码将IProductRepository接口注册到Unity,然后创建UnityResolver。
C#
public static void Register(HttpConfiguration config)
{
var container = new UnityContainer();
container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
config.DependencyResolver = new UnityResolver(container);
// Other Web API configuration not shown.
}
依赖范围和控制器生命周期
控制器是根据请求创建的。为了管理对象的生命周期,IDependencyResolver 使用了 范围 的概念。
附加到 HttpConfiguration 对象的依赖解析器具有全局范围。当 Web API 创建控制器时,它会调用 BeginScope。此方法返回一个代表子作用域的 IDependencyScope。
Web API 然后在子作用域上调用 GetService 来创建控制器。请求完成后,Web API 会在子作用域上调用 Dispose。使用 Dispose 方法来处理控制器的依赖项。
如何实现 BeginScope 取决于 IoC 容器。对于 Unity,范围对应于子容器:
C#
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}