【问题标题】:How to inject a class (not an interface) in C#?如何在 C# 中注入一个类(不是接口)?
【发布时间】:2014-08-28 18:01:33
【问题描述】:

我在这里使用 Unity。但也许我们只需要指出一个正确的方向。

我们知道如何注入接口:

public class AccountController:ApiController
{
    private readonly IAccountRepository _repository;

    public AccountController(IAccountRepository repository)
    {
        _repository = repository;
    }
}

使用 RegisterType

var container = new UnityContainer();
container.RegisterType<IAccountRepository, AccountRepository>(new HierarchicalLifetimeManager());

但是在我的 AccountRepository 中,我们将一个类注入到构造函数中。

private readonly ApplicationUserManager _userManager;
public AccountRepository(ApplicationUserManager userManager)
{
    _userManager = userManager;
}

所以在调用ApiController的时候还是会报这个错误:

尝试创建“AccountController”类型的控制器时出错。确保控制器有一个无参数的公共构造函数。

堆栈跟踪:

在 System.Linq.Expressions.Expression.New(Type type) 在 System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType) 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) 的请求,类型 controllerType,Func`1& activator)

由于我创建了其他一些运行良好的 ApiController,我想这一定是因为我们的 ApplicationUserManager 未能解决。

这里 ApplicationUserManager 继承自 UserManager 而不是接口。我不能使用container.RegisterType&lt;interface, derived_class&gt;。正确的解决方法是什么?


这里是 ApplicationUserManager

public class ApplicationUserManager : UserManager<User>
{
    public ApplicationUserManager(IdentityContext identityContext)
        : base(new UserStore<User>(identityContext))
    {
    }
}

正如下面一些 cmets 所建议的那样。以下是 RegisterType 语句:

var container = new UnityContainer();
container.RegisterType<IAccountRepository, AccountRepository>(new HierarchicalLifetimeManager());
container.RegisterType<ApplicationUserManager, ApplicationUserManager>(new HierarchicalLifetimeManager());
container.RegisterType<IdentityContext, IdentityContext>(new HierarchicalLifetimeManager());

config.DependencyResolver = new UnityResolver(container);

看起来需要一些特殊的工作来设置 ASP.NET 身份。我在这里找到一个链接: Configure Unity DI for ASP.NET Identity。但到目前为止我仍然无法让它工作。

【问题讨论】:

  • 你试过RegisterType&lt;ApplicationUserManager, ApplicationUserManager&gt;吗?
  • 不知道我能做到。让我试一试。
  • 是的。签名是RegisterType,不限于先接口后强类型。
  • 感谢您的指导。但似乎还有一些缺失。查看更新的问题(底部的部分)
  • 几年前我问的相关问题:stackoverflow.com/questions/6051821/…

标签: c# dependency-injection unity-container


【解决方案1】:

问题在于您的 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 中的依赖注入

迈克·沃森

在这篇文章中

  1. 教程中使用的软件版本
  2. 什么是依赖注入?
  3. Web API 依赖解析器
  4. 使用 Unity 容器解决依赖关系
  5. 配置依赖关系解析器
  6. 依赖范围和控制器生命周期

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);
}

【讨论】:

    【解决方案2】:

    您不需要这两行代码,因为 Unity 会为您解决。

    container.RegisterType<ApplicationUserManager, ApplicationUserManager>(new HierarchicalLifetimeManager());
    container.RegisterType<IdentityContext, IdentityContext>(new HierarchicalLifetimeManager());
    

    IdentityContext 的构造函数是什么样的?它很可能有一个您尚未注册的依赖项。如果它有多个构造函数,Unity 将选择参数数量最多的构造函数。

    【讨论】:

      【解决方案3】:

      如果您想要一个非默认的生命周期管理器,您仍然必须使用统一注册类型。但是,Unity 使用不同的语法,只有一个通用参数来注册具体类型(与类型映射相反):

      container.RegisterType<ApplicationUserManager>(new HierarchicalLifetimeManager());
      container.RegisterType<IdentityContext>(new HierarchicalLifetimeManager());
      

      显然我无法从这里试用代码,但看看是否能解决您的问题。

      【讨论】:

        猜你喜欢
        • 2017-04-19
        • 2020-04-09
        • 2022-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-30
        • 2017-02-02
        • 1970-01-01
        相关资源
        最近更新 更多