准备几个实例对象:
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容器来实例化对象;
var builder = new ContainerBuilder();//准备容器
builder.RegisterType<Doge>();//注册对象
var container = builder.Build();//创建容器完毕
var dog = container.Resolve<Doge>();//通过IOC容器创建对象
dog.SayHello();
还可以直接实例注入:
builder.RegisterInstance(new Doge());//实例注入
单例托管:
builder.RegisterInstance(Singleton.GetInstance()).ExternallyOwned();//将单例对象托管到IOC容器
还可以Lambda表达式注入:
builder.Register(c => new Person() { Name = "张三", Age = 20 });//Lambda表达式创建
Person p = container.Resolve<erson>();
还可以注入泛型类:
builder.RegisterGeneric(typeof(List<>));
List<string> list = container.Resolve<List<string>>();
你却说搞这么多过场,就为了创建一个对象?!咱不着急,接下来的才是重头戏
5.以接口方式注入
接着刚才的例子,添加个接口IAnimal,让Doge来实现它;
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注册对象的方式改变为:
var builder = new ContainerBuilder();//准备容器
builder.RegisterType<Doge>().As<IAnimal>();//映射对象
var container = builder.Build();//创建容器完毕
var dog = container.Resolve<IAnimal>();//通过IOC容器创建对象
dog.SayHello();
如果一个类型被多次注册,以最后注册的为准。通过使用PreserveExistingDefaults() 修饰符,可以指定某个注册为非默认值。
builder.RegisterType<Doge>().As<IAnimal>();//映射对象
builder.RegisterType<Cat>().As<IAnimal>().PreserveExistingDefaults();//指定Cat为非默认值
var dog = container.Resolve<IAnimal>();//通过IOC容器创建对象
dog.SayHello();
如果一个接口类被多个实例对象实现,可以进行命名,注入的时候使用名字进行区分
builder.RegisterType<Doge>().Named<IAnimal>("doge");//映射对象
builder.RegisterType<ig>().Named<IAnimal>("pig");//映射对象
var dog = container.ResolveNamed<IAnimal>("pig");//通过IOC容器创建对象
dog.SayHello();
不过这种方式是不推荐使用的,因为autofac容器会被当作Service Locator使用,推荐的做法是通过索引类型来实现,Autofac.Features.Indexed.IIndex<K,V>是Autofac自动实现的一个关联类型。使用IIndex<K,V>作为参数的构造函数从基于键的服务中选择需要的实现:
var animal = container.Resolve<IIndex<AnumalType,IAnimal>>();
var cat = animal[AnumalType.Cat];
cat.SayHello();
IIndex中第一个泛型参数要跟注册时一致,在例子中是AnimalType枚举。其他两种注册方法没有这样的索引查找功能,这也是为什么设计者推荐Keyed注册的原因之一。
6.自动装配
从容器中的可用对象中选择一个构造方法来创建对象,这个过程叫做自动装配。它是通过反射实现的,所以实际上容器创造对象的行为比较适合用在配置环境中。
改造Person类:
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默认从容器中选择参数最多的构造函数。如果想要选择一个不同的构造函数,就需要在注册的时候就指定它:
builder.RegisterType<erson>().UsingConstructor(typeof(string));
这种写法将指定调用Person(string)构造函数,如该构造函数不存在则报错。
额外的构造函数参数:
有两种方式可以添加额外的构造函数参数,在注册的时候和在检索的时候。在使用自动装配实例的时候这两种都会用到。
注册时添加参数,使用WithParameters()方法在每一次创建对象的时候将组件和参数关联起来。
List<NamedParameter> pars = new List<NamedParameter>() { new NamedParameter("Age", 20), new NamedParameter("Name", "张三") };
builder.RegisterType<erson>().WithParameters(pars);
在检索阶段添加参数:
在Resolve()的时候提供的参数会覆盖所有名字相同的参数,在注册阶段提供的参数会覆盖容器中所有可能的服务。
7.MVC控制器和WebAPI控制器注入
当然是web MVC项目了,要在MVC或WebApi项目中用autofac,当然需要以下nuget包了,
准备几个Repository和Service;
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public interface IRepository
{
List<erson> GetPersons();
}
public class RepositoryBase : IRepository
{
public List<erson> Persons { get; set; } = new List<erson>();
public RepositoryBase()
{
for (int i = 0; i < 10; i++)
{
Persons.Add(new Person()
{
Id = i + 1,
Name = "张三" + i,
Age = 10 + i * 2
});
}
}
public List<erson> GetPersons()
{
return Persons;
}
}
public class PersonRepository : RepositoryBase
{
}
public interface IService
{
List<erson> 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容器;
//注册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:为你注入的这个服务的生命周期.(注:生命周期我们后面讲)
现在就可以在控制器中通过构造函数注入对象了
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);
}
}
注意:由于 SignalR 是内部构件,所以不支持SignalR每请求的生命周期依赖。
13. 注册程序集
然而大多数时候我们的项目很多代码是直接用代码生成器生成的,像Repository和Service并非完全手写的,并不想这么一个一个类的去builder.RegisterType,那多麻烦啊,所以autofac还提供了程序集批量注入的选项;一句话搞定:
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith("Repository") || t.Name.EndsWith("Service")).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();
这里真的只有一句话!
14. Resolve的参数
当注册或者检索component的时候可以使用参数。
1、传递参数给Resolve
Resolve接受可变参数或IEnumerable<T>传入多个值:
Person p = container.Resolve<;Person>(new NamedParameter("name", "王五"), new NamedParameter("age", 22));
Person下必须添加如下构造函数:
public Person(string name, int age) : this(name)
{
Name = name;
Age = age;
}
2、可用的参数类型
Autofac提供几种不同的参数对应策略:
NamedParameter :像上面那样对应的参数名字
TypedParameter:对应到参数的类型(必须是具体的类型)
ResolvedParameter:灵活的参数匹配
NamedParameter 和TypedParameter:只能提供常量参数
3、从表达式中使用参数
如果使用表达式注册的方式,可以使用第二个可用的委托参数来获得参数。
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:
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容器的时候:
builder.RegisterType<DataContext>().OnRelease(db => db.Dispose());
给对象注册OnRelease事件,每次使用完的时候会由IOC容器去释放。
17.绑定事件
在对象生命周期的不同阶段使用事件。Autofac暴露五个事件接口供实例的按如下顺序调用
1)OnRegistered
2)OnPreparing
3)OnActivated
4)OnActivating
5)OnRelease
这些事件会在注册的时候被订阅,或者被附加到IComponentRegistration 的时候。
builder.RegisterType<;Person>().OnRegistered(e => Console.WriteLine("在注册的时候调用!")).OnPreparing(e => Console.WriteLine("在准备创建的时候调用!")).OnActivating(e => Console.WriteLine("在创建之前调用!")).OnActivated(e => Console.WriteLine("创建之后调用!")).OnRelease(e => Console.WriteLine("在释放占用的资源之前调用!"));
以上示例输出如下: