来源https://docs.asp.net/en/latest/fundamentals/dependency-injection.html ASP.NET Core 1.0在设计上原生就支持和有效利用依赖注入。在Startup类中,应用可以通过将框架内嵌服务注入到方法中来使用他们;另一方面,你也可以配置服务来注入使用。默认的服务容器只提供了最小的特性集合,所以并不打算取代其他的IoC容器。

什么是依赖注入DI

依赖注入是为了达到解耦对象和其依赖的一项技术。一个类为了完成自身某些操作所需的对象是通过某种方式提供的,而不是使用静态引用或者直接实例化。通常情况下,类通过构造器来声明其依赖,遵循显式依赖原则。这种方式称作构造器注入。

当以DI思想来设计类时,这些类更加松耦合,因为他们不直接硬编码的依赖其合作者。这遵循了依赖倒置原则,即高层模块不应依赖底层模块,两者都应依赖抽象。类在构建时所需是抽象(如接口interface),而不是具体的实现。把依赖抽离成接口,把这些接口的实现作为参数也是策略设计模式的例子。

当一个系统使用DI来设计时,很多类通过构造器或者属性来添加依赖,这样就很方便有一个专门的类来创建这些类以及他们相关的依赖。这样的类称之为“容器”或者“IoC容器”或“DI容器”。一个容器本质上是一个工厂,来给请求者提供类型实例。如果给定类型声明了自身依赖,容器也配置了来提供这些依赖类型,那么它会创建这些依赖作为请求实例的一部分。通过这种方式可以为 类提供复杂的依赖图,而不需要任何硬编码的对象依赖。除了创建依赖对象外,容器一般还管理应用内的对象生命周期。

ASP.NET Core 1.0提供了一个简单的内置容器(以IServiceProvider为代表),默认支持构造器注入,这样ASP.NET可以通过DI使某些服务可用。ASP.NET把它所管理的类型称之为服务。本文的剩下部分,服务即指ASP.NET IoC容器所管理的类型。你可以在Startup类中的ConfigureServices 方法中配置内置的容器服务。 Note: Martin Fowler写过一篇很详细的依赖反转的文章。微软对此也有很棒的描述连接

使用框架提供的服务

ConfigureServices方法负责定义应用使用的服务,包括平台特性服务如EF和ASP.NET MVC。最初提供给ConfigureServices的IServiceCollection只有少数服务。默认web模板提供了怎么通过扩展方法来添加额外服务到容器的例子,如AddEntityFramework, AddIdentity, 和AddMVC。

public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); services.AddMvc(); // Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}

ASP.NET提供的特性和中间件遵循使用一个AddService扩展方法的约定,来注册该特性使用的所需的所有服务。

Note:你可以在Startup方法中请求某些framework-provided服务,详见应用启动Application Startup 当然,除了配置框架提供的各种服务,你也可以配置自己定义的服务。

注册自定义服务

在默认web模板中,有如下两个服务被添加到IServiceCollection中

// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

AddTransient方法将抽象类型映射为实体服务,对于每个请求这都单独实例化,这称作服务的生命周期。额外生命周期选项如下。对于每个注册的服务选择合适的生命周期是很重要的。是对每个请求类都提供一个新的实例化服务?还是在给定web请求内只实例化一次?还是在应用周期内只有单例?

在本文的例子中,有个简单的CharacterController来显示Character姓名,在Index方法中显示已存储的Character(如果没有则创建)。虽然注册了EF服务,但本例持久化没有使用数据库。具体的数据获取服务抽象到了ICharacterRepository接口实现中,这遵从了仓储模式。在构造器中请求ICharacterRepository参数,并将其赋给私有变量,来根据需要获取Character。

using System.Linq;
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Models;
using Microsoft.AspNet.Mvc; namespace DependencyInjectionSample.Controllers
{
public class CharactersController : Controller
{
private readonly ICharacterRepository _characterRepository; public CharactersController(ICharacterRepository characterRepository)
{
_characterRepository = characterRepository;
} // GET: /characters/
public IActionResult Index()
{
var characters = _characterRepository.ListAll();
if (!characters.Any())
{
_characterRepository.Add(new Character("Darth Maul"));
_characterRepository.Add(new Character("Darth Vader"));
_characterRepository.Add(new Character("Yoda"));
_characterRepository.Add(new Character("Mace Windu"));
characters = _characterRepository.ListAll();
} return View(characters);
}
}

接口ICharacterRepository只简单定义了两个方法,Controller通过其来操作Charcter实例。

using System.Collections.Generic;
using DependencyInjectionSample.Models; namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}

接口有具体类型CharacterRepository来实现,在运行时被使用。 Note: CharacterRepository类只是使用DI的普通例子,你可以对应用所有的服务使用DI,而不仅仅是“仓储”和数据获取类。

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces; namespace DependencyInjectionSample.Models
{
public class CharacterRepository : ICharacterRepository
{
private readonly ApplicationDbContext _dbContext; public CharacterRepository(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
} public IEnumerable<Character> ListAll()
{
return _dbContext.Characters.AsEnumerable();
} public void Add(Character character)
{
_dbContext.Characters.Add(character);
_dbContext.SaveChanges();
}
}
}

请注意,CharacterRepository在其构造器中请求了ApplicationDbContext实例。这种链式的依赖注入是很常见的,被依赖本身又有自己的依赖。容器来负责以树形的方式来解析所有这些依赖,并返回解析完成的服务。

Note: 创建请求对象,以及其依赖,其依赖的依赖,有时这被称之为对象图。同样的,需要解析的对象集合称之为依赖树或者依赖图

在本例中,ICharacterRepository和ApplicationDbContext都必须在ConfigureServices中注册。ApplicationDbContext是通过扩展方法AddEntityFramework来配置,它包括添加DbContext (AddDbContext)的一个扩展。仓储的注入在在ConfigureServices方法的结尾。

services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddScoped<ICharacterRepository, CharacterRepository>(); // Show different lifetime options
services.AddTransient<IOperationTransient, Operation>();

EF contexts需要使用scoped生命周期来添加到服务容器。如果你使用了上面的helper方法,这是已经处理好的。使用EF的仓储服务应该使用同样的生命周期。

警告:主要不安全的来源是通过单例来解析Scoped生命周期服务服务。这样做的后果,很有可能在处理后续请求时使用的服务的状态是错误的。

服务生命周期和注册选项

ASP.NET 服务可以配置如下生命周期:

  • Transient: Transient服务在每次被请求时都会被创建。这种生命周期比较适用于轻量级的无状态服务。
  • Scoped: Scoped生命周期的服务是每次web请求被创建。
  • Singleton: Singleton生命能够周期服务在第一被请求时创建,在后续的每个请求都会使用同一个实例。如果你的应用需要单例服务,推荐的做法是交给服务容器来负责单例的创建和生命周期管理,而不是自己来走这些事情。
  • Instance: 你也可以选择直接添加实例到服务容器。如果这样做,该实例会被后续的所有请求所使用(这样就会创建一个scoped-Singleton实例)。Instance和Singleton的一个主要区别在于,Instance服务是由ConfigureServices创建,然而Singleton服务是lazy-loaded,在第一个被请求时才会被创建。

服务可以通过若干种方式注册到容器。我们已经看到,对于给定类型通过指定具体类型来注册服务的实现。除此之外,也可以指定一个工厂,用来按需创建实例。第三种方法是直接指定要使用的类型实例,在这种方式下,容器自身不会尝试去创建实例。

为了演示这四种不同的生命周期和注册选项,考虑一个简单接口,代表这一个或多个任务操作,并且含有一个唯一标识符OperationId。根据我们如何配置服务的生命周期,容器对请求类或者提供同一个实例或者不同实例。为了弄明白生命周期是如何请求的,对于每一个生命周期类型我们都创建一个类型。

using System;

namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
} public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationInstance : IOperation
{
}
}

我们通过一个类来实现这些接口,接受一个Guid作为构造器参数,或者使用new Guid来提供(如果没有提供的话)。 接下来在ConfigureServices中,根据类型的生命周期来添加到容器中

services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddInstance<IOperationInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

注意到对于Instance生命周期的实例,我们是自己提供了已知的Guid.Empty标识符,这样我们能在该实例被使用时识别它。我们也注册了一个OperationService,它依赖其他Operation类型。这样我们就能弄清楚在一个请求内,对于每个类型我们是得到同样的实例还是一个新的实例。

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
public class OperationService
{
public IOperationTransient TransientOperation { get; private set; }
public IOperationScoped ScopedOperation { get; private set; }
public IOperationSingleton SingletonOperation { get; private set; }
public IOperationInstance InstanceOperation { get; private set; } public OperationService(IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
InstanceOperation = instanceOperation;
}
}
}

为了演示对应用的单个请求内和不同请求内的对象生命周期,样例包含一个OperationController依赖每种类型的Operation以及OperationService。Index方法显示所有的服务Id。

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNet.Mvc; namespace DependencyInjectionSample.Controllers
{
public class OperationsController : Controller
{
private readonly OperationService _operationService;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationInstance _instanceOperation; public OperationsController(OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationInstance instanceOperation)
{
_operationService = operationService;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
_instanceOperation = instanceOperation;
} public IActionResult Index()
{
ViewBag.Transient = _transientOperation;
ViewBag.Scoped = _scopedOperation;
ViewBag.Singleton = _singletonOperation;
ViewBag.Instance = _instanceOperation;
ViewBag.Service = _operationService;
return View();
}
}
}

然后有两个请求到达controller action。

观察请求内和请求间的OperationId哪个变化。

  • Transient 服务的对象总是不同的。每个controller和service都提供一个新的实例
  • Scoped的对象在一个request内是一样的,而不同的request间是不一样的。
  • Singleton对象是一直保持不表的。
  • Instance对象,对于每一个对象和request都是一样的,也即是在ConfigureServices中所指定的对象。

请求服务和应用服务

HttpContext中的一个ASP.NET请求中的可用服务分为两个集合,ApplicationServicesRequestServices

Request services作为应用的一部分是你可以配置和request的。而Application Services则是局限于在在应用启动(Startup)时可用的服务。Scoped的服务是作为Request Services的一部分而不是Applocation Services的一部分。当对象指定依赖时,是由RequestServices中的类型所提供,而不是ApplicationServices。

一般来将你不应该直接使用这些属性,而是倾向于通过类的构造器来请求这些类型,让框架来注入这些依赖。这样产生的类更容易测试和更松耦合。

Note: 需要重点记住的是,应用几乎总是会使用RequestServices,任何情况下你都不应该直接使用这些属性。而是通过构造器来请求所需服务。

自定义依赖注入服务

你可以设计自己的服务并通过依赖注入到需求方。这样可以避免使用有状态的静态方法调用(会导致code smell,即static cling)和服务内对依赖类的直接实例化。当选择是否通过New来实例化一个类型或者通过依赖注入来氢气,记住“New is Glue”也许是点帮助的。通过遵循Solid面向对象设计原则,设计的类自然就会small, well-factored,和easily tested。

如果你发现类有了太多需要注入的依赖怎么办?一般来说,这是类承担了太多职责的标志,很有可能违反了SRP(单一职责原则)。检查是否能把其中的某些职责转到新的类。记住,Controller类应该只关注UI,所以业务规则和数据获取实现应该放在合适的关注点分离的类中。

至于数据获取,你可以注入EF DbContext类型到Controller中(假设你已经在Startup类中配置了EF)。然而,一般避免在UI项目中直接依赖DbContext类型,而是依赖抽象,如Repository接口,在接口的实现中限制EF的相关知识。这样会减少项目和数据访问策略的耦合,使得测试代码更加容易。

取代默认服务容器

内建的服务容器只满足框架最基本的需求,大部分的consumer 应用基于此构建。然而,如果开发者希望取代内建的容器,使用自己偏好的容器,也是可以很容易做到的。ConfigureServices方法一般返回void,但是如果返回类型签名改为IServiceProvider,就可以配置和返回别的容器。有很多IoC .NET容器。当它们可用时,本文会添加这些容器的DNX实现。在本例中,使用Autofac包。

首先在project.json中添加合适的容器包。

"dependencies" : {
"Autofac": "4.0.0-rc1",
"Autofac.Extensions.DependencyInjection": "4.0.0-rc1"
},

然后在ConfigureServices中配置容器,并返回IServiceProvider:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// add other framework services // Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return container.Resolve<IServiceProvider>();
}

Note: 当使用第三方DI容器时,需要改变ConfigreServices的返回签名,改成IServiceProvider而不是void。

最后在DefaultModule中配置Autofac。

public class DefaultModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
}
}

然后在运行时,Autofac将会解析类型和注入的依赖

Package(Nuget) ProjectSite
Autofac.Dnx http://autofac.org
StructureMap.Dnx http://structuremap.github.io

推荐规范

当使用依赖注入时,记住如下推荐规范:

  • DI是针对于有复杂依赖的对象。Controllers, services, adapters和 repositories都是一些可以添加依赖的对象的例子。
  • 避免直接在DI中存储数据和配置。例如,用户购物车不应添加到服务容器中。配置应该使用Options Model中文链接。类似的,避免“data holder” objects that only exist to allow access to some other object。如果可能尽量通过DI来获取实际的item。
  • 避免静态获取服务
  • 在应用代码中避免service location
  • 避免静态获取HttpContext

Note: 如上所有推荐规范,你可能遇到必须忽略某条的情形。但是这种情形很少,而且基本都是框架本身内部的情形。 记住,依赖注入是static/global对象获取模式的一个替代方式。如果你把DI和静态对象接入混用,你可能不能体会到DI的优势。

Download sample from GitHub

ASP.NET Core 1.0基础之依赖注入的更多相关文章

  1. ASP.NET Core 1.0 中的依赖项管理

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  2. ASP.NET Core - 在ActionFilter中使用依赖注入

    上次ActionFilter引发的一个EF异常,本质上是对Core版本的ActionFilter的知识掌握不够牢固造成的,所以花了点时间仔细阅读了微软的官方文档.发现除了IActionFilter.I ...

  3. .Net Core 3.0 内置依赖注入:举例

    原文:.Net Core 3.0 内置依赖注入:举例 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn ...

  4. ASP.NET Core 1.0 基础与应用启动

    .NET Core http://dotnet.github.io/[https://github.com/dotnet/coreclr] ASP.NET Core 1.0 https://get.a ...

  5. ASP.NET Core 1.0 基础之配置

    来源https://docs.asp.net/en/latest/fundamentals/configuration.html ASP.NET Core 1.0支持不同的配置选项.应用配置数据可以是 ...

  6. ASP.NET Core 1.0基础之日志

    过年出去玩了一圈,回来继续翻译.前两天偷懒没有翻译,只是转了两篇C# 7计划中的新features,大家还是很支持的.现在继续完善这个系列. 来源https://docs.asp.net/en/lat ...

  7. ASP.NET Core 1.0基础之诊断

    来源https://docs.asp.net/en/latest/fundamentals/diagnostics.html ASP.NET Core 1.0包含了一些新的特性来辅助诊断问题.可以在S ...

  8. ASP.NET Core 1.0基础之应用启动

    来源https://docs.asp.net/en/latest/fundamentals/startup.html ASP.NET 5 使得应用对每个http请求有完整的控制权.Startup类是程 ...

  9. asp.net core 系列之Dependency injection(依赖注入)

    这篇文章主要讲解asp.net core 依赖注入的一些内容. ASP.NET Core支持依赖注入.这是一种在类和其依赖之间实现控制反转的一种技术(IOC). 一.依赖注入概述 1.原始的代码 依赖 ...

随机推荐

  1. 20155309 《java程序设计》实验四Android程序设计

    任务一: 完成Hello World, 要求修改res目录中的内容,Hello World后要显示自己的学号 完成这个任务,首先需要了解Android应用程序文件的组成: src目录: 在src目录中 ...

  2. 移动端布局 - REM方式

    默认以宽度为640px的设计稿为基准页面,然后通过JS获取当前显示设备的尺寸,对应的调整 html 标签的font-size大小,从而实现通过以rem为单位的移动端布局适配. 具体代码 (functi ...

  3. HttpService与WebService的差异

    httpservice通过post和get得到你想要的东西webservice就是使用soap协议得到你想要的东西,相比httpservice能处理些更加复杂的数据类型 当你要调用一个你本服务的内容的 ...

  4. 容器计划任务大坑:在alpine容器里,想用非root帐号执行crontab任务

    我只能说抱歉,我前前后后测试了七天, 将自己预想的配置错误,一个一个去验证. 非root帐号在alpine容器里执行crontab任务,还是失败, 输出依旧是一片空白~ stackoverflow里, ...

  5. 《精通Python设计模式》学习之抽象工厂

    这种工厂模式用得少, 可能在游戏类的编程中用得比较多吧. 这个思路清晰一定要OK的. class Frog: def __init__(self, name): self.name = name de ...

  6. USACO 4.2 Job Processing

    Job ProcessingIOI'96 A factory is running a production line that requires two operations to be perfo ...

  7. Educational Codeforces Round 44 (Rated for Div. 2) F - Isomorphic Strings

    F - Isomorphic Strings 题目大意:给你一个长度为n 由小写字母组成的字符串,有m个询问, 每个询问给你两个区间, 问你xi,yi能不能形成映射关系. 思路:这个题意好难懂啊... ...

  8. 《JAVA软件结构与数据结构》第一周学习总结

    学号 20172326 <JAVA软件结构与数据结构>第一周学习总结 教材学习内容总结 软件质量的几大特性 增长函数与大O记法 大O记法用来表示表示增长函数,从而来表示算法的复杂度 算法的 ...

  9. beeshell —— 开源的 React Native 组件库

    介绍 beeshell 是一个 React Native 应用的基础组件库,基于 0.53.3 版本,提供一整套开箱即用的高质量组件,包含 JavaScript(以下简称 JS)组件和复合组件(包含 ...

  10. python opencv3 背景分割 mog2 knn

    git:https://github.com/linyi0604/Computer-Vision 使用mog2算法进行背景分割 # coding:utf-8 import cv2 # 获取摄像头对象 ...