ASPを分析する.NET Core(Part 3)- UseMvc
18091 ワード
原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc発表:2017年4月環境:ASP.NET Core 1.1
このシリーズの前の2つの文章はASPを紹介した.NET CoreにおけるIServiceCollectionの2つの主要な拡張方法(AddMvcCoreとAddMvc).プログラムでMVCミドルウェアを使用する準備ができたら、必要なMVCサービスを追加します.
次に、ASP.NET CoreプログラムでMVCを起動するには、StartupクラスのConfigureメソッドでUseMvc IApplicationBuilder拡張メソッドを実行する必要があります.この方法は、MVCフレームワークが要求を処理し、応答(通常はview resultまたはjson)を返すことができるように、アプリケーションパイプにMVCミドルウェアを登録する.本稿では、アプリケーションの起動時にUserMvcメソッドが何をしたかを分析します.
以前の記事と同様に、rel/1.1.2 MVCバージョンライブラリを分析対象として使用しました.コードは元のprojectに基づいている.json、VS 2017では複数のASPをデバッグする簡単な方法がないようですので.NET Coreソース.
UseMvcは、Action委任パラメータを持つIApplicationBuilderの拡張方法です.IrouteBuilderは、MVCのルーティングを構成するために使用されます.UserMvcには、パラメータを必要とせず、空の委任呼び出しマスター関数を簡単に渡すリロード方法もあります.次のようになります.
メインUseMvcメソッド:
この方法を分析してみましょう.まず、IServiceProviderにMvcMarkerServiceサービスが登録されているかどうかを確認します.このためにApplicationServicesプロパティを使用してIServiceProviderを暴露し、GetServiceを呼び出し、登録されているMvcMarkerServiceを検索します.MvcMarkerServiceはAddMvcCoreの実行時に登録されているため、見つからない場合は、ConfigureServicesの実行前にAddMvcまたはAddMvcCoreが呼び出されていないことを示します.このようなMarker Servicesは多くの場所で使用されており、コードの実行前に正しい依存関係が存在するかどうかを確認するのに役立ちます.
次に、UserMvcは、ServiceProviderからMiddlewareFilterBuilderを要求し、IApplicationBuilderを使用する.New()メソッドは、アプリケーションビルダーを設定します.このメソッドを呼び出すと、ApplicationBuilderは独自の新しいインスタンスのコピーを作成して返します.
さらに、UserMvcは新しいRouteBuilderを初期化し、デフォルトのハンドラ(default handler)を登録されたMvcRouteHandlerに設定します.このときDIは魔力があるように,依存対象の山がインスタンス化し始める.ここでMvcRouteHandlerを要求するのは、そのコンストラクション関数に依存関係があるため、関連する他のクラスも初期化されます.これらのクラスのコンストラクション関数ごとに、追加の依存関係が要求され、作成されます.これはDIシステムの動作の完璧な例である.コンフィギュレーションサービス内のすべてのインタフェースおよびインプリメンテーションはcontainerに登録されていますが、実際のオブジェクトは注入時にのみ作成されます.RouteBuilderを作成すると、オブジェクトはすべての依存関係が構築されるまで雪だるまのように作成されます.一部のオブジェクトは、サービスプロバイダが要求するかどうかにかかわらず、アプリケーション全体のライフサイクルで作成される単一のモードとして登録されています.他のオブジェクトは、リクエストのたびにコンストラクション関数によって作成されるか、またはそのたびに作成されるオブジェクトが固有である可能性があります.依存注入がどのように働くか、特にASP.NET Core ServiceProviderの詳細はこの記事の範囲を超えています.
RouteBuilderが作成されると、アクション依頼が呼び出され、RouteBuilderが一意のパラメータとして使用されます.MvcSandbox列では、lambdaを介してエージェントメソッドに渡されるUseMvcメソッドと呼ばれています.この機能は、次のように「default」というルーティングをマッピングします.
MapRouteはIrouteBuilderの拡張メソッドで、主なMapRouteメソッドは次のようになります.
これにより、ServiceProviderによって解決されたIinlineConstraintResolverインスタンスがD e f a ultInlineConstraintResolverに要求されます.このクラスは、IOptionsパラメータを使用してコンストラクション関数を初期化します.
Microsoft.Extensions.Optionsプログラムセットは、パラメータとしてRouteOptionsを呼び出すMvcCoreRouteOptionsSetup.Configureメソッド.これにより、KnownRouteValueConstraintタイプのコンストレイントがコンストレイントマッピング辞書にマッピングされます.RouteOptionsが初めて構築されると、辞書は複数のデフォルト制約を初期化します.ルーティングコードについては後述する.
指定した名前とテンプレートから新しいRouteオブジェクトを構築します.我々の例では、新しいオブジェクトがRouteBuilderに追加されました.Routesリストにあります.これで、MvcSandboxカラムプログラムが実行されると、私たちのrouter builderにはルーティング記録があります.
MvcApplicationBuilderExtensionsクラスには、UseMvcWithDefaultRouteという拡張メソッドも含まれていることに注意してください.このメソッドはUseMvcを呼び出し、ハードコーディングでデフォルトのルーティングを設定します.
このルーティングは、MvcSandboxプログラムで定義されているのと同じ名前とテンプレートで定義されます.したがって、MvcSandbox Startupクラスの軽微なコード保護プログラム(slight code saver)として使用される可能性があります.最も基本的なMvcプログラムの場合、この拡張方法を使用すると、ルーティングの要件を満たすのに十分である可能性がある.しかし、多くの実際の使用シーンでは、より完全なルートを伝えると信じています.基本的なルーティングテンプレートから始めたい場合は、パラメータを使用せずにUseMvc()を直接呼び出すことができます.
次に、静的AttributeRoutingを呼び出す.CreateAttributeMegaRouteメソッドは、生成されたルーティングをRouteBuilderのRoutes List(インデックス位置0)に追加します.CreateAttributeMegaRouteは、「Creates an attribute route using the provided services and provided target router」で説明しています.
この方法はIrouteインタフェースを実装した新しいAttributeRouteを作成した.そのコンストラクション関数は、次のように見えます.
このコンストラクション関数には、I a c t i o n D e scriptorCollectionProvider、IServiceProvider、およびActionDescriptor配列パラメータを受け入れ、IrouterのFuncを返す必要があります.デフォルトでは、AddMvcでサービスを登録すると、ServiceProviderで登録された単一のオブジェクトであるActionDescriptor CollectionProviderインスタンスが取得されます.ActionDescriptorsは、アプリケーションで使用可能なMVCアクションを作成および検出することを示します.これらのオブジェクトは別途分析します.
新しいAttributeRouteコード(CreateAttributeMegaRouteメソッドの内部)を作成するには、lambdaを使用してFunc,Irouter>のコードを定義します.この場合、委任関数は、ServiceProviderからMvcAttributeRouteHandlerを要求します.Transientとして登録されているため、ServiceProviderを要求するたびに新しいMvcAttributeRouteHandlerインスタンスが返されます.次に、委任コードは、入力されたActionDescriptions配列を使用してM v c AttributeRouteHandlerにActions属性(ActionDescriptionsの配列)を設定し、最後に新しいプロセッサに戻る.
UserMvcに戻り、アプリケーションビルダーのUseRouter拡張メソッドが呼び出しを完了します.UseRouterに渡されるオブジェクトは、RouteBuilderのBuildメソッドを呼び出すことによって作成されます.このメソッドは、ルーティングセットにルーティングを追加します.RouteCollectionでは、名前が付けられているもの、名前が付けられていないものを追跡します.我々のMvcSandboxの例では、「default」という名前のルーティングと、名前のないAttributeRouteが得られます.
UseRouterには2つの署名があります.Irouterを渡すので、次の方法を呼び出します.
この方法は、ServiceProviderのRoutingMarkerServiceのチェックを実現します.これが予定通り登録されていると仮定し、RouterMiddlewareをミドルウェアパイプライン(middeware pipeline)に追加します.このミドルウェアこそ、コントローラの要求とMVCの動作(action)を一致させて処理しようとするIrouterを使用します.このプロセスの詳細は、将来のブログに表示されます.
これで、アプリケーションパイプが構成され、アプリケーションはリクエストを受信する準備ができています.本文も終わります.
本論文ではUseMvcを解析し,後で使用するMiddlewareFilterBuilderが設定されていることを示した.その後、RouteBuilderによってIrouterも取得され、この段階ではほとんどの作業がルーティングを登録します.設定が完了すると、ルーティングミドルウェアはパイプに登録されます.このコード(ミドルウェア)は、受信されたリクエストをどのようにチェックし、パスを適切なコントローラにマッピングし、それらの操作を処理できるかを知っています.
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return app.UseRouter(routes.Build());
}
このシリーズの前の2つの文章はASPを紹介した.NET CoreにおけるIServiceCollectionの2つの主要な拡張方法(AddMvcCoreとAddMvc).プログラムでMVCミドルウェアを使用する準備ができたら、必要なMVCサービスを追加します.
次に、ASP.NET CoreプログラムでMVCを起動するには、StartupクラスのConfigureメソッドでUseMvc IApplicationBuilder拡張メソッドを実行する必要があります.この方法は、MVCフレームワークが要求を処理し、応答(通常はview resultまたはjson)を返すことができるように、アプリケーションパイプにMVCミドルウェアを登録する.本稿では、アプリケーションの起動時にUserMvcメソッドが何をしたかを分析します.
以前の記事と同様に、rel/1.1.2 MVCバージョンライブラリを分析対象として使用しました.コードは元のprojectに基づいている.json、VS 2017では複数のASPをデバッグする簡単な方法がないようですので.NET Coreソース.
UseMvcは、Action委任パラメータを持つIApplicationBuilderの拡張方法です.IrouteBuilderは、MVCのルーティングを構成するために使用されます.UserMvcには、パラメータを必要とせず、空の委任呼び出しマスター関数を簡単に渡すリロード方法もあります.次のようになります.
public static IApplicationBuilder UseMvc(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMvc(routes =>
{
});
}
メインUseMvcメソッド:
public static IApplicationBuilder UseMvc(
this IApplicationBuilder app,
Action configureRoutes)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (configureRoutes == null)
{
throw new ArgumentNullException(nameof(configureRoutes));
}
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
"AddMvc",
"ConfigureServices(...)"));
}
var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService();
middlewarePipelineBuilder.ApplicationBuilder = app.New();
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService(),
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return app.UseRouter(routes.Build());
}
この方法を分析してみましょう.まず、IServiceProviderにMvcMarkerServiceサービスが登録されているかどうかを確認します.このためにApplicationServicesプロパティを使用してIServiceProviderを暴露し、GetServiceを呼び出し、登録されているMvcMarkerServiceを検索します.MvcMarkerServiceはAddMvcCoreの実行時に登録されているため、見つからない場合は、ConfigureServicesの実行前にAddMvcまたはAddMvcCoreが呼び出されていないことを示します.このようなMarker Servicesは多くの場所で使用されており、コードの実行前に正しい依存関係が存在するかどうかを確認するのに役立ちます.
次に、UserMvcは、ServiceProviderからMiddlewareFilterBuilderを要求し、IApplicationBuilderを使用する.New()メソッドは、アプリケーションビルダーを設定します.このメソッドを呼び出すと、ApplicationBuilderは独自の新しいインスタンスのコピーを作成して返します.
さらに、UserMvcは新しいRouteBuilderを初期化し、デフォルトのハンドラ(default handler)を登録されたMvcRouteHandlerに設定します.このときDIは魔力があるように,依存対象の山がインスタンス化し始める.ここでMvcRouteHandlerを要求するのは、そのコンストラクション関数に依存関係があるため、関連する他のクラスも初期化されます.これらのクラスのコンストラクション関数ごとに、追加の依存関係が要求され、作成されます.これはDIシステムの動作の完璧な例である.コンフィギュレーションサービス内のすべてのインタフェースおよびインプリメンテーションはcontainerに登録されていますが、実際のオブジェクトは注入時にのみ作成されます.RouteBuilderを作成すると、オブジェクトはすべての依存関係が構築されるまで雪だるまのように作成されます.一部のオブジェクトは、サービスプロバイダが要求するかどうかにかかわらず、アプリケーション全体のライフサイクルで作成される単一のモードとして登録されています.他のオブジェクトは、リクエストのたびにコンストラクション関数によって作成されるか、またはそのたびに作成されるオブジェクトが固有である可能性があります.依存注入がどのように働くか、特にASP.NET Core ServiceProviderの詳細はこの記事の範囲を超えています.
RouteBuilderが作成されると、アクション依頼が呼び出され、RouteBuilderが一意のパラメータとして使用されます.MvcSandbox列では、lambdaを介してエージェントメソッドに渡されるUseMvcメソッドと呼ばれています.この機能は、次のように「default」というルーティングをマッピングします.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
MapRouteはIrouteBuilderの拡張メソッドで、主なMapRouteメソッドは次のようになります.
public static IRouteBuilder MapRoute(
this IRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints,
object dataTokens)
{
if (routeBuilder.DefaultHandler == null)
{
throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
}
var inlineConstraintResolver = routeBuilder
.ServiceProvider
.GetRequiredService();
routeBuilder.Routes.Add(new Route(
routeBuilder.DefaultHandler,
name,
template,
new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints),
new RouteValueDictionary(dataTokens),
inlineConstraintResolver));
return routeBuilder;
}
これにより、ServiceProviderによって解決されたIinlineConstraintResolverインスタンスがD e f a ultInlineConstraintResolverに要求されます.このクラスは、IOptionsパラメータを使用してコンストラクション関数を初期化します.
Microsoft.Extensions.Optionsプログラムセットは、パラメータとしてRouteOptionsを呼び出すMvcCoreRouteOptionsSetup.Configureメソッド.これにより、KnownRouteValueConstraintタイプのコンストレイントがコンストレイントマッピング辞書にマッピングされます.RouteOptionsが初めて構築されると、辞書は複数のデフォルト制約を初期化します.ルーティングコードについては後述する.
指定した名前とテンプレートから新しいRouteオブジェクトを構築します.我々の例では、新しいオブジェクトがRouteBuilderに追加されました.Routesリストにあります.これで、MvcSandboxカラムプログラムが実行されると、私たちのrouter builderにはルーティング記録があります.
MvcApplicationBuilderExtensionsクラスには、UseMvcWithDefaultRouteという拡張メソッドも含まれていることに注意してください.このメソッドはUseMvcを呼び出し、ハードコーディングでデフォルトのルーティングを設定します.
public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
このルーティングは、MvcSandboxプログラムで定義されているのと同じ名前とテンプレートで定義されます.したがって、MvcSandbox Startupクラスの軽微なコード保護プログラム(slight code saver)として使用される可能性があります.最も基本的なMvcプログラムの場合、この拡張方法を使用すると、ルーティングの要件を満たすのに十分である可能性がある.しかし、多くの実際の使用シーンでは、より完全なルートを伝えると信じています.基本的なルーティングテンプレートから始めたい場合は、パラメータを使用せずにUseMvc()を直接呼び出すことができます.
次に、静的AttributeRoutingを呼び出す.CreateAttributeMegaRouteメソッドは、生成されたルーティングをRouteBuilderのRoutes List(インデックス位置0)に追加します.CreateAttributeMegaRouteは、「Creates an attribute route using the provided services and provided target router」で説明しています.
public static IRouter CreateAttributeMegaRoute(IServiceProvider services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
return new AttributeRoute(
services.GetRequiredService(),
services,
actions =>
{
var handler = services.GetRequiredService();
handler.Actions = actions;
return handler;
});
}
この方法はIrouteインタフェースを実装した新しいAttributeRouteを作成した.そのコンストラクション関数は、次のように見えます.
public AttributeRoute(
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
IServiceProvider services,
Func handlerFactory)
{
if (actionDescriptorCollectionProvider == null)
{
throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider));
}
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (handlerFactory == null)
{
_handlerFactory = handlerFactory;
}
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_services = services;
_handlerFactory = handlerFactory;
}
このコンストラクション関数には、I a c t i o n D e scriptorCollectionProvider、IServiceProvider、およびActionDescriptor配列パラメータを受け入れ、IrouterのFuncを返す必要があります.デフォルトでは、AddMvcでサービスを登録すると、ServiceProviderで登録された単一のオブジェクトであるActionDescriptor CollectionProviderインスタンスが取得されます.ActionDescriptorsは、アプリケーションで使用可能なMVCアクションを作成および検出することを示します.これらのオブジェクトは別途分析します.
新しいAttributeRouteコード(CreateAttributeMegaRouteメソッドの内部)を作成するには、lambdaを使用してFunc,Irouter>のコードを定義します.この場合、委任関数は、ServiceProviderからMvcAttributeRouteHandlerを要求します.Transientとして登録されているため、ServiceProviderを要求するたびに新しいMvcAttributeRouteHandlerインスタンスが返されます.次に、委任コードは、入力されたActionDescriptions配列を使用してM v c AttributeRouteHandlerにActions属性(ActionDescriptionsの配列)を設定し、最後に新しいプロセッサに戻る.
UserMvcに戻り、アプリケーションビルダーのUseRouter拡張メソッドが呼び出しを完了します.UseRouterに渡されるオブジェクトは、RouteBuilderのBuildメソッドを呼び出すことによって作成されます.このメソッドは、ルーティングセットにルーティングを追加します.RouteCollectionでは、名前が付けられているもの、名前が付けられていないものを追跡します.我々のMvcSandboxの例では、「default」という名前のルーティングと、名前のないAttributeRouteが得られます.
UseRouterには2つの署名があります.Irouterを渡すので、次の方法を呼び出します.
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (router == null)
{
throw new ArgumentNullException(nameof(router));
}
if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
nameof(RoutingServiceCollectionExtensions.AddRouting),
"ConfigureServices(...)"));
}
return builder.UseMiddleware(router);
}
この方法は、ServiceProviderのRoutingMarkerServiceのチェックを実現します.これが予定通り登録されていると仮定し、RouterMiddlewareをミドルウェアパイプライン(middeware pipeline)に追加します.このミドルウェアこそ、コントローラの要求とMVCの動作(action)を一致させて処理しようとするIrouterを使用します.このプロセスの詳細は、将来のブログに表示されます.
これで、アプリケーションパイプが構成され、アプリケーションはリクエストを受信する準備ができています.本文も終わります.
小結
本論文ではUseMvcを解析し,後で使用するMiddlewareFilterBuilderが設定されていることを示した.その後、RouteBuilderによってIrouterも取得され、この段階ではほとんどの作業がルーティングを登録します.設定が完了すると、ルーティングミドルウェアはパイプに登録されます.このコード(ミドルウェア)は、受信されたリクエストをどのようにチェックし、パスを適切なコントローラにマッピングし、それらの操作を処理できるかを知っています.
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return app.UseRouter(routes.Build());
}