0%

Autofac 踩雷筆記

 

最近專案因為用 autofac , 又踩一堆雷 , 套件換來換去心累 , 只好筆記一下 , 我是用 net 5 進行實作 , 參考這個大陸人

.net core

這邊直接開個預設 web api 範本來起手

先安裝 AutofacAutofac.Extensions.DependencyInjection , 接著調整 Program 多插上這句 UseServiceProviderFactory(new AutofacServiceProviderFactory())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Program
{
public static void Main( string[] args )
{
CreateHostBuilder( args ).Build().Run();
}

public static IHostBuilder CreateHostBuilder( string[] args ) =>
Host.CreateDefaultBuilder( args )
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults( webBuilder =>
{
webBuilder.UseStartup<Startup>();
} );
}

接著手動在 Startup 加入方法 ConfigureContainer 然後寫 autofac 的設定在裡面 , 我是遇到 Cannot choose between multiple constructors with equal length 1 on type 這個雷 , 所以設定去找自己想要的建構子
ConfigureServices 先調整這句 services.AddControllers() => services.AddControllers().AddControllersAsServices()
接著加入這句 services.AddAutofac( ConfigureContainer ) 即可 , 另外也可以繼承自 autofac 的 Module override Load 方法寫設定在裡面也可以

Startup

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
56
57
public class Startup
{
public Startup( IConfiguration configuration )
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services )
{

services.AddControllers().AddControllersAsServices();
services.AddSwaggerGen( c =>
{
c.SwaggerDoc( "v1", new OpenApiInfo { Title = "WebApplicationAutofac", Version = "v1" } );
} );

services.AddAutofac( ConfigureContainer );
}

public void ConfigureContainer( ContainerBuilder builder )
{
//方法 1 直接寫裡面即可
builder.RegisterType<WeatherForecastController>().PropertiesAutowired();
builder.RegisterType<TestDI>()
.AsSelf()
.UsingConstructor(typeof(string))
.WithParameter( new TypedParameter( typeof( string ), "s" ) )
.WithParameter( new TypedParameter( typeof( int ), 123 ) )
.PropertiesAutowired();

//方法 2
//builder.RegisterModule<ConfigureAutofac>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IWebHostEnvironment env )
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI( c => c.SwaggerEndpoint( "/swagger/v1/swagger.json", "WebApplicationAutofac v1" ) );
}

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints( endpoints =>
{
endpoints.MapControllers();
} );
}
}

ConfigureAutofac

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConfigureAutofac : Module
{
protected override void Load( ContainerBuilder builder )
{
builder.RegisterType<WeatherForecastController>().PropertiesAutowired();
builder.RegisterType<TestDI>()
.AsSelf()
.UsingConstructor(typeof(string))
.WithParameter( new TypedParameter( typeof( string ), "s" ) )
.WithParameter( new TypedParameter( typeof( int ), 123 ) )
.PropertiesAutowired();
}
}

TestDI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestDI    {
public ILogger<TestDI> MyProperty { get; set; }
public TestDI( string s )
{
Console.WriteLine( s );
}

public TestDI( int i )
{
Console.WriteLine( i );
}

public void Test()
{
MyProperty.LogInformation( "test" );
Console.WriteLine("test");
}
}

abp

abp 內建就直接整合 autofac
因為炸了這個 error Cannot choose between multiple constructors with equal length 1 on type 不得不手動設定 , 不然預設就可以直接無腦用 DI
以官方的範例為例只要加在 Acme.BookStore.Web 這個專案底下的 BookStoreWebModule 這個類別的 PreConfigureServices 方法即可
後來發現直接放在 ConfigureServices 也是可以動

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
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(
typeof(BookStoreResource),
typeof(BookStoreDomainModule).Assembly,
typeof(BookStoreDomainSharedModule).Assembly,
typeof(BookStoreApplicationModule).Assembly,
typeof(BookStoreApplicationContractsModule).Assembly,
typeof(BookStoreWebModule).Assembly
);

});

context.Services.PreConfigure<IMvcBuilder>( mvcBuilder =>
{
mvcBuilder.AddNewtonsoftJson(
options => options.SerializerSettings.ContractResolver = new DefaultContractResolver() );
} );
context.Services.PreConfigure<AbpJsonOptions>( options =>
{
options.UseHybridSerializer = false;
} );

//設定手動 DI
var builder = context.Services.GetContainerBuilder();

//abp 的話這句預設就會幫你注入
//builder.RegisterType<MyAppService>().PropertiesAutowired();

//手動設定選擇建構子
builder.RegisterType<TestDI>()
.AsSelf()
.UsingConstructor( typeof( string ) )
.WithParameter( new TypedParameter( typeof( string ), "s" ) )
//.WithParameter( new TypedParameter( typeof( int ), 123 ) )
.PropertiesAutowired();
}

TestDI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestDI    {
public ILogger<TestDI> MyProperty { get; set; }
public TestDI( string s )
{
Console.WriteLine( s );
}

public TestDI( int i )
{
Console.WriteLine( i );
}

public void Test()
{
MyProperty.LogInformation( "test" );
Console.WriteLine("test");
}
}

MyAppService

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
public class MyAppService : BookStoreAppService
{
//private readonly TestService service;
//public MyAppService( TestService service )
//{
// this.service = service;
//}

protected TestService MyTestService => lazy.Value;
private readonly Lazy<TestService> lazy = null;
public MyAppService()
{

lazy = new Lazy<TestService>( () => new TestService( ) );

}

public TestDI Haha { get; set; }

[HttpGet("Ha")]
public void Ha()
{
Haha.Test();
Haha.Test();
}
}

最後萬一炸 Could not find ContainerBuilder. Be sure that you have called UseAutofac method before!
可以參考這篇
ProgramUseAutofac() 順序調整一下即可
Program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
//調整過的位置
.UseAutofac()
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
//原本位置
//.UseAutofac()
.UseSerilog();

lazy 地雷

今天遇到的雷筆記一下 , 因為舊系統用了一堆 lazy , 三不五時就炸 null , 只好想辦法乖乖把 lazy 先暫時換掉

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
public class TestLazyService: ITransientDependency
{
protected LaSaiService LazyService => lazyService.Value;
private readonly Lazy<LaSaiService> lazyService = null;

public TestLazyService()
{
lazyService = new Lazy<LaSaiService>( () => new LaSaiService() );
}

public void Test()
{
LazyService.Test();
}

}

public class LaSaiService: ITransientDependency
{
public ILogger<LineService> Logger { get; set; }

public LaSaiService()
{
//注意這句使用 property injection 時一開始在建構子裡面會是 null
if(Logger is null)
Console.WriteLine("Logger is null");
}

public void Test()
{
//這句也是關鍵 , 使用 lazy 去 new 出來 LaSaiService 的話 , 這邊會是 null
Logger.LogInformation( "TEST" );
}

}

ConfigureServices 插入這個測試看看

1
2
3
4
5
6
7
8
9
10
11
private void TestDI()
{
ContainerBuilder builder = new ContainerBuilder();

builder.RegisterType<LaSaiService>()
.PropertiesAutowired();
//TestLazyService

builder.RegisterType<TestLazyService>()
.PropertiesAutowired();
}

後來又遇到 circular-dependencies 可以這樣解看看

快速寫 Autofac 屬性注入

實務上遇到一個煩人的問題 , 因為舊系統搬到 .net core 一堆物件需要使用 DI
如果用建構子注入的話會造成太多建構子相依 , 導致系統難以修改 , 所以改用 property injection 找到 ConfigureServices
接著加入這段 , 可想而知一堆要加進去手動寫的話會想哭

1
2
3
4
5
6
7
8
//todo: property injection
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<A>().PropertiesAutowired();
builder.RegisterType<B>().PropertiesAutowired();
builder.RegisterType<C>().PropertiesAutowired();
builder.RegisterType<D>().PropertiesAutowired();
builder.RegisterType<E>().PropertiesAutowired();
builder.RegisterType<F>().PropertiesAutowired();

所以參考這篇直接依靠 powershell 快速收工

1
ls | ForEach-Object -Process { "builder.RegisterType<" + [System.IO.Path]::GetFileNameWithoutExtension($_) + ">().PropertiesAutowired();" }

abp 用法參考強國人

關閉