1. 引入Nuget包
Autofac
Autofac.Extensions.DependencyInjection
2. 修改Program.cs
將默認ServiceProviderFactory
指定為AutofacServiceProviderFactory
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseServiceProviderFactory(new AutofacServiceProviderFactory());
3. 修改Startup.cs
添加方法 ConfigureContainer
public void ConfigureContainer(ContainerBuilder builder)
{
// 在這里添加服務注冊
builder.RegisterType<TopicService>();
}
4. 配置Controller全部由Autofac創建
默認情況下,Controller的參數會由容器創建,但Controller的創建是有AspNetCore框架實現的。要通過容器創建Controller,需要在Startup中配置一下:
services.Replace(
ServiceDescriptor
.Transient<IControllerActivator, ServiceBasedControllerActivator>()
);
// 或者將Controller加入到Services中,這樣寫上面的代碼就可以省略了
services.AddControllersWithViews().AddControllersAsServices();
如果需要在Controller中使用屬性注入,需要在ConfigureContainer
中添加如下代碼
var controllerBaseType = typeof(ControllerBase);
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
.PropertiesAutowired();
5. 在Controller中使用
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly TopicService _service;
private readonly IServiceProvider _provider;
public TopicService Service { get; set; }
public TestController(TopicService service, IServiceProvider provider)
{
_service = service;
_provider = provider;
}
[HttpGet("{id}")]
public async Task<Result> GetTopics(int id)
{
// 構造函數注入
return await _service.LoadWithPosts(id);
}
[HttpGet("Get/{id}")]
public async Task<Result> GetTopics2(int id)
{
// 屬性注入
return await Service.LoadWithPosts(id);
}
}
6. 使用攔截器
添加Nuget包:Autofac.Extras.DynamicProxy
一、定義一個攔截器類,實現IInterceptor
public class TestInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("你正在調用方法 \"{0}\" 參數是 {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
Console.WriteLine("方法執行完畢,返回結果:{0}", invocation.ReturnValue);
}
}
二、修改Startup
的ConfigureContainer
方法
注意:
1、攔截器注冊要在使用攔截器的接口和類型之前
2、在類型中使用,僅virtual方法可以觸發攔截器
builder.RegisterType<TestInterceptor>(); // 要先注冊攔截器
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.AsImplementedInterfaces()
.EnableInterfaceInterceptors();
var controllerBaseType = typeof(ControllerBase);
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
.PropertiesAutowired() // 允許屬性注入
.EnableClassInterceptors(); // 允許在Controller類上使用攔截器
三、在需要使用攔截器的類或接口上添加描述
[Intercept(typeof(TestInterceptor))]
四、Sample
在接口上添加攔截器,當調用接口的方法時,都會進入攔截器
public class LogUtil : ILogUtil
{
public void Show(string message)
{
Console.WriteLine(message);
}
}
[Intercept(typeof(TestInterceptor))]
public interface ILogUtil
{
void Show(string message);
}
在Controller上使用攔截器
[Intercept(typeof(TestInterceptor))]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly TopicService _service;
private readonly IServiceProvider _provider;
private readonly ILogUtil _log;
public TopicService Service { get; set; }
public TestController(TopicService service, IServiceProvider provider, ILogUtil log)
{
_service = service;
_provider = provider;
_log = log;
}
// 會觸發攔截器
[HttpGet("{id}")]
public virtual async Task<Result> GetTopics(int id)
{
// 構造函數注入
return await _service.LoadWithPosts(id);
}
// 不會觸發攔截器
[HttpGet("Get/{id}")]
public async Task<Result> GetTopics2(int id)
{
return await Service.LoadWithPosts(id);
}
// 會由_log觸發攔截器
[HttpGet("Get2")]
public string GetTopics3()
{
_log.Show("abc");
return "Hello World";
}
// 會觸發攔截器2次
[HttpGet("Get2")]
public virtual string GetTopics4()
{
_log.Show("abc");
return "Hello World";
}
}
7. 一個接口多個實現
// 1、需要指定鍵值 是一個Object類型
// 2、注冊服務使用方法Keyed 參數為指定的鍵值中的值 (每一個服務的實現和鍵值要一一對應起來,這里不能重復)
// 3、獲取服務: 直接通過ResolveKeyed() 獲取服務無,方法需要傳入 指定對應的鍵值
// 先獲取一個IIndex,再通過IInex 索引來獲取服務的實例
containerBuilder.RegisterType<TestServiceD>().Keyed<ITestServiceD>(DeviceState.TestServiceD);
containerBuilder.RegisterType<TestServiceD_One>().Keyed<ITestServiceD>(DeviceState.TestServiceD_One);
containerBuilder.RegisterType<TestServiceD_Two>().Keyed<ITestServiceD>(DeviceState.TestServiceD_Two);
containerBuilder.RegisterType<TestServiceD_Three>().Keyed<ITestServiceD>(DeviceState.TestServiceD_Three);
// 為不同的實現指定名稱,這個比較簡單,推薦
containerBuilder.RegisterType<TestServiceD_Three>().Named<ITestServiceD>("three");
IContainer container = containerBuilder.Build();
IIndex<DeviceState, ITestServiceD> index = container.Resolve<IIndex<DeviceState, ITestServiceD>>();
ITestServiceD testServiceD= index[DeviceState.TestServiceD];
ITestServiceD TestServiceD_One = index[DeviceState.TestServiceD_One];
ITestServiceD TestServiceD_Two = index[DeviceState.TestServiceD_Two];
ITestServiceD TestServiceD_Three = index[DeviceState.TestServiceD_Three];
// 根據名稱解析
var t2 = container.ResolveNamed<ITestServiceD>("three");
Console.WriteLine("abc");
上面的做法在客戶端或者之前的MVC項目中可以這樣用。但在AspNetCore3.0中,我們似乎根本拿不到IContainer,所以不能手動Resolve指定的實現(請看下方的補充),下面是我自己摸索的辦法:
public interface ITestUtil
{
void Show(string content);
}
public class TestUtil1 : ITestUtil
{
public void Show(string content)
{
Console.WriteLine("TestUtil1:" + content);
}
}
public class TestUtil2 : ITestUtil
{
public void Show(string content)
{
Console.WriteLine($"TestUtil2:{content}");
}
}
別忘了在Startup中注冊服務
builder.RegisterType<TestUtil1>().As<ITestUtil>();
builder.RegisterType<TestUtil2>().As<ITestUtil>();
// 或者這樣做,如果程序級中的類型實現了某個接口,
// 會自動把該類型注冊為接口的實現,
// 這個方法比較狠,個人感覺還是慎用的好
builder.RegisterAssemblyTypes(this.GetType().Assembly)
.AsImplementedInterfaces()
.PropertiesAutowired();
// 默認情況下,構造函數注入和屬性注入的結果都是最后注冊的那個實現,
// 也就是TestUtil2
private readonly ITestUtil _util;
public HomeController(ITestUtil util, IServiceProvider provider)
{
_util = util;
// 如果知道注冊的順序,可以用這種方式,
// 第一個注冊是TestUtil1,所以這里返回TestUtil1
var util1 = provider.GetServices<ITestUtil>().ElementAtOrDefault(0);
util1?.Show("指定注冊為ITestUtil的第一個實現");
// 一般情況下用這種方式,指定成具體的類型 TestUtil1
var utilFirst = provider.GetServices<ITestUtil>()
.SingleOrDefault(t => t.GetType() == typeof(TestUtil1));
util1?.Show("指定名稱為TestUtil的實現");
}
補充:獲取容器 IContainer
下面是官方推薦的方式,定義一個繼承自Autofac.Module的類,重寫其Load方法,將Startup.cs中ConfigurationContainer方法中的代碼全部移至Load的方法內,
請注意該 RmesAutoFacModule
類中定義了一個靜態字段 _container
,用於接收注冊完成后的Container容器。
在 Load
方法的最后一行,builder
將容器傳出,我們使用 _container
接收到,后面就可以直接使用 RmesAutoFacModule.GetContainer()
獲取容器了。
public class RmesAutoFacModule : Module
{
private static IContainer _container;
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AsImplementedInterfaces()
.EnableInterfaceInterceptors();
var controllerBaseType = typeof(ControllerBase);
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
.PropertiesAutowired() // 允許屬性注入
.EnableClassInterceptors(); // 允許在Controller類上使用攔截器
// 手動高亮
builder.RegisterBuildCallback(container => _container = container);
}
public static IContainer GetContainer()
{
return _container;
}
}