c# - 如何在 C# 中注入(inject)类(不是接口(interface))?

标签 c# dependency-injection unity-container

我正在使用 团结这里。但可能我们只需要被指出一个正确的方向。

我们知道如何注入(inject)接口(interface):

public class AccountController:ApiController
{
    private readonly IAccountRepository _repository;

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

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

但在我的 账户库 ,我们有一个类注入(inject)到构造函数中。
private readonly ApplicationUserManager _userManager;
public AccountRepository(ApplicationUserManager userManager)
{
    _userManager = userManager;
}

因此,在调用 ApiController 时,我仍然收到此错误:

An error occurred when trying to create a controller of type 'AccountController'. Make sure that the controller has a parameterless public constructor.



堆栈跟踪:

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



由于我创建了一些其他运行良好的 ApiControllers,我想这一定是因为我们的 应用程序用户管理器 未能解决。

这里应用程序用户管理器 继承自 用户管理器 而不是接口(interface)。我无法使用 container.RegisterType<interface, derived_class> .解决它的正确方法是什么?

这是应用程序用户管理器 :
public class ApplicationUserManager : UserManager<User>
{
    public ApplicationUserManager(IdentityContext identityContext)
        : base(new UserStore<User>(identityContext))
    {
    }
}

正如下面的一些评论所建议的那样。这是注册类型 声明:
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 .但到目前为止,我仍然无法使其工作。

最佳答案

问题是您的 AccountController 类没有无参数构造函数,即您在堆栈跟踪中看到的 DefaultHttpControllerActivator 不知道如何实例化它 .

幸运的是,ASP.NET WebAPI 具有将 Controller 实例化任务委托(delegate)给 DI 容器的内置方法。这篇文章准确地涵盖了您的用例,甚至还有源代码可供下载:https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

为了防止链接腐烂,我将下面的文章存档(尽我所能从 html 转换格式,请随时改进它):

ASP.NET Web API 2 中的依赖注入(inject)

通过迈克沃森

在本文中

  • 教程中使用的软件版本
  • 什么是依赖注入(inject)?
  • Web API 依赖解析器
  • 使用 Unity 容器解决依赖关系
  • 配置依赖解析器
  • 依赖范围和 Controller 生命周期

  • Download Completed Project

    This tutorial shows how to inject dependencies into your ASP.NET Web API controller.

    Software versions used in the tutorial

    • Web API 2
    • Unity Application Block
    • Entity Framework 6 (version 5 also works)


    什么是依赖注入(inject)?

    依赖项是另一个对象需要的任何对象。例如,通常定义一个处理数据访问的存储库。让我们用一个例子来说明。首先,我们将定义一个域模型:

    C#
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
    

    这是一个使用 Entity Framework 将项目存储在数据库中的简单存储库类。

    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 Controller 。实体。 (为了简单起见,我省略了 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);
        }
    }
    

    注意 Controller 类依赖于 ProductRepository ,我们让 Controller 创建 ProductRepository实例。但是,出于多种原因,以这种方式对依赖项进行硬编码是一个坏主意。
  • 如果要更换ProductRepository使用不同的实现,您还需要修改 Controller 类。
  • 如果ProductRepository有依赖关系,你必须在 Controller 内部配置这些。对于具有多个 Controller 的大型项目,您的配置代码会分散在您的项目中。
  • 很难进行单元测试,因为 Controller 是硬编码来查询数据库的。对于单元测试,您应该使用模拟或 stub 存储库,这在当前设计中是不可能的。

  • 我们可以通过将存储库注入(inject) Controller 来解决这些问题。首先,重构ProductRepository类到接口(interface):

    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.
    }
    

    此示例使用构造函数注入(inject)。您还可以使用 setter 注入(inject),通过 setter 方法或属性设置依赖项。

    但是现在有一个问题,因为您的应用程序没有直接创建 Controller 。 Web API 在路由请求时创建 Controller ,而 Web API 对 IProductRepository 一无所知.这就是 Web API 依赖解析器的用武之地。

    Web API 依赖解析器

    Web API 定义了 IDependencyResolver 用于解决依赖关系的接口(interface)。下面是接口(interface)的定义:

    C#
    public interface IDependencyResolver : IDependencyScope, IDisposable
    {
        IDependencyScope BeginScope();
    }
    
    public interface IDependencyScope : IDisposable
    {
        object GetService(Type serviceType);
        IEnumerable<object> GetServices(Type serviceType);
    }
    

    IDependencyScope 接口(interface)有两种方法:
  • 获取服务 创建一种类型的一个实例。
  • 获取服务 创建指定类型的对象集合。

  • IDependencyResolver 方法继承 IDependencyScope 并添加 开始范围方法。我将在本教程后面讨论范围。

    当 Web API 创建 Controller 实例时,它首先调用 IDependencyResolver.GetService ,传入 Controller 类型。您可以使用此可扩展性 Hook 来创建 Controller ,解决任何依赖关系。如 获取服务 返回 null,Web API 在 Controller 类上查找无参数构造函数。

    使用 Unity 容器解决依赖关系

    虽然你可以写一个完整的 IDependencyResolver 从头开始实现,该接口(interface)实际上旨在充当 Web API 和现有 IoC 容器之间的桥梁。

    IoC 容器是负责管理依赖项的软件组件。您向容器注册类型,然后使用容器创建对象。容器会自动计算出依赖关系。许多 IoC 容器还允许您控制对象生命周期和范围等内容。

    笔记

    “IoC”代表“控制反转”,这是框架调用应用程序代码的通用模式。一个 IoC 容器为你构造你的对象,它“反转”了通常的控制流。

    在本教程中,我们将使用 Microsoft Patterns & Practices 中的 Unity。 (其他流行的库包括 CaSTLe Windsor、Spring.Net、Autofac、Ninject 和 StructureMap。)您可以使用 NuGet 包管理器来安装 Unity。来自 工具 在 Visual Studio 菜单中,选择 库包管理器 ,然后选择 包管理器控制台 .在包管理器控制台窗口中,键入以下命令:

    安慰
    Install-Package Unity
    

    这是 的实现IDependencyResolver 包装了一个 Unity 容器。

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

    笔记

    如果获取服务 方法无法解析类型,它应该返回 .如果获取服务 方法无法解析类型,它应该返回一个空的集合对象。不要为未知类型抛出异常。

    配置依赖解析器

    上设置依赖解析器DependencyResolver 全局属性(property) Http配置目的。

    以下代码注册 IProductRepository与 Unity 接口(interface),然后创建一个 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.
    }
    

    依赖范围和 Controller 生命周期

    Controller 是根据请求创建的。要管理对象生命周期, IDependencyResolver 使用范围的概念。

    附加到 的依赖解析器Http配置对象具有全局作用域。当 Web API 创建 Controller 时,它会调用 开始范围 .此方法返回 IDependencyScope 代表一个子作用域。

    然后 Web API 调用 获取服务 在子作用域上创建 Controller 。请求完成后,Web API 调用 处置 在子作用域上。使用 处置 处理 Controller 依赖项的方法。

    您如何实现 开始范围取决于 IoC 容器。对于 Unity,作用域对应于一个子容器:

    C#
    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }
    

    关于c# - 如何在 C# 中注入(inject)类(不是接口(interface))?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25555207/

    相关文章:

    c# - 将 ASP.Net Core Angular 模板从版本 8 更新到 9

    java - 如何在 Spring 中定义要 Autowiring 的 bean

    c# - Unity 3 Configuration By Convention 在 Web 项目中找不到类型

    c# - Unity中有跟踪吗?

    c# - Caliburn.micro 统一

    c# - 在 IHttpClientFactory 注入(inject)时模拟 HttpClient 处理程序

    c# - 如何避免传递给构造函数的多个参数的赋值

    c# - Fluent API HasXXX 与 WithXXX 方法

    exception - 资源注释 : No qualifying bean of type [javax. sql.DataSource] 已定义:预期单个匹配 bean 但找到 2

    c# - 向 wpf 行为注入(inject)依赖的干净方法