Autofac 是一款優秀的IOC的開源工具,完美的适配.Net特性,但是有時候我們想通過屬性注入的方式來擷取我們注入的對象,對不起,有時候你還真是擷取不到,這因為什麼呢?
1.你對Autofac 不太了解,在這個浮躁的社會,沒有人會認真的了解每個開源項目,隻要求能用就行
2.沒有時間了解,你是一個很忙的人,工作很忙,應酬很忙
3.剛開始使用Autofac 還沒來得及深入了解就要做項目。
不管是什麼原因,總之我們注入的屬性就是無法直接由autofac 自動注入,或者說我們希望由Autofac自動注入的屬性為Null,這可是很讓我們糾結的事情。下面我們就來通過一系列我們可能的操作來還原事情的真相。
真相1:通過registerType注冊類型,希望通過屬性擷取注入的類型。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 ContainerBuilder builder = new ContainerBuilder();
6 // builder.RegisterModule(new LoggingModule());
7 builder.RegisterType<Test>();
8 builder.RegisterType<Test2>();
9 var container = builder.Build();
10
11 Test2 test2 = container.Resolve<Test2>();
12 test2.Show();
13
14 }
15 }
16
17 public class Test {
18 public void Show()
19 {
20 Console.WriteLine("FileName");
21 }
22 }
23
24 public class Test2
25 {
26 public Test Test { get; set; }
27
28 public void Show()
29 {
30 if (Test != null)
31 {
32 Test.Show();
33 }
34 }
35 }
我們通過RegisterType注入了兩個類型Test和Test2,其中Test2中由一個屬性為Test類型的Test變量,我們期望Test會自動注入,我們可以直接使用Test.Show方法,但是現實情況是:
我們期望會被Autofac自動注入的屬性為Null,我們的期望落空。既然通過RegisterType無法注入,那麼通過Register注入呢,是否可行呢?
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 ContainerBuilder builder = new ContainerBuilder();
6 // builder.RegisterModule(new LoggingModule());
7 //builder.RegisterType<Test>();
8
9
10 builder.Register(t => new Test()).As<Test>();
11 builder.RegisterType<Test2>();
12 var container = builder.Build();
13
14 Test2 test2 = container.Resolve<Test2>();
15 test2.Show();
16
17 }
18 }
19
20 public class Test {
21 public void Show()
22 {
23 Console.WriteLine("FileName");
24 }
25 }
26
27 public class Test2
28 {
29 public Test Test { get; set; }
30
31 public void Show()
32 {
33 if (Test != null)
34 {
35 Test.Show();
36 }
37 }
38 }
我們通過Register注入一個執行個體,最後我們的期望還是落空了,還有一種方式就是通過Module進行注冊,這種方式還不行,那就說明autofac的屬性注入式騙人的(心裡想的),我們來通過Module來實作。
真相3:通過module進行注冊
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 ContainerBuilder builder = new ContainerBuilder();
6 builder.RegisterModule(new LoggingModule());
7 //builder.RegisterType<Test>();
8
9
10 //builder.Register(t => new Test()).As<Test>();
11 //builder.RegisterType<Test2>();
12 var container = builder.Build();
13
14 //Test2 test2 = container.Resolve<Test2>();
15 Test2 ee = new Test2();
16 ee.Show();
17
18 }
19 }
20
21 public class Test {
22 public void Show()
23 {
24 Console.WriteLine("FileName");
25 }
26 }
27
28 public class Test2
29 {
30 public Test Test { get; set; }
31
32 public void Show()
33 {
34 if (Test != null)
35 {
36 Test.Show();
37 }
38 }
39 }
40 public class LoggingModule : Module
41 {
42 private readonly ConcurrentDictionary<string, Test> _loggerCache;
43
44 public LoggingModule()
45 {
46 _loggerCache = new ConcurrentDictionary<string, Test>();
47 }
48
49 protected override void Load(ContainerBuilder moduleBuilder)
50 {
51 // by default, use Coevery's logger that delegates to Castle's logger factory
52 moduleBuilder.RegisterType<Test>().As<Test>().InstancePerLifetimeScope();
53
54
55 // call CreateLogger in response to the request for an ILogger implementation
56 // moduleBuilder.Register(CreateLogger).As<ILogging.ILogger>().InstancePerDependency();
57
58 }
59
60 protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
61 {
62 var implementationType = registration.Activator.LimitType;
63
64 // build an array of actions on this type to assign loggers to member properties
65 var injectors = BuildLoggerInjectors(implementationType).ToArray();
66
67 // if there are no logger properties, there's no reason to hook the activated event
68 if (!injectors.Any())
69 return;
70
71 // otherwise, whan an instance of this component is activated, inject the loggers on the instance
72 registration.Activated += (s, e) =>
73 {
74 foreach (var injector in injectors)
75 injector(e.Context, e.Instance);
76 };
77 }
78
79 private IEnumerable<Action<IComponentContext, object>> BuildLoggerInjectors(Type componentType)
80 {
81 // Look for settable properties of type "ILogger"
82 var loggerProperties = componentType
83 .GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)
84 .Select(p => new
85 {
86 PropertyInfo = p,
87 p.PropertyType,
88 IndexParameters = p.GetIndexParameters(),
89 Accessors = p.GetAccessors(false)
90 })
91 .Where(x => x.PropertyType == typeof(Test)) // must be a logger
92 .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer
93 .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set
94
95 // Return an array of actions that resolve a logger and assign the property
96 foreach (var entry in loggerProperties)
97 {
98 var propertyInfo = entry.PropertyInfo;
99
100 yield return (ctx, instance) =>
101 {
102 string component = componentType.ToString();
103 var logger = _loggerCache.GetOrAdd(component, key => ctx.Resolve<Test>(new TypedParameter(typeof(Type), componentType)));
104 propertyInfo.SetValue(instance, logger, null);
105 };
106 }
107 }
我們通過Module注冊了Test,但是我們通過斷點調試可以看到,我們通過屬性注入的還是沒有得到,還是為Null,這是不是autofac不支援屬性注入,我們在心裡隻罵坑爹啊。
但是我們仔細看一下會發現一個問題,我們的Test2 是通過New得到的,而不是通過autofac得到,我們并沒有将Test2注入到autofac中,是不是因為這個願意呢?
我們來嘗試一下,這可是我最後的機會了,因為除了這個原因我實在想不出還有什麼别的原因。
1 static void Main(string[] args)
2 {
3 ContainerBuilder builder = new ContainerBuilder();
4 builder.RegisterModule(new LoggingModule());
5 //builder.RegisterType<Test>();
6
7
8 //builder.Register(t => new Test()).As<Test>();
9 builder.RegisterType<Test2>();
10 var container = builder.Build();
11
12 Test2 test2 = container.Resolve<Test2>();
13 // Test2 ee = new Test2();
14 test2.Show();
15
16 }
我們修改了一下代碼,将Test2也注入到Autofac中,然後通過autofac的resolve擷取,奇迹出現了,我們看到了我們注冊的類型,在屬性注入得到了我們想要的執行個體。
這不得不說是一個令我激動的事情,因為這個屬性得到的實在是太難,讓我嘗試了很多種,經曆了很多次絕望。
是以我發現,如果你要想實作autofac的自動屬性注入,由三個步驟,第一個通過module注冊你要通過屬性擷取的類型,第二個,在屬性所在的class中,也要注冊到autofac中,最後一點,擷取屬性所在的class的執行個體必須通過autofac擷取,也就是絕對不要通過new來擷取,因為autofac的存在就是為了讓你在一定程度上減少new的使用。
我們使用過autofac的MVC實作,我們發現在controller中可以得到我們的屬性值,那是因為controller已經注冊到了autofac中,因為肯定有一句builder。registerController的存在。
所在對于想實作autofac自動屬性注入的朋友,一定要記得将類型通過module注入到autofac。并且屬性所在的類型必須通過autofac擷取,因為我們必須讓autofac知道類型的存在,才可能會自動注入。
這是一篇說明文,簡短的說明,希望沒有高深的知識點,但是如果你不了解,會花費很長時間才可能查找到錯誤的地方。應驗了那句話,知道了不難,不知道了難上加難。
我的座右銘:做架構師,要做牛逼的架構師。
有人的地方就有政治,有政治就有鬥争,但我讨厭政治,讨厭無休止的迎合。