反轉起來~透過IOC解析來執行依賴反轉 (第14天)

Agenda

前言

前一篇介紹Asp.net MVC可透過DependencyResolver.SetResolver替換成IOC容器注入控制器物件.

要建立客製化的解析器可以實現IDependencyResolver介面並使用DependencyResolver.SetResolver替換DefaultDependencyResolver預設解析器

DependencyResolver,ControllerControllerFactory的關係如下圖

IOC_Asp.netMVC.png

本篇介紹DependencyResolverAsp.net MVC中有哪些實際的應用.

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

DefaultControllerActivator

DefaultControllerFactory建構子建立DefaultControllerActivator,而DefaultControllerActivator有一個Create方法使用他來建立Controller物件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver)
{
if (controllerActivator != null)
{
_controllerActivator = controllerActivator;
}
else
{
_activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>(
() => null,
new DefaultControllerActivator(dependencyResolver),
"DefaultControllerFactory constructor");
}
}

如果我們沒透過DependencyResolver.SetResolver方法設定其他解析器,預設使用DefaultControllerActivator類別幫助我們建立Controller物件透過Create方法.

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
private class DefaultControllerActivator : IControllerActivator
{
private Func<IDependencyResolver> _resolverThunk;

public DefaultControllerActivator()
: this(null)
{
}

public DefaultControllerActivator(IDependencyResolver resolver)
{
if (resolver == null)
{
_resolverThunk = () => DependencyResolver.Current;
}
else
{
_resolverThunk = () => resolver;
}
}

public IController Create(RequestContext requestContext, Type controllerType)
{
try
{
return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
}
catch (Exception ex)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ErrorCreatingController,
controllerType),
ex);
}
}
}

因為DependencyResolver.Current建構子傳入參數IDependencyResolver resolver一般是NULL,所以會使用DependencyResolver.Current解析器.

Create方法預設利用DefaultDependencyResolver.GetService創建物件(使用Activator.CreateInstance())

BuildManagerViewEngine

BuildManagerViewEngine類別的詳細介紹會在之後的View如何產生有更細節的資訊.

這邊是提一下哪邊有用到IDependencyResolver解析器.

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
internal class DefaultViewPageActivator : IViewPageActivator
{
private Func<IDependencyResolver> _resolverThunk;

public DefaultViewPageActivator()
: this(null)
{
}

public DefaultViewPageActivator(IDependencyResolver resolver)
{
if (resolver == null)
{
_resolverThunk = () => DependencyResolver.Current;
}
else
{
_resolverThunk = () => resolver;
}
}

public object Create(ControllerContext controllerContext, Type type)
{
try
{
return _resolverThunk().GetService(type) ?? Activator.CreateInstance(type);
}
catch (MissingMethodException exception)
{
// Ensure thrown exception contains the type name. Might be down a few levels.
MissingMethodException replacementException =
TypeHelpers.EnsureDebuggableException(exception, type.FullName);
if (replacementException != null)
{
throw replacementException;
}

throw;
}
}
}

一樣可以看到有一個Create方法.透過跟DefaultControllerActivator一樣的操作來使用IDependencyResolver解析器

預設使用DependencyResolver.Current

FilterProviderCollection

可以透過IOC容器注入客製化ProvideFilter使用行為.

預設ProvideFilter有三個(詳細資訊會在之後分享)

  • GlobalFilterCollection(在Global擴充)
  • ControllerInstanceFilterProvider(Controller自行Override)
  • FilterAttributeFilterProvider(提供Attribute註冊最常用)

MVC Filters With Dependency Injection文章有介紹如何使用

1
2
3
4
5
6
7
8
9
10
11
12
13
internal IFilterProvider[] CombinedItems
{
get
{
IFilterProvider[] combinedItems = _combinedItems;
if (combinedItems == null)
{
combinedItems = MultiServiceResolver.GetCombined<IFilterProvider>(Items, _dependencyResolver);
_combinedItems = combinedItems;
}
return combinedItems;
}
}

Autofac對於MVC擴充解析器AutofacDependencyResolver

上面有說如果要改變MVC使用解析器可以透過DependencyResolver.SetResolver方法傳入一個IDependencyResolver物件,Autofac對於使的是AutofacDependencyResolver 原始碼.

替換完成後MVC就會使用AutofacDependencyResolver.GetService取得物件.

這裡就不多敘述Autofac內部完成細節.

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
public class AutofacDependencyResolver : IDependencyResolver
{
private static Func<AutofacDependencyResolver> _resolverAccessor = DefaultResolverAccessor;

private readonly Action<ContainerBuilder> _configurationAction;

private readonly ILifetimeScope _container;

private ILifetimeScopeProvider _lifetimeScopeProvider;

public AutofacDependencyResolver(ILifetimeScope container)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}

this._container = container;
}

public AutofacDependencyResolver(ILifetimeScope container, Action<ContainerBuilder> configurationAction)
: this(container)
{
if (configurationAction == null)
{
throw new ArgumentNullException(nameof(configurationAction));
}

this._configurationAction = configurationAction;
}

public AutofacDependencyResolver(ILifetimeScope container, ILifetimeScopeProvider lifetimeScopeProvider) :
this(container)
{
if (lifetimeScopeProvider == null)
{
throw new ArgumentNullException(nameof(lifetimeScopeProvider));
}

this._lifetimeScopeProvider = lifetimeScopeProvider;
}


public AutofacDependencyResolver(ILifetimeScope container, ILifetimeScopeProvider lifetimeScopeProvider, Action<ContainerBuilder> configurationAction)
: this(container, lifetimeScopeProvider)
{
if (configurationAction == null)
{
throw new ArgumentNullException(nameof(configurationAction));
}

this._configurationAction = configurationAction;
}

/// <summary>
/// Gets the Autofac implementation of the dependency resolver.
/// </summary>
public static AutofacDependencyResolver Current
{
get
{
return _resolverAccessor();
}
}

public ILifetimeScope ApplicationContainer
{
get { return this._container; }
}

public ILifetimeScope RequestLifetimeScope
{
get
{
if (this._lifetimeScopeProvider == null)
{
this._lifetimeScopeProvider = new RequestLifetimeScopeProvider(this._container);
}
return this._lifetimeScopeProvider.GetLifetimeScope(this._configurationAction);
}
}


public static void SetAutofacDependencyResolverAccessor(Func<AutofacDependencyResolver> accessor)
{
if (accessor == null)
{
_resolverAccessor = DefaultResolverAccessor;
}
else
{
_resolverAccessor = accessor;
}
}

public virtual object GetService(Type serviceType)
{
return this.RequestLifetimeScope.ResolveOptional(serviceType);
}

public virtual IEnumerable<object> GetServices(Type serviceType)
{
var enumerableServiceType = typeof(IEnumerable<>).MakeGenericType(serviceType);
var instance = this.RequestLifetimeScope.Resolve(enumerableServiceType);
return (IEnumerable<object>)instance;
}

private static AutofacDependencyResolver DefaultResolverAccessor()
{
var currentResolver = DependencyResolver.Current;
var autofacResolver = currentResolver as AutofacDependencyResolver;
if (autofacResolver != null)
{
return autofacResolver;
}

var targetType = currentResolver.GetType().GetField("__target");
if (targetType != null && targetType.FieldType == typeof(AutofacDependencyResolver))
{
return (AutofacDependencyResolver)targetType.GetValue(currentResolver);
}

throw new InvalidOperationException(string.Format(
CultureInfo.CurrentCulture,
AutofacDependencyResolverResources.AutofacDependencyResolverNotFound,
currentResolver.GetType().FullName, typeof(AutofacDependencyResolver).FullName));
}
}

小結:

本篇挑了幾個有使用到DependencyResolver的使用點

DefaultControllerActivator建立Controller會利用當前使用的解析器來幫我們達成(預設DefaultDependencyResolver)

如果我們不想要使用預設解析器也可自行替換自己的解析器(像Autofac第三方容器)來控制我們自己如何產生物件.

能看到Asp.net MVC在設計上運用許多小巧思可讓系統可以更好的擴充且不用到到原本的程式碼

這些設計技巧很值得我們還學習效法.

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/ithelp-day14/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^