ASPに深く入り込む.NET MVCの3:Controllerのアクティブ化
23080 ワード
前述したRouting Moduleは、MvcHandlerに制御権を渡した.MvcHandlerはIHttpAsyncHandlerインタフェースを実現したので、すぐにBeginProcessRequestメソッドが呼び出される.このメソッドでは、まずTrust Levelのようなセキュリティ検出が行われ、しばらくはともかく、ProcessRequestInitメソッドが呼び出される(削除あり):
まずcontrollerの名前を取得し、次にcontrollerをインスタンス化します.ここでは抽象的なファクトリのモードを採用しています.まずControllerBuilderを利用してIController Factoryのインスタンスを取得し、ControllerBuilderはDependency Injectionを採用してIController Factoryをインスタンス化します.MVCでのDIの実装については後述しますが、デフォルトではControllerBuilderはインスタンスを返します.次にCreateControllerメソッドを呼び出します.
メソッドは2つのステップに分けられ、タイプを取得してからインスタンスを取得します.
DefaultControllerFactoryは、ルーティング情報に基づいて対応するControllerのタイプを検索する際に、まずDataTokenにNamespaceがあるかどうかを判断し、GetControllerType WithinNamespacesメソッドを呼び出してController対応のクラスを検索します.まずこの方法を見てみましょう.
ASP.NETでは反射が多く使われているため、これらの反射されたクラスをキャッシュして性能を向上させる必要がある.まずEnsureInitializedという興味深い方法を見てみると、この方法のパラメータBuildManagerは積層包装されているが、実はSystemである.Web.Compilation.BuildManagerのインスタンスです.
まずTypeCacheUtilはControllerのタイプをすべて取得します.TypeCacheUtilは前文にすでに登場しており、すべてのAreaRegistrationのサブタイプを取得するために使用されています.ここでは、この方法をよく見てみましょう.
この方法では、コントロールタイプの名前がキャッシュから読み出されます.キャッシュはテキストファイルに存在します.名前はcacheNameです.ここではMvc-ControllerType Cacheです.xml.このファイルの内容は次のとおりです.
MVCApp.Areas.Admin.Controllers.HomeController MVCApp.Controllers.HomeController
パラメータpredicateを見てみましょう.このパラメータはどのクラスがControllerであるかをフィルタするために使用されます.この方法の実装も興味深いです.
これは、クラスがControllerになるには、Controllerで終わる必要があり、publicである必要があり、IControllerインタフェースを実装する必要があることを示しています.すべてのControllerクラスが見つかった後、EnsureInitializedメソッドに戻り、検索を容易にするためにこれらのクラスをインデックス化し、まずControllerの名前でグループ化し、Namespaceでグループ化します.以下に示すように、グループ化後、必要なコントローラを簡単に見つけることができます.
GetControllerType WithinNamespacesメソッドに戻ると、キャッシュにインデックスされたControllerの情報があります.次に、キャッシュでControllerの名前に基づいてControllerを検索します.GetControllerTypeはこのプロセスを実現しました.プロセスは複雑ではありませんが、詳細は少なくありません.具体的なコードは貼られません.プロセスは、まずcontrollerの名前に対応するLookupを検出し、namespaceが合っているかどうかを確認します.GetControllerType WithinNamespacesのパラメータnamespaceが空またはコンテンツがない場合は、controllerの名前のみが判断されます.検索結果は3種類あり、1つのControllerのtypeだけが満たされている場合はこのタイプを返し、見つからない場合はnullを返し、複数見つかった場合は異常を投げ出す.
GetControllerTypeメソッドに戻ります.GetControllerType WithinNamespacesがnullを返し、UseNamespaceFallbackがtrueに設定されている場合、次の検索が行われます.そうでない場合nullを返します.次の検索は、プロジェクトのDefaultNamespaceで検索されます.NamespaceのないRouteDataの場合、デフォルトではここで検索されます.最後にすべてのnamespaceで検索します.これで、コントローラ名に基づいてコントローラクラスのtypeを検索できます.
次に、このタイプをインスタンス化します.インスタンス化の方法は簡単で呼び出すだけです
それでいいです.でもASP.NET MVCはここでは比較的複雑なDIメカニズムを使用しているが、デフォルトではDefaultDependencyResolverのGetServiceメソッドを呼び出しており、このメソッドは最終的にActivatorのみを呼び出す.CreateInstanceメソッド.MVCにおけるDIメカニズムについては,ここではあまり分析しないが,別文で述べる.これで、Controllerクラスが構築されました.Actionのアクティブ化について説明します.
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
// Get the controller type
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
// Instantiate the controller and call Execute
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}
まずcontrollerの名前を取得し、次にcontrollerをインスタンス化します.ここでは抽象的なファクトリのモードを採用しています.まずControllerBuilderを利用してIController Factoryのインスタンスを取得し、ControllerBuilderはDependency Injectionを採用してIController Factoryをインスタンス化します.MVCでのDIの実装については後述しますが、デフォルトではControllerBuilderはインスタンスを返します.次にCreateControllerメソッドを呼び出します.
public virtual IController CreateController(RequestContext requestContext, string controllerName) {
Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = DefaultControllerFactoryGetControllerInstance(requestContext, controllerType);
return controller;
}
メソッドは2つのステップに分けられ、タイプを取得してからインスタンスを取得します.
protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName) {
// first search in the current route's namespace collection
object routeNamespacesObj;
Type match;
if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj)) {
IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
if (routeNamespaces != null && routeNamespaces.Any()) {
HashSet<string> nsHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, nsHash);
// the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"])) {
// got a match or the route requested we stop looking
return match;
}
}
}
// then search in the application's default namespace collection
if (ControllerBuilder.DefaultNamespaces.Count > 0) {
HashSet<string> nsDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, nsDefaults);
if (match != null) {
return match;
}
}
// if all else fails, search every namespace
return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null /* namespaces */);
}
DefaultControllerFactoryは、ルーティング情報に基づいて対応するControllerのタイプを検索する際に、まずDataTokenにNamespaceがあるかどうかを判断し、GetControllerType WithinNamespacesメソッドを呼び出してController対応のクラスを検索します.まずこの方法を見てみましょう.
private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces) {
// Once the master list of controllers has been created we can quickly index into it
ControllerTypeCache.EnsureInitialized(BuildManager);
ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
switch (matchingTypes.Count) {
case 0:
// no matching types
return null;
case 1:
// single matching type
return matchingTypes.First();
default:
// multiple matching types
throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);
}
}
ASP.NETでは反射が多く使われているため、これらの反射されたクラスをキャッシュして性能を向上させる必要がある.まずEnsureInitializedという興味深い方法を見てみると、この方法のパラメータBuildManagerは積層包装されているが、実はSystemである.Web.Compilation.BuildManagerのインスタンスです.
public void EnsureInitialized(IBuildManager buildManager) {
if (_cache == null) {
lock (_lockObj) {
if (_cache == null) {
List<Type> controllerTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(_typeCacheName, IsControllerType, buildManager);
var groupedByName = controllerTypes.GroupBy(
t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
StringComparer.OrdinalIgnoreCase);
_cache = groupedByName.ToDictionary(
g => g.Key,
g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
StringComparer.OrdinalIgnoreCase);
}
}
}
}
まずTypeCacheUtilはControllerのタイプをすべて取得します.TypeCacheUtilは前文にすでに登場しており、すべてのAreaRegistrationのサブタイプを取得するために使用されています.ここでは、この方法をよく見てみましょう.
public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager) {
TypeCacheSerializer serializer = new TypeCacheSerializer();
// first, try reading from the cache on disk
List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
if (matchingTypes != null) {
return matchingTypes;
}
// if reading from the cache failed, enumerate over every assembly looking for a matching type
matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();
// finally, save the cache back to disk
SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);
return matchingTypes;
}
この方法では、コントロールタイプの名前がキャッシュから読み出されます.キャッシュはテキストファイルに存在します.名前はcacheNameです.ここではMvc-ControllerType Cacheです.xml.このファイルの内容は次のとおりです.
パラメータpredicateを見てみましょう.このパラメータはどのクラスがControllerであるかをフィルタするために使用されます.この方法の実装も興味深いです.
internal static bool IsControllerType(Type t) {
return
t != null &&
t.IsPublic &&
t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t);
}
これは、クラスがControllerになるには、Controllerで終わる必要があり、publicである必要があり、IControllerインタフェースを実装する必要があることを示しています.すべてのControllerクラスが見つかった後、EnsureInitializedメソッドに戻り、検索を容易にするためにこれらのクラスをインデックス化し、まずControllerの名前でグループ化し、Namespaceでグループ化します.以下に示すように、グループ化後、必要なコントローラを簡単に見つけることができます.
GetControllerType WithinNamespacesメソッドに戻ると、キャッシュにインデックスされたControllerの情報があります.次に、キャッシュでControllerの名前に基づいてControllerを検索します.GetControllerTypeはこのプロセスを実現しました.プロセスは複雑ではありませんが、詳細は少なくありません.具体的なコードは貼られません.プロセスは、まずcontrollerの名前に対応するLookupを検出し、namespaceが合っているかどうかを確認します.GetControllerType WithinNamespacesのパラメータnamespaceが空またはコンテンツがない場合は、controllerの名前のみが判断されます.検索結果は3種類あり、1つのControllerのtypeだけが満たされている場合はこのタイプを返し、見つからない場合はnullを返し、複数見つかった場合は異常を投げ出す.
GetControllerTypeメソッドに戻ります.GetControllerType WithinNamespacesがnullを返し、UseNamespaceFallbackがtrueに設定されている場合、次の検索が行われます.そうでない場合nullを返します.次の検索は、プロジェクトのDefaultNamespaceで検索されます.NamespaceのないRouteDataの場合、デフォルトではここで検索されます.最後にすべてのnamespaceで検索します.これで、コントローラ名に基づいてコントローラクラスのtypeを検索できます.
次に、このタイプをインスタンス化します.インスタンス化の方法は簡単で呼び出すだけです
Activator.CreateInstance(controllerType);
それでいいです.でもASP.NET MVCはここでは比較的複雑なDIメカニズムを使用しているが、デフォルトではDefaultDependencyResolverのGetServiceメソッドを呼び出しており、このメソッドは最終的にActivatorのみを呼び出す.CreateInstanceメソッド.MVCにおけるDIメカニズムについては,ここではあまり分析しないが,別文で述べる.これで、Controllerクラスが構築されました.Actionのアクティブ化について説明します.