深刻浅出依赖注入容器——Autofac

1.写在前面

相信你们对IOC和DI都耳熟能详,它们在项目里面带来的便利你们也都知道,微软新出的.NetCore也大量采用了这种手法。html

现在.NetCore也是大势所趋了,基本上以.Net为技术主导的公司都在向.NetCore转型了,我也一直在想抽时间写几篇.NetCore的文章,可无奈最近的项目实在太赶,也没时间写什么文章。前端

但今天这篇文章不是专门讲.NetCore的。jquery

算了,废话很少说,开始今天的主题吧。git

本篇文章主要讲解Autofac的基本使用和高级用法,以及可能会踩到的一些坑。web

 

2.基本概念

相信你们都知道IOC、DIP和DI是什么,用却是网上抄点代码过来,项目就能跑起来了,但真要你讲出个花样,估计仍是有点悬吧,这也是找工做面试时候常常被问起的。面试

 

控制反转

 谁控制谁?c#

IoC/DI容器控制应用主程序。后端

 控制什么?bash

IoC/DI容器控制对象自己的建立、实例化;控制对象之间的依赖关系。服务器

 何谓反转(对应于正向)?

由于如今应用程序不能主动去建立对象了,而是被动等待对象容器给它注入它所须要的资源,因此称之为反转。

 哪些方面反转了?

    1.建立对象

    2.程序获取资源的方式反了

 为什么须要反转?

    1.引入IoC/DI容器事后,体系更为松散,并且管理和维护以及项目升级更有序;

    2.类之间真正实现了解耦

 

依赖

 什么是依赖(按名称理解、按动词理解)?

依赖(按名称理解):依赖关系;依赖(按动词理解):依赖的动做

 谁依赖于谁?

应用程序依赖于IoC/DI容器

 为何须要依赖?

由于发生了反转,应用程序依赖的资源都是IoC/DI容器里面

 依赖什么东西?

应用程序依赖于IoC/DI容器为它注入所须要的资源。(好比:依赖关系)

 

注入

 谁注入于谁?

IoC/DI容器注入于应用程序。

 注入什么东西?

注入应用程序须要的对象,好比依赖关系。

 为什么要注入?

由于程序要正常运行须要访问这些对象。

 

IOC(控制反转Inversion of Control)

控制反转(Inversion of Control)就是使用对象容器反过来控制应用程序所须要的外部资源,这样的一种程序开发思想,调用者再也不建立被调用者的实例,由IOC框架实现(容器建立)因此称为控制反转;建立对象和对象非托管资源的释放都由外部容器去完成,实现项目层与层之间的解耦的一种设计思想。

 

DI(依赖注入)和DIP(依赖倒置原则)

相信不少人还分不清楚DI和DIP这两个词,甚至认为它们就是同一个词。

依赖倒置原则(Dependency Inversion Principle)为咱们提供了下降模块间耦合度的一种思路,而依赖注入(Dependency Injection)是一种具体的实施方法,容器建立好实例后再注入调用者称为依赖注入,就是应用程序依赖IOC容器来注入所须要的外部资源,这样一种程序的开发思想。

能作什么(What)?松散耦合对象,解耦项目架构层。

怎么作(How)?使用Autofac/Unity/Spring等框架类库,里面有实现好了的IoC/DI容器。

用在什么地方(Where)?凡是程序里面须要使用外部资源的状况,好比建立对象,均可以考虑使用IoC/DI容器。

 

DI和IOC是同一律念吗?

确定不是同一律念啊,但它们两个描述的是同一件事件,从不一样的角度来讲:IOC是从对象容器的角度;DI是从应用程序的角度。

控制反转的描述:对象容器反过来控制应用程序,控制应用程序锁所须要的一些对象,好比DbContext。

依赖注入的描述:应用程序依赖对象容器,依赖它注入所须要的外部资源。

 

对IoC的理解:

a. 应用程序无需主动new对象,而是描述对象应该如何被建立(构造方法、属性、方法参数等)。

b. 应用程序不须要主动装配对象之间的依赖关系,而是描述须要哪一个服务,IoC容器会帮你装配,被动接受装配。

c. 主动变被动,是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减小类与类之间依赖的设计原则。

 

3.Autofac/Unity简介

Autofac是.NET领域最为流行的IOC框架之一,传说是速度最快的一个,而今微软也很青睐的一个轻量高效的IOC框架,简单易上手且让人着迷;

Unity是微软官方出品的IOC框架,用法和Autofac大体差很少。

 

4.基本使用

经过nuget引入autofac;

准备几个实例对象:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Doge
{
     public  void  SayHello()
     {
         Console.WriteLine( "我是小狗,汪汪汪~" );
     }
}
public  class  Person
{
     public  string  Name {  get set ; }
     public  int  Age {  get set ; }
}

咱们传统的作法固然是直接new啦,但如今有了IOC容器,怎么还能那样作呢!

接下来准备IOC容器,经过IOC容器来实例化对象;

1
2
3
4
5
var builder =  new  ContainerBuilder(); //准备容器
builder.RegisterType<Doge>(); //注册对象
var container = builder.Build(); //建立容器完毕
var dog = container.Resolve<Doge>(); //经过IOC容器建立对象
dog.SayHello();

还能够直接实例注入:

1
builder.RegisterInstance( new  Doge()); //实例注入

单例托管:

1
builder.RegisterInstance(Singleton.GetInstance()).ExternallyOwned(); //将单例对象托管到IOC容器

还能够Lambda表达式注入:

1
2
builder.Register(c =>  new  Person() { Name =  "张三" , Age = 20 }); //Lambda表达式建立
Person p = container.Resolve<Person>();

还能够注入泛型类:

1
2
builder.RegisterGeneric( typeof (List<>));
List< string > list = container.Resolve<List< string >>();

你却说搞这么多过场,就为了建立一个对象?!咱不着急,接下来的才是重头戏

 

5.以接口方式注入

接着刚才的例子,添加个接口IAnimal,让Doge来实现它;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public  interface  IAnimal
{
     void  SayHello();
}
public  class  Doge : IAnimal
{
     public  void  SayHello()
     {
         Console.WriteLine( "我是小狗,汪汪汪~" );
     }
}
public  class  Cat : IAnimal
{
     public  void  SayHello()
     {
         Console.WriteLine( "我是小猫,喵喵喵~" );
     }
}
public  class  Pig : IAnimal
{
     public  void  SayHello()
     {
         Console.WriteLine( "我是小猪,呼呼呼~" );
     }
}

而后IOC注册对象的方式改变为:

1
2
3
4
5
var builder =  new  ContainerBuilder(); //准备容器
builder.RegisterType<Doge>().As<IAnimal>(); //映射对象
var container = builder.Build(); //建立容器完毕
var dog = container.Resolve<IAnimal>(); //经过IOC容器建立对象
dog.SayHello();

若是一个类型被屡次注册,以最后注册的为准。经过使用PreserveExistingDefaults() 修饰符,能够指定某个注册为非默认值。

1
2
3
4
5
builder.RegisterType<Doge>().As<IAnimal>(); //映射对象
builder.RegisterType<Cat>().As<IAnimal>().PreserveExistingDefaults(); //指定Cat为非默认值
 
var dog = container.Resolve<IAnimal>(); //经过IOC容器建立对象
dog.SayHello();

若是一个接口类被多个实例对象实现,能够进行命名,注入的时候使用名字进行区分

1
2
3
4
5
builder.RegisterType<Doge>().Named<IAnimal>( "doge" ); //映射对象
builder.RegisterType<Pig>().Named<IAnimal>( "pig" ); //映射对象
 
var dog = container.ResolveNamed<IAnimal>( "pig" ); //经过IOC容器建立对象
dog.SayHello();

ResolveNamed()只是Resolve()的简单重载,指定名字的服务实际上是指定键的服务的简单版本。有Named的方式很方便,可是只支持字符串,但有时候咱们可能须要经过其余类型做键,好比枚举。

先声明一个枚举类:

1
2
3
4
public  enum  AnumalType
{
     Doge, Pig, Cat
}

而后将上面的代码改形成:

1
2
3
4
5
builder.RegisterType<Doge>().Keyed<IAnimal>(AnumalType.Doge); //映射对象
builder.RegisterType<Pig>().Keyed<IAnimal>(AnumalType.Pig); //映射对象
 
var dog = container.ResolveKeyed<IAnimal>(AnumalType.Cat); //经过IOC容器建立对象
dog.SayHello();

不过这种方式是不推荐使用的,由于autofac容器会被看成Service Locator使用,推荐的作法是经过索引类型来实现,Autofac.Features.Indexed.IIndex<K,V>是Autofac自动实现的一个关联类型。使用IIndex<K,V>做为参数的构造函数从基于键的服务中选择须要的实现:

1
2
3
var animal = container.Resolve<IIndex<AnumalType,IAnimal>>();
var cat = animal[AnumalType.Cat];
cat.SayHello();

IIndex中第一个泛型参数要跟注册时一致,在例子中是AnimalType枚举。其余两种注册方法没有这样的索引查找功能,这也是为何设计者推荐Keyed注册的缘由之一。

 

6.自动装配

从容器中的可用对象中选择一个构造方法来建立对象,这个过程叫作自动装配。它是经过反射实现的,因此实际上容器创造对象的行为比较适合用在配置环境中。

改造Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  Person
{
     public  Person() { }
     public  Person( string  name)
     {
         Name = name;
     }
     public  Person( string  name,  int  age) :  this (name)
     {
         Age = age;
     }
     public  string  Name {  get set ; }
     public  int  Age {  get set ; }
}
 

注入时指定构造函数

Autofac默认从容器中选择参数最多的构造函数。若是想要选择一个不一样的构造函数,就须要在注册的时候就指定它:

1
builder.RegisterType<Person>().UsingConstructor( typeof ( string ));

这种写法将指定调用Person(string)构造函数,如该构造函数不存在则报错。

 

额外的构造函数参数:

  有两种方式能够添加额外的构造函数参数,在注册的时候和在检索的时候。在使用自动装配实例的时候这两种都会用到。

  注册时添加参数,使用WithParameters()方法在每一次建立对象的时候将组件和参数关联起来。

1
2
List<NamedParameter> pars =  new  List<NamedParameter>() {  new  NamedParameter( "Age" , 20),  new  NamedParameter( "Name" "张三" ) };
builder.RegisterType<Person>().WithParameters(pars); 
 

 在检索阶段添加参数:

  在Resolve()的时候提供的参数会覆盖全部名字相同的参数,在注册阶段提供的参数会覆盖容器中全部可能的服务。

 

7.MVC控制器和WebAPI控制器注入

固然是web MVC项目了,要在MVC或WebApi项目中用autofac,固然须要如下nuget包了,

准备几个Repository和Service;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public  class  Person
{
     public  int  Id {  get set ; }
     public  string  Name {  get set ; }
     public  int  Age {  get set ; }
}
public  interface  IRepository
{
     List<Person> GetPersons();
}
public  class  RepositoryBase : IRepository
{
     public  List<Person> Persons {  get set ; } =  new  List<Person>();
     public  RepositoryBase()
     {
         for  ( int  i = 0; i < 10; i++)
         {
             Persons.Add( new  Person()
             {
                 Id = i + 1,
                 Name =  "张三"  + i,
                 Age = 10 + i * 2
             });
         }
     }
     public  List<Person> GetPersons()
     {
     return  Persons;
     }
}
public  class  PersonRepository : RepositoryBase
{
}
public  interface  IService
{
     List<Person> GetPersons();
}
public  class  ServiceBase : IService
{
     public  IRepository Repository {  get set ; }
     public  ServiceBase(IRepository repository)
     {
         Repository = repository;
     }
     public  List<Person> GetPersons()
     {
         return  Repository.GetPersons();
     }
}
public  class  PersonService : ServiceBase
{
     public  PersonService(IRepository repository) :  base (repository)
     {
     }
}

网站启动时注册容器,在Global的Application_Start方法中注册IOC容器;

1
2
3
4
5
6
7
8
9
10
11
12
//注册IOC容器
var builder =  new  ContainerBuilder();
//告诉autofac未来要建立的控制器类存放在哪一个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly()); //注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); //注册WebAPI控制器
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来建立
GlobalConfiguration.Configuration.DependencyResolver =  new  AutofacWebApiDependencyResolver(container); //先给WebAPI注册
DependencyResolver.SetResolver( new  AutofacDependencyResolver(container)); //再给MVC注册

InstancePerDependency:为你注入的这个服务的生命周期.(注:生命周期咱们后面讲)

如今就能够在控制器中经过构造函数注入对象了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  HomeController : Controller
{
     public  IService Service {  get set ; }
     public  HomeController(IService service)
     {
         Service = service;
     }
     // GET: Home
     public  ActionResult Index()
     {
         var ps = Service.GetPersons();
         return  Json(ps, JsonRequestBehavior.AllowGet);
     }
}

 

8.属性注入

有时候咱们须要对对象的属性进行注入,好比EF上下文对象DbContext,很简单,两句话搞定;

咱们先来模拟一个DbContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  class  DataContext
{
     public  ICollection<Person> Persons {  get set ; } =  new  List<Person>();
     public  DataContext()
     {
         for  ( int  i = 0; i < 10; i++)
         {
             Persons.Add( new  Person()
             {
                 Id = i + 1,
                 Name =  "张三"  + i,
                 Age = 10 + i * 2
             });
         }
     }
}

在IOC容器中注入;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//注册IOC容器
var builder =  new  ContainerBuilder();
//告诉autofac未来要建立的控制器类存放在哪一个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency(); //注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency(); //注册WebAPI控制器
builder.RegisterFilterProvider(); //特性注入
builder.RegisterType<DataContext>().InstancePerRequest();
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来建立
GlobalConfiguration.Configuration.DependencyResolver =  new  AutofacWebApiDependencyResolver(container); //先给WebAPI注册
DependencyResolver.SetResolver( new  AutofacDependencyResolver(container)); //再给MVC注册

PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)表示属性注入,当实例对象存在有被IOC容器对象托管的时候,IOC容器会自动给属性实例化,Controller则能够不经过构造函数注入,直接属性注入;

1
2
3
4
5
6
7
8
public  class  HomeController : Controller
{
     public  DataContext DataContext {  get set ; }
     public  ActionResult GetPersons()
     {
         return  Json(DataContext.Persons, JsonRequestBehavior.AllowGet);
     }
}

关于参数PropertyWiringOptions的三个选项:

PropertyWiringOptions.None:默认的注入方式,当发现注入的对象有属性依赖的时候,会注入一个新的对象;

PropertyWiringOptions.AllowCircularDependencies:循环依赖注入方式,当发现注入的对象有循环依赖关系的时候,会循环注入;

PropertyWiringOptions.PreserveSetValues:保留预设值注入,当注入时发现属性已经有初始值,会自动忽略。

一样,咱们也能够改造刚才的Repository,经过属性注入DataContext;

1
2
3
4
5
6
7
8
public  class  RepositoryBase : IRepository
{
     public  DataContext DataContext {  get set ; }
     public  List<Person> GetPersons()
     {
         return  DataContext.Persons.ToList();
     }
}

上面是自动注入属性,有时候咱们还能够手动去注入属性:

1
builder.RegisterType<Person>().OnActivated(e => e.Instance.Name =  "李四" );

若是你预先知道属性的名字和值,你还可使用:

1
builder.RegisterType<Person>().WithProperty( "Name" "李四" );
 

9.方法注入

能够实现方法注入的方式有两种。

 

一、使用Activator

若是你使用委托来激活,只要调用这个方法在激活中:

1
2
3
4
5
6
builder.Register(c =>
{
     var result =  new  Person();
     result.SayHello( "my name is van" );
     return  result;
});

注意,使用这种方法,Person类里必需要有这个方法:

1
2
3
4
public  void  SayHello( string  hello)
{
     Console.WriteLine(hello);
}
 

二、使用Activating Handler

若是你使用另一种激活,好比反射激活,建立激活的事件接口OnActivating,这种方式仅需一行代码:

1
builder.RegisterType<Person>().OnActivating(e => e.Instance.SayHello( "my name is van!" ));
 

10. Attribute注入

有时候咱们还须要注入一些Attribute特性,在使用MVC的时候,确定会用到特性,好比ActionFilter,确定会有一些本身定义的特性,咱们想在里面作一些逻辑操做,好比用户登陆状态检查,咱们就须要在ActionFilter里面实例化Service对象,那么这些特性里面要用到相关的服务,该怎么注入呢?

很简单,咱们只须要在IOC容器构建时再加上builder.RegisterFilterProvider()便可;

1
2
3
4
5
6
7
8
9
10
11
12
13
//注册IOC容器
var builder =  new  ContainerBuilder();
//告诉autofac未来要建立的控制器类存放在哪一个程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly()); //注册MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); //注册WebAPI控制器
builder.RegisterFilterProvider(); //特性注入
//注册Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//将当前容器交给MVC底层,保证容器不被销毁,控制器由autofac来建立
GlobalConfiguration.Configuration.DependencyResolver =  new  AutofacWebApiDependencyResolver(container); //先给WebAPI注册
DependencyResolver.SetResolver( new  AutofacDependencyResolver(container)); //再给MVC注册

而后就能够直接经过属性的方式注入到ActionFilter中,是的,你没看错,就只须要这一行代码就能够了,特性里面就能够取到想要的服务了;

1
2
3
4
5
6
7
8
9
10
11
public  class  MyActionFilterAttribute : ActionFilterAttribute
{
     public  IService Service {  get set ; }
     /// <summary>在执行操做方法以前由 ASP.NET MVC 框架调用。</summary>
     /// <param name="filterContext">筛选器上下文。</param>
     public  override  void  OnActionExecuting(ActionExecutingContext filterContext)
     {
         var ps = Service.GetPersons();
         base .OnActionExecuting(filterContext);
     }
}
 

11.Hangfire注入

若是咱们项目中还用到了hangfire这样的分布式任务调度框架,咱们也能够经过autofac来进行对象的依赖注入;

首先咱们引入hangfire的一些基础包,并配置好hangfire;

固然,还要引入hangfire的autofac支持库:

Startup.cs类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  class  Startup
{
     public  void  Configuration(IAppBuilder app)
     {
         //配置任务持久化到内存
         GlobalConfiguration.Configuration.UseMemoryStorage();
         //启用dashboard
         app.UseHangfireServer( new  BackgroundJobServerOptions { WorkerCount = 10 });
         app.UseHangfireDashboard( "/taskcenter" new  DashboardOptions()
         {
             Authorization =  new [] {  new  MyRestrictiveAuthorizationFilter() }
         });  //注册dashboard的路由地址
     }
}
public  class  MyRestrictiveAuthorizationFilter : IDashboardAuthorizationFilter
{
     //public RedisHelper RedisHelper { get; set; } = new RedisHelper();
     public  bool  Authorize(DashboardContext context)
     {
         return  true ;
     }
}

Global.Application_Start():

1
2
3
4
5
6
7
8
9
Hangfire.GlobalConfiguration.Configuration.UseMemoryStorage();
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container);  //注册IOC容器
Server =  new  BackgroundJobServer( new  BackgroundJobServerOptions
{
     ServerName = $ "{Environment.MachineName}" //服务器名称
     SchedulePollingInterval = TimeSpan.FromSeconds(1),
     ServerCheckInterval = TimeSpan.FromSeconds(1),
     WorkerCount = Environment.ProcessorCount * 2,
});

配置hangfire后台任务;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  interface  IHangfireBackJob
{
     DataContext DataContext {  get set ; }
     void  UpdatePersons();
}
public  class  HangfireBackJob : IHangfireBackJob
{
     public  DataContext DataContext {  get set ; }
     public  void  UpdatePersons()
     {
         DataContext.Persons.Clear();
         for  ( int  i = 0; i < 10; i++)
         {
             DataContext.Persons.Add( new  Person()
             {
                 Name =  "李四"  + i,
                 Age = 20 + i * 2,
                 Id = i + 1
             });
         }
     }
}

配置IOC容器:

1
2
3
builder.RegisterType<DataContext>().InstancePerBackgroundJob(MatchingScopeLifetimeTags.RequestLifetimeScopeTag, AutofacJobActivator.LifetimeScopeTag);  //指定生命周期为每一个后台任务依赖,而且每次http请求内单例
builder.RegisterType<BackgroundJobClient>().SingleInstance(); //指定生命周期为单例
builder.RegisterType<HangfireBackJob>().As<IHangfireBackJob>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();

建立任务:

1
2
3
4
5
6
7
public  ActionResult CreateJob()
{
     var type =  typeof (IHangfireBackJob);
     var job =  new  Job(type, type.GetMethod( "UpdatePersons" ));
     BackgroundJobClient.Create(job,  new  EnqueuedState());
     return  Content( "ok" );
}

 

12. SignalR注入

若是咱们项目中还用到了SignalR这样的socket通讯框架,咱们也能够经过autofac来进行对象的依赖注入;按照刚才hangfire的套路,咱们先引入SignalR的一些基础包,并配置好SignalR;固然,还要引入SignalR的autofac支持库:

注册容器的方式和上面的例子都有些不一样了,SignalR的IOC容器注入必须在Startup里面注入,不能在Global中注入,由于SignalR是Owin项目,OWIN 集成常见错误为使用GlobalHost。OWIN中配置你会抓狂. OWIN集成中,任何地方你都不能引用 。当年博主我也不太清楚,反正当时也踩了这一大坑。至于hangfire也是owin项目,为何能够在Global里面注入,由于hangfire不是用GlobalHost去注入的,而是GlobalConfiguration。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  Startup
{
     public  void  Configuration(IAppBuilder app)
     {
         var builder =  new  ContainerBuilder();
         var config =  new  HubConfiguration();
         builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
         builder.RegisterType<DataContext>().InstancePerLifetimeScope();
         var container = builder.Build();
         config.Resolver =  new  AutofacDependencyResolver(container);
         app.UseAutofacMiddleware(container);
         app.MapSignalR( "/signalr" , config);
     }
}

而后,hub就能够用属性注入,构造函数注入等方式了;

1
2
3
4
5
6
7
8
9
[HubName( "myhub" )] //声明hub的显式名字
public  class  MyHub : Hub
{
     public  DataContext DataContext {  get set ; }
     public  void  Send()
     {
         Clients.All.hello(DataContext.Persons.ToJsonString());
     }
}

前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
< html >
< head >
< meta  name = "viewport"  content = "width=device-width"  />
< title >WebSocket</ title >
< script  src = "~/Scripts/jquery-3.3.1.js" ></ script >
< script  src = "~/Scripts/jquery.signalR-2.2.3.js" ></ script >
< script  src = "~/signalr/hubs" ></ script > <!--后端SignalR根据注册的路由生成的js脚本-->
</ head >
< body >
< span  class = "msg" ></ span >
</ body >
</ html >
< script >
$(function () {
     //客户端都以驼峰命名法使用
     let hubProxy = $.connection["myhub"]; //hub代理对象
     var $msg = $(".msg");
     //注册客户端方法
     hubProxy.client.hello = function (msg) {
         $msg.text(msg);
     }
     //向服务端发数据
     $.connection.hub.start().done(function () {
         hubProxy.server.send();
     });
});
</ script >

注意:因为 SignalR 是内部构件,因此不支持SignalR每请求的生命周期依赖。

 

13. 注册程序集

然而大多数时候咱们的项目不少代码是直接用代码生成器生成的,像Repository和Service并不是彻底手写的,并不想这么一个一个类的去builder.RegisterType,那多麻烦啊,因此autofac还提供了程序集批量注入的选项;一句话搞定:

1
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith( "Repository" ) || t.Name.EndsWith( "Service" )).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();

这里真的只有一句话!

 

14. Resolve的参数

当注册或者检索component的时候可使用参数。

 

一、传递参数给Resolve

Resolve接受可变参数或IEnumerable<T>传入多个值:

1
Person p = container.Resolve<Person>( new  NamedParameter( "name" "王五" ),  new  NamedParameter( "age" , 22));

Person下必须添加以下构造函数:

1
2
3
4
5
public  Person( string  name,  int  age) :  this (name)
{
     Name = name;
     Age = age;
}
 

二、可用的参数类型

  Autofac提供几种不一样的参数对应策略:

1
2
3
4
NamedParameter :像上面那样对应的参数名字
TypedParameter:对应到参数的类型(必须是具体的类型)
ResolvedParameter:灵活的参数匹配
NamedParameter 和TypedParameter:只能提供常量参数
 

三、从表达式中使用参数

若是使用表达式注册的方式,可使用第二个可用的委托参数来得到参数。

1
2
builder.Register((c, p) =>  new  Person(p.Named< string >( "name" ), p.Named< int >( "age" )));
Person pp = container.Resolve<Person>( new  NamedParameter( "name" "王五" ),  new  NamedParameter( "age" , 22));
 

15. 对象生命周期InstancePerDependency

对每个依赖或每一次调用建立一个新的惟一的实例。也称做瞬态或者工厂,使用PerDependency做用域,服务对于每次请求都会返回互补影响实例。在没有指定其余参数的状况下,这也是默认的建立实例的方式。

官方文档解释:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.)

 

InstancePerLifetimeScope

在一个生命周期域中,每个依赖或调用建立一个单一的共享的实例,且每个不一样的生命周期域,实例是惟一的,不共享的,也就是线程内惟一对象。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a single ILifetimeScope gets the same, shared instance. Dependent components in different lifetime scopes will get different instances.

 

InstancePerMatchingLifetimeScope

在一个作标识的生命周期域中,每个依赖或调用建立一个单一的共享的实例。打了标识了的生命周期域中的子标识域中能够共享父级域中的实例。若在整个继承层次中没有找到打标识的生命周期域,则会抛出异常:DependencyResolutionException。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope tagged with any of the provided tags value gets the same, shared instance. Dependent components in lifetime scopes that are children of the tagged scope will share the parent's instance. If no appropriately tagged scope can be found in the hierarchy an DependencyResolutionException is thrown.

 

InstancePerOwned

在一个生命周期域中所拥有的实例建立的生命周期中,每个依赖组件或调用Resolve()方法建立一个单一的共享的实例,而且子生命周期域共享父生命周期域中的实例。若在继承层级中没有发现合适的拥有子实例的生命周期域,则抛出异常:DependencyResolutionException。

官方文档解释:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope created by an owned instance gets the same, shared instance. Dependent components in lifetime scopes that are children of the owned instance scope will share the parent's instance. If no appropriate owned instance scope can be found in the hierarchy an DependencyResolutionException is thrown.

 

SingleInstance

每一次依赖组件或调用Resolve()方法都会获得一个相同的共享的实例。其实就是单例模式。

官方文档解释:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance.

 

InstancePerRequest

在一次Http请求上下文中,共享一个组件实例。仅适用于asp.net mvc开发。

 

16. 须要Dispose的对象的注入

像RedisClient、DbContext这类对象须要用完以后被Dispose的,也很简单;

改形成可Dispose的DataContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  DataContext : IDisposable
{
     public  ICollection<Person> Persons {  get set ; } =  new  List<Person>();
     public  DataContext()
     {
         for  ( int  i = 0; i < 10; i++)
         {
             Persons.Add( new  Person()
             {
                 Id = i + 1,
                 Name =  "张三"  + i,
                 Age = 10 + i * 2
             });
         }
     }
     /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary>
     public  void  Dispose()
     {
         Persons =  null ;
     }
}

而后在建立IOC容器的时候:

1
builder.RegisterType<DataContext>().OnRelease(db => db.Dispose());

给对象注册OnRelease事件,每次使用完的时候会由IOC容器去释放。

 

17.绑定事件

在对象生命周期的不一样阶段使用事件。Autofac暴露五个事件接口供实例的按以下顺序调用

1
2
3
4
5
1)OnRegistered
2)OnPreparing
3)OnActivated
4)OnActivating
5)OnRelease

  这些事件会在注册的时候被订阅,或者被附加到IComponentRegistration 的时候。

1
builder.RegisterType<Person>().OnRegistered(e => Console.WriteLine( "在注册的时候调用!" )).OnPreparing(e => Console.WriteLine( "在准备建立的时候调用!" )).OnActivating(e => Console.WriteLine( "在建立以前调用!" )).OnActivated(e => Console.WriteLine( "建立以后调用!" )).OnRelease(e => Console.WriteLine( "在释放占用的资源以前调用!" ));

  以上示例输出以下:

  

 

  OnActivating

  组件被建立以前调用,在这里你能够:

1
2
3
1)将实例转向另一个或者使用代理封装它
2)进行属性注入
3)执行其余初始化工做
 

  OnActivated

  在component被彻底建立的时候调用一次。在这个时候你能够执行程序级别的一些工做(这些工做依赖于对象被彻底建立)-这种状况很罕见。

 

  OnRelease

  替代component的标准清理方法。实现了IDisposable 接口的标准清理方法(没有标记为ExternallyOwned) 经过调用Dispose 方法。没有实现IDisposable或者被标记为ExternallyOwned的清理方法是一个空函数-不执行任何操做。OnRelease 就是用来覆盖默认的清理行为的。

 

18. .NetCore中使用Autofac

在 Starpup 中 配置 Autofac,注意的是要将 ConfigureServices 的返回类型从void类型 改为IServiceProvider,并 return new AutofacServiceProvider(ApplicationContainer); 官方解释是,让第三方容器接管Core的默认DI。

1
2
3
4
5
6
7
8
9
10
11
public  IServiceProvider ConfigureServices(IServiceCollection services)
{
     services.AddMvc().AddControllersAsServices().AddViewComponentsAsServices().AddTagHelpersAsServices(); //将控制器以及视图开启属性注入方式
     var builder =  new  ContainerBuilder();
     builder.RegisterType<DataContext>().InstancePerLifetimeScope();
     builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith( "Repository" ) || t.Name.EndsWith( "Service" )).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();
     builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith( "Controller" )).AsSelf().PropertiesAutowired().InstancePerDependency(); //注册控制器为属性注入,若是你想要控制器支持属性注入,这句很重要
     builder.Populate(services);
     var container = builder.Build();
     return  new  AutofacServiceProvider(container);
}
 

源码下载:

http://masuit.com/download?path=/file/AutofacDemo.7z

相关文章
相关标签/搜索