首页 文章

如何在C#中注入一个类(不是接口)?

提问于
浏览
7

我在这里使用 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.Web.Http.Internal.TypeActivator.Create的System.Linq.Expressions.Expression.New(Type type)中,在System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage请求,在System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create中键入controllerType,Func`1和activator)(HttpRequestMessage请求,HttpControllerDescriptor controllerDescriptor,类型controllerType)

由于我已经创建了一些工作正常的其他ApiControllers,我想这一定是因为我们的 ApplicationUserManager 无法解决 .

这里 ApplicationUserManager 继承自 UserManager 而不是接口 . 我不能用 container.RegisterType<interface, derived_class> . 解决问题的正确方法是什么?


这是 ApplicationUserManager

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

正如下面的一些评论所示 . 这是 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 Identity 需要一些特殊的工作 . 我在这里找到一个链接:Configure Unity DI for ASP.NET Identity . 但到目前为止,我仍然无法使其发挥作用 .

3 回答

  • 1

    问题是你的AccountController类没有无参数构造函数,你在堆栈跟踪 has no idea how to instantiate it 中看到的DefaultHttpControllerActivator .

    幸运的是,ASP.NET WebAPI具有将控制器实例化任务委派给DI容器的内置方式 . 本文完全涵盖您的用例,甚至还有下载源代码:https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

    为了防止链接腐烂我已经存档下面的文章(尽可能地从html转换格式,随意改进它):

    ASP.NET Web API 2中的

    依赖注入

    作者:Mike Wasson

    在本文中

    • 本教程中使用的软件版本

    • 什么是依赖注入?

    • Web API依赖关系解析器

    • 使用Unity容器的依赖项解析

    • 配置依赖关系解析器

    • 依赖范围和控制器生命周期

    Download Completed Project

    本教程介绍如何将依赖项注入ASP.NET Web API控制器 . 教程Web API 2 Unity Application Block Entity Framework 6中使用的软件版本(版本5也可以使用)

    什么是依赖注入?

    依赖项是另一个对象所需的任何对象 . 例如,它举例说明了一下 . 首先,我们将定义一个域模型:

    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控制器 . (为了简单起见,我省略了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方法或属性设置依赖项 .

    但是现在有一个问题,因为你的应用程序对 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模式和实践中的Unity . (其他受欢迎的库包括Castle Windsor,Spring.Net,Autofac,Ninject和StructureMap . )您可以使用NuGet Package Manager来安装Unity . 从Visual Studio中的 Tools 菜单中,选择 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 属性上设置依赖项解析程序 .

    以下代码在Unity中注册 IProductRepository 接口,然后创建 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);
    }
    
  • 0

    你不需要这两行,因为Unity会为你解决它 .

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

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

  • 0

    如果您想要一个非默认的生命周期管理器,您仍然需要使用unity注册这些类型 . 但是,Unity使用不同的语法,只有一个通用参数来注册具体类型(而不是类型映射):

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

    显然我不能从这里尝试代码,但看看是否能解决你的问题 .

相关问题