探索ASP.NET MVCフレームワークのコントローラの検索とアクティブ化メカニズム
30404 ワード
引用する
前のブログではMVCフレームワークのルーティングメカニズムを紹介し、URLリクエストがASPからどのように行われるかを知っています.NET処理ラインはIHttpHandlerインスタンス(MvcHandler)に到達した.今日、MvcHandlerから次のステップの分析を行い、MVCフレームワークが指定されたコントローラを見つけ、コントローラインスタンスをアクティブ化(作成)する方法を見てみましょう.
すべてはMvcHandlerのProcessRequestメソッドから始まる(コントローラファクトリインスタンスを取得する)
Httpリクエストがサービス側に到着し、対応するIHttpHandlerが見つかった後、ProcessRequestメソッドを実行してリクエストを処理することを知っています.次に、MvcHandlerがリクエストをどのように処理しているかを見てみましょう.ソース番号:
1 IController controller; 2 IControllerFactory factory; 3 ProcessRequestInit(httpContext, out controller, out factory); 4 try 5 { 6 controller.Execute(RequestContext); 7 } 8 finally 9 { 10 factory.ReleaseController(controller); 11 }
このコードから,コントローラインスタンスを取得する際にコントローラファクトリによって作成されることを示し,次にProcessRequestInitメソッドを見てみる.ソースコードを参照:
1 private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
2 {
3 // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
4 // at Request.Form) to work correctly without triggering full validation.
5 // Tolerate null HttpContext for testing.
6 HttpContext currentContext = HttpContext.Current;
7 if (currentContext != null)
8 {
9 bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext);
10 if (isRequestValidationEnabled == true)
11 {
12 ValidationUtility.EnableDynamicValidation(currentContext);
13 }
14 }
15
16 AddVersionHeader(httpContext);
17 RemoveOptionalRoutingParameters();
18
19 // Get the controller type
20 string controllerName = RequestContext.RouteData.GetRequiredString("controller");
21
22 // Instantiate the controller and call Execute
23 factory = ControllerBuilder.GetControllerFactory();
24 controller = factory.CreateController(RequestContext, controllerName);
25 if (controller == null)
26 {
27 throw new InvalidOperationException(
28 String.Format(
29 CultureInfo.CurrentCulture,
30 MvcResources.ControllerBuilder_FactoryReturnedNull,
31 factory.GetType(),
32 controllerName));
33 }
34 }
この方法は20行から始まり,コントローラの名前を先に取得し,コントローラファクトリインスタンス(コード23行)を取得する.次に、コントローラファクトリインスタンスを使用してコントローラインスタンスを作成します.次に、ControllerBuilderがコントローラファクトリインスタンスを作成するプロセスを見てみましょう.
次にGetControllerFactoryメソッドの内部実装を見てみましょう.ソースコードは簡単です.1行のコードです.
1 public IControllerFactory GetControllerFactory()
2 {
3 return _serviceResolver.Current;
4 }
明らかに、これは読み取りです.ServiceResolverインスタンスのCurrentプロパティ値は、次のように注意してください.ServiceResolverインスタンスの作成とそのCurrentプロパティの割り当てについて、ソースコードを参照すると、次のコードが表示されます.
1 private static ControllerBuilder _instance = new ControllerBuilder();
2 private Func<IControllerFactory> _factoryThunk = () => null;
3 private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
4 private IResolver<IControllerFactory> _serviceResolver;
5
6 public ControllerBuilder() : this(null) { }
7
8 internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
9 {
10 _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
11 () => _factoryThunk(),
12 new DefaultControllerFactory { ControllerBuilder = this },
13 "ControllerBuilder.GetControllerFactory");
14 }
15
16 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Calling method multiple times might return different objects.")]
17 public IControllerFactory GetControllerFactory()
18 {
19 return _serviceResolver.Current;
20 }
ControllerBuilderクラスに静的フィールドが表示されます.Instance,静的フィールドはプログラム実行前にロードされているので,コードから分かるように,public ControllerBuilder():this(null)を実行する必要があり,その後,this(null)はパラメータ付き構造関数ControllerBuilder(Iresolver
以上の解析により,大まかな流れが分かったが,SingleServiceResolveの内部に深く入り込み,そのインスタンスのCurrentがどのように計算されているかを見た.デフォルトでは_factoryThunkが渡しを依頼した値はnullである.ソースコード:
1 internal class SingleServiceResolver<TService> : IResolver<TService>
2 where TService : class
3 {
4 private Lazy<TService> _currentValueFromResolver;
5 private Func<TService> _currentValueThunk;
6 private TService _defaultValue;
7 private Func<IDependencyResolver> _resolverThunk;
8 private string _callerMethodName;
9
10 public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName)
11 {
12 if (currentValueThunk == null)
13 {
14 throw new ArgumentNullException("currentValueThunk");
15 }
16 if (defaultValue == null)
17 {
18 throw new ArgumentNullException("defaultValue");
19 }
20 // DefaultDependencyResolver
21 _resolverThunk = () => DependencyResolver.Current;
22 _currentValueFromResolver = new Lazy<TService>(GetValueFromResolver);
23 _currentValueThunk = currentValueThunk;
24 _defaultValue = defaultValue;
25 _callerMethodName = callerMethodName;
26 }
27
28 internal SingleServiceResolver(Func<TService> staticAccessor, TService defaultValue, IDependencyResolver resolver, string callerMethodName)
29 : this(staticAccessor, defaultValue, callerMethodName)
30 {
31 if (resolver != null)
32 {
33 _resolverThunk = () => resolver;
34 }
35 }
36
37 public TService Current
38 {
39 get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; }
40 }
41
42 private TService GetValueFromResolver()
43 {
44 TService result = _resolverThunk().GetService<TService>();
45
46 if (result != null && _currentValueThunk() != null)
47 {
48 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName));
49 }
50
51 return result;
52 }
53 }
Currentプロパティの評価には、コードの赤い部分を見る3つのプロセスが含まれています.次に、この3つのステップを見てみましょう.
最初のステップはcurrentValueFromResolver.Valueの値.私たちは知っています.CurrentValueFromResolverはGetValueFromResolverメソッドをバインドしています.この方法の内部にはresolverThunk依頼が用いられ、この依頼はDependencyResolverに戻る.Current.以上のコードでコメントしました.DependencyResolver.Currentが実際に使用しているのは、DefaultDependencyResolverクラスです.DefaultDependencyResolverクラスのGetServiceメソッドを使用します.この方法の内部に深く入り込んで、その論理が次のようになっていることを発見しました.
1 public object GetService(Type serviceType)
2 {
3 // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null
4 // to improve performance and the debugging experience with first-chance exceptions enabled.
5 if (serviceType.IsInterface || serviceType.IsAbstract)
6 {
7 return null;
8 }
9
10 try
11 {
12 return Activator.CreateInstance(serviceType);
13 }
14 catch
15 {
16 return null;
17 }
18 }
伝達されるTypeタイプがインタフェースまたは抽象クラスであればnullを返す.私たちが伝えたサービスタイプはインタフェースであることは明らかです.最初のステップが戻ったときnull.次に、2番目のステップを分析し続けます.
2つ目のステップでは、呼び出されたのは_CurrentValueThunk依頼.この依頼のデフォルト値はnullを返します.もちろん、コントローラファクトリをカスタマイズするときは、この依頼値を再設定します.これはMVCフレームワークの拡張点です.カスタムコントローラを設定する工場の内部で発生したことを見てみましょう.ソースコードを見てみましょう.
1 public void SetControllerFactory(IControllerFactory controllerFactory)
2 {
3 if (controllerFactory == null)
4 {
5 throw new ArgumentNullException("controllerFactory");
6 }
7
8 _factoryThunk = () => controllerFactory;
9 }
これを見て、SetControllerFactoryメソッドを通じて、私たちは_factoryThunkは、カスタムコントローラファクトリのインスタンスを返すように依頼しました.このようにCurrentプロパティ値を取得する場合、2番目のステップは、この依頼を呼び出してコントローラファクトリインスタンスに戻ることです.デフォルト値はnullです.カスタム値を設定すると、カスタムコントローラファクトリ値が返されます.
3つ目のステップはdefaultValueの値.MVCフレームワークのデフォルト値はDefaultControllerFactoryであることは明らかです.したがってMVCフレームワークのデフォルトのコントローラファクトリはDefaultControllerFactoryです.ここまで,この開始プロセスリクエストで取得したコントローラファクトリの例がDefaultControllerFactoryの例であることを解析した.私たちは下を見続けた.
コントローラインスタンスのアクティブ化(作成)
上記の分析では、コントローラファクトリがDefaultControllerFactoryであることがわかりました.次に、コントローラインスタンスがどのように作成されたのかを内部に深く見てみましょう.最初の部分コードの論理(すなわちProcessRequestメソッド)により,コントローラを作成する方法がCreateControllerメソッドであることが分かったので,このメソッドから着手する.
1 public virtual IController CreateController(RequestContext requestContext, string controllerName)
2 {
3 if (requestContext == null)
4 {
5 throw new ArgumentNullException("requestContext");
6 }
7 if (String.IsNullOrEmpty(controllerName))
8 {
9 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
10 }
11 Type controllerType = GetControllerType(requestContext, controllerName);
12 IController controller = GetControllerInstance(requestContext, controllerType);
13 return controller;
14 }
プロセス全体が主に2つの部分に分かれているのを見ました.手順1:コントローラタイプを取得します.ステップ2:コントローラタイプに基づいてコントローラインスタンスを取得します.次は2つのステップに分けてこの過程を紹介します.
コントローラのタイプを最初に取得するには、次の手順に従います.
1 protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName)
2 {
3 if (String.IsNullOrEmpty(controllerName))
4 {
5 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
6 }
7
8 // first search in the current route's namespace collection
9 object routeNamespacesObj;
10 Type match;
11 if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj))
12 {
13 IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
14 if (routeNamespaces != null && routeNamespaces.Any())
15 {
16 HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
17 match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceHash);
18
19 // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
20 if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"]))
21 {
22 // got a match or the route requested we stop looking
23 return match;
24 }
25 }
26 }
27
28 // then search in the application's default namespace collection
29 if (ControllerBuilder.DefaultNamespaces.Count > 0)
30 {
31 HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
32 match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceDefaults);
33 if (match != null)
34 {
35 return match;
36 }
37 }
38
39 // if all else fails, search every namespace
40 return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null /* namespaces */);
41 }
このコードの構造がはっきりしていることがわかります.最初のステップでは、現在のルーティング設定のネーミングスペースで条件を満たすコントローラを探します.見つからない場合は、プロジェクトで設定したネーミングスペースから探します.まだ見つからない場合は、名前空間の遍歴を探します.
ステップ2コントローラタイプに基づいてインスタンスを作成する
次に、2つ目のステップを見て、コントローラインスタンスを作成し、論理的にも別の方法にカプセル化します(GetControllerInstance).次に、その内部実装を見てみましょう.
1 /// <summary>
2 ///
3 /// </summary>
4 /// <param name="requestContext"></param>
5 /// <param name="controllerType"></param>
6 /// <returns></returns>
7 protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
8 {
9 if (controllerType == null)
10 {
11 throw new HttpException(404,
12 String.Format(
13 CultureInfo.CurrentCulture,
14 MvcResources.DefaultControllerFactory_NoControllerFound,
15 requestContext.HttpContext.Request.Path));
16 }
17 if (!typeof(IController).IsAssignableFrom(controllerType))
18 {
19 throw new ArgumentException(
20 String.Format(
21 CultureInfo.CurrentCulture,
22 MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
23 controllerType),
24 "controllerType");
25 }
26 return ControllerActivator.Create(requestContext, controllerType);
27 }
コントローラの作成は本質的にコントローラアクターに渡されて作成されるのを見ました.このコントローラのアクティブ化器がどのように動作しているかをよく見る必要があります.まず、ControllerActivvatorの値を見つけなければなりません.次のコードが表示されます.
1 private IControllerActivator ControllerActivator
2 {
3 get
4 {
5 if (_controllerActivator != null)
6 {
7 return _controllerActivator;
8 }
9 _controllerActivator = _activatorResolver.Current;
10 return _controllerActivator;
11 }
12 }
次に下を見てみましょう
1 public DefaultControllerFactory()
2 : this(null, null, null)
3 {
4 }
5
6 public DefaultControllerFactory(IControllerActivator controllerActivator)
7 : this(controllerActivator, null, null)
8 {
9 }
10
11 internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver)
12 {
13 // , DefaultControllerActivator
14 if (controllerActivator != null)
15 {
16 _controllerActivator = controllerActivator;
17 }
18 else
19 {
20 _activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>(
21 () => null,
22 new DefaultControllerActivator(dependencyResolver),
23 "DefaultControllerFactory constructor");
24 }
25 }
ここでは、SingleServiceResolverの汎用クラスに詳しいコード(赤い部分)も見ました.MVCフレームワークのデフォルトのコントローラアクティベータがDefaultControllerActivatvatorであることがわかります.次に、D e f a ultControllerActivvatorクラスの内部ロジックを見てみましょう.
1 /// <summary>
2 /// MVC
3 /// </summary>
4 private class DefaultControllerActivator : IControllerActivator
5 {
6 private Func<IDependencyResolver> _resolverThunk;
7
8 public DefaultControllerActivator()
9 : this(null)
10 {
11 }
12
13 public DefaultControllerActivator(IDependencyResolver resolver)
14 {
15 if (resolver == null)
16 {
17 //DependencyResolver.Current DefaultDependencyResolver
18 _resolverThunk = () => DependencyResolver.Current;
19 }
20 else
21 {
22 _resolverThunk = () => resolver;
23 }
24 }
25
26 public IController Create(RequestContext requestContext, Type controllerType)
27 {
28 try
29 {
30 return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
31 }
32 catch (Exception ex)
33 {
34 throw new InvalidOperationException(
35 String.Format(
36 CultureInfo.CurrentCulture,
37 MvcResources.DefaultControllerFactory_ErrorCreatingController,
38 controllerType),
39 ex);
40 }
41 }
42 }
アクチュエータのCreateメソッドを見てみましょう.コントローラの例はこのメソッドによって作成されます.コントローラの例は、コントローラのタイプに基づいて作成されています.ここまで、コントローラインスタンスをアクティブにする方法について説明しました.次の文章では、リクエストの手順に沿って探索を続けます.