[.NET开发] ASP.NET Core 1.0基础之依赖注入
2016-03-14 16:52
1326 查看
在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)
{http://www.nvzi91.cn/fujianyan/29933.html
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
http://www.nvzi91.cn/zigongnamoyan/29934.html
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}http://www.nvzi91.cn/chunvmoxiufu/29935.html
复制代码<
4000
/div>
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;
http://www.nvzi91.cn/penqiangyan/29936.html
namespace DependencyInjectionSample.Controllers
{
public class CharactersController : Controller
{
private readonly ICharacterRepository _characterRepository;
public CharactersController(ICharacterRepository characterRepository)
{
_characterRepository = characterRepository;
}
http://www.nvzi91.cn/yindaoyan/29937.html
// 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();
}http://www.kmrlyy.com/lcnz/33448.html
return View(characters);
}
}
复制代码
接口ICharacterRepository只简单定义了两个方法,Controller通过其来操作Charcter实例。
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{http://www.kmrlyy.com/penqiangyan/33450.html
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
复制代码
接口有具体类型CharacterRepository来实现,在运行时被使用。
Note:CharacterRepository类只是使用DI的普通例子,你可以对应用所有的服务使用DI,而不仅仅是“仓储”和数据获取类。
using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;
http://www.kmrlyy.com/zgjl/33451.html
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();
}http://www.kmrlyy.com/gongjingai/33452.html
public void Add(Character character)
{http://www.kmrlyy.com/gongjingfeida/33453.html
_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>();
http://m.nvzi91.cn/gongjingmilan/29348.html
// 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;
http://m.nvzi91.cn/gongjingmilan/29348.html
namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{http://m.nvzi91.cn/gongjingyan/29349.html
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationInstance : IOperation
{
}
}http://m.nvzi91.cn/gongjingxirou/29350.html
复制代码
我们通过一个类来实现这些接口,接受一个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
{www.nvzi91.cn
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;
}m.nvzi91.cn
}
}
复制代码
为了演示对应用的单个请求内和不同请求内的对象生命周期,样例包含一个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();
}www.kmrlyy.com
}
}
复制代码
然后有两个请求到达controller action。
观察请求内和请求间的OperationId哪个变化。
Transient 服务的对象总是不同的。每个controller和service都提供一个新的实例 Scoped的对象在一个request内是一样的,而不同的request间是不一样的。 Singleton对象是一直保持不表的。 Instance对象,对于每一个对象和request都是一样的,也即是在ConfigureServices中所指定的对象。
请求服务和应用服务
HttpContext中的一个ASP.NET请求中的可用服务分为两个集合, ApplicationServices 和 RequestServices 。
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将会解析类型和注入的依赖
推荐规范
当使用依赖注入时,记住如下推荐规范:
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
什么是依赖注入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)
{http://www.nvzi91.cn/fujianyan/29933.html
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
http://www.nvzi91.cn/zigongnamoyan/29934.html
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}http://www.nvzi91.cn/chunvmoxiufu/29935.html
复制代码<
4000
/div>
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;
http://www.nvzi91.cn/penqiangyan/29936.html
namespace DependencyInjectionSample.Controllers
{
public class CharactersController : Controller
{
private readonly ICharacterRepository _characterRepository;
public CharactersController(ICharacterRepository characterRepository)
{
_characterRepository = characterRepository;
}
http://www.nvzi91.cn/yindaoyan/29937.html
// 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();
}http://www.kmrlyy.com/lcnz/33448.html
return View(characters);
}
}
复制代码
接口ICharacterRepository只简单定义了两个方法,Controller通过其来操作Charcter实例。
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{http://www.kmrlyy.com/penqiangyan/33450.html
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
复制代码
接口有具体类型CharacterRepository来实现,在运行时被使用。
Note:CharacterRepository类只是使用DI的普通例子,你可以对应用所有的服务使用DI,而不仅仅是“仓储”和数据获取类。
using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;
http://www.kmrlyy.com/zgjl/33451.html
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();
}http://www.kmrlyy.com/gongjingai/33452.html
public void Add(Character character)
{http://www.kmrlyy.com/gongjingfeida/33453.html
_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>();
http://m.nvzi91.cn/gongjingmilan/29348.html
// 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;
http://m.nvzi91.cn/gongjingmilan/29348.html
namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{http://m.nvzi91.cn/gongjingyan/29349.html
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationInstance : IOperation
{
}
}http://m.nvzi91.cn/gongjingxirou/29350.html
复制代码
我们通过一个类来实现这些接口,接受一个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
{www.nvzi91.cn
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;
}m.nvzi91.cn
}
}
复制代码
为了演示对应用的单个请求内和不同请求内的对象生命周期,样例包含一个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();
}www.kmrlyy.com
}
}
复制代码
然后有两个请求到达controller action。
观察请求内和请求间的OperationId哪个变化。
Transient 服务的对象总是不同的。每个controller和service都提供一个新的实例 Scoped的对象在一个request内是一样的,而不同的request间是不一样的。 Singleton对象是一直保持不表的。 Instance对象,对于每一个对象和request都是一样的,也即是在ConfigureServices中所指定的对象。
请求服务和应用服务
HttpContext中的一个ASP.NET请求中的可用服务分为两个集合, ApplicationServices 和 RequestServices 。
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
相关文章推荐
- 比较分析 Spring AOP 和 AspectJ 之间的差别
- 树莓派折腾录三. 通过Synergy共享键盘鼠标(Raspberry Pi 2 model B+RaspEX/RASPBIAN JESSIE)
- Asp.net在IE10、IE11下事件丢失经验总结
- ASP.NET 共用类库
- asp.net webservice 调用实例
- Asp.net MVC使用KindEditor4
- asp.net 内置对象
- ASP.NET MVC从客户端中检测到有潜在危险的 Request.Form 值
- ASP.Net中命名空间Namespace浅析和使用例子
- ASP.NET(C#)常用数据加密和解密方法汇总
- ASP.NET MVC4(Razor)从客户端中检测到有潜在危险的 Request.Form 值
- Joint Sentiment/Topic Model & Aspect and Sentiment Unification Model
- ASP.NET WEB API必知必会:特性路由
- Raspberry Pi B的UART极简例程
- Java 8: 从永久代(PermGen)到元空间(Metaspace)
- asp.net 服务端控件的使用
- 兔子--html,js,php,ASP,ASP.NET,JSP的关系
- 现有分布式技术(socket、.net remoting、asp.net webservice、WSE、ES)和wcf的比较及优势
- asp.net实现动态添加table行
- TY_GASPX SQL