ASP.NET Core 3.0 Razor Pagesの公式チュートリアルをやって感じたのは、Startup.cs が一番の鬼門かな、ということ。
もう少し、Startup.csの理解を深めておく必要がありそうです。
僕もまだわかっていないことが多いので、もし間違い等あれば指摘していただけると嬉しいです。
Startup.cs
ということで、チュートリアルで作成した Startup.csを開いてみます。
このクラスでは、アプリの動作を構成するコードを記述するようです。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.HttpsPolicy;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Hosting;usingRazorPagesMovie.Models;usingMicrosoft.EntityFrameworkCore;namespaceRazorPagesMovie{publicclassStartup{publicStartup(IConfigurationconfiguration){Configuration=configuration;}publicIConfigurationConfiguration{get;}// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){services.AddRazorPages();services.AddDbContext<RazorPagesMovieContext>(options=>options.UseSqlite(Configuration.GetConnectionString("MovieContext")));}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Error");// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints=>{endpoints.MapRazorPages();});}}}ConfigureServicesと Configureの2つのメソッドがあります。呼び出される順番は、ConfigureServices→ Configureの順です。
ConfigureServicesメソッド
コメントを読むと、ランタイムから呼び出されるメソッドで、このメソッド内で、コンテナにサービスを追加するコードを書くということのようです。
コンテナとサービスが何かが良くわかってませんが、このWebアプリに必要な機能をここで追加するということだと思います。ASP.NET Coreはプラガブル(といっていいのかな)な構造になっていて、開発者が必要な機能を明示的に組み込むようになっています。
最初の
services.AddRazorPages();では、Razor pagesの機能を有効にしています。
次の
services.AddDbContext<RazorPagesMovieContext>(options=>options.UseSqlite(Configuration.GetConnectionString("MovieContext")));では、チュートリアルで作成した DBアクセスのための RazorPagesMovieContextをアプリケーションから利用できるようにするためにコードです。
SQLiteを利用し、その接続文字列は、構成ファイルの "MovieContext" から取得しています。
これによって、各ページモデルで RazorPagesMovieContextのインスタンスを生成する必要はなくなります。
チュートリアルのコードでは、
publicclassIndexModel:PageModel{privatereadonlyRazorPagesMovie.Models.RazorPagesMovieContext_context;publicIndexModel(RazorPagesMovie.Models.RazorPagesMovieContextcontext){_context=context;}...と、ページモデルのコンストラクタで、RazorPagesMovieContextのインスタンスを受け取っていましたが、ConfigureServicesメソッドで、サービスを登録していることでこれが実現できていたということですね。
AddEntityFrameworkStoresといったメソッドも用意されているようです。これら Addで始まるメソッドは、IServiceCollectionインターフェースに対する拡張メソッドとして定義されています。
なお、ここで追加したサービスは、依存関係の挿入(DI) または ApplicationServicesを利用して利用することができます。
Configure メソッド
Configure メソッドもランタイムから呼び出されます。
コメントには、このメソッドを使ってHTTPリクエストパイプラインを設定します、とあります。
ということは、この順番が意味をも持つってことですね。
まだ、完全に理解していないけど、
app.UseHttpsRedirection();は、httpをhttpsにリダイレクトさせる。
app.UseStaticFiles();は、静的ファイルを提供できるようにする。
app.UseRouting();は、ルーティングを標準設定で構成する。
app.UseAuthorization();は、認証を構成する。
ということをやっているようです。
Useで始まるメソッドは、IApplicationBuilder の拡張メソッドとして定義されています。
env.IsDevelopment
それと、Configureメソッドの最初では、env.IsDevelopmentの値を見て、if文で分岐させている個所があります。envは、引数で渡ってくる IWebHostEnvironmentのインスタンスです。
開発環境と運用環境で動作を変更するために利用しています。
調べたところ、ASP.NET Core はアプリの起動時に環境変数 ASPNETCORE_ENVIRONMENTの値を読み込み、このプロパティの値を設定しているようです。
ASPNETCORE_ENVIRONMENTには、"Development"、"Staging"、"Production" という 3 つの値を指定できます。ASPNETCORE_ENVIRONMENT が設定されていない場合、既定で Production になります。
Visual Studio Codeの、launch.jsonを見ると、
"configurations":[{"name":".NET Core Launch (web)","type":"coreclr","request":"launch","preLaunchTask":"build","program":"${workspaceFolder}/bin/Debug/netcoreapp3.0/RazorPagesMovie.dll","args":[],"cwd":"${workspaceFolder}","stopAtEntry":false,"serverReadyAction":{"action":"openExternally","pattern":"^\\s*Now listening on:\\s+(https?://\\S+)"},"env":{"ASPNETCORE_ENVIRONMENT":"Development"},"sourceFileMap":{"/Views":"${workspaceFolder}/Views"}},となっています。なので、VS Codeから起動する場合は、env.IsDevelopmentプロパティは、trueになります。
なお、env.IsDevelopmentプロパティが、falseの時には、
app.UseExceptionHandler("/Error");app.UseHsts();となっているので、例外発生時は、"/Error"にリダイレクトされるようです。つまり、Error.cshtml, Error.cshtml.cs が利用されるということですね。
app.UseHsts()は、 HSTS (Hypertext Strict Transport Security) をブラウザに通知するようにしているコードです。
Visual Studio IDEでは、プロジェクトのプロパティページでASPNETCORE_ENVIRONMENT 環境変数の値を設定できます。
appsettings.Development.json
そういえば、チュートリアルで appsettings.jsonについてすこし触れましたが、appsettings.Development.jsonというファイルもプロジェクトには存在していました。
開発時(ASPNETCORE_ENVIRONMENT=Development)には、appsettings.jsonの内容に、appsettings.Development.jsonの内容が上書きされて、利用されることになるようです。
利用される接続文字列は、appsettings.jsonに書かれているのですが、appsettings.Development.jsonにも書けば、デバッグ時は、
appsettings.Development.jsonに書かれているConnectionStringsの値が利用されるということですね。
つまり、開発中と運用で接続文字列を簡単に切り替えることができるということです。
web.configとweb.debug.config,web.release.configとの関係に似ていますね。
でも、web.debug.config,web.release.configでの特殊な記法が必要ないので、理解しやすいですね。
Startup コンストラクター
Startコンストラクタは、以下のパラメータを受け取りことができます。
IHostingEnvironment (環境別にサービスを構成するため)。
IConfiguration (スタートアップ時にアプリケーションを構成するため)。
ILoggerFactory (ロギングを構成するため)
これらのコンストラクタは省略することもできます。
実際、チュートリアルで利用したコンストラクタは、以下のように IConfigurationだけを受け取っています。
publicStartup(IConfigurationconfiguration){Configuration=configuration;}全てを受け取る場合は、以下のように書きます。
publicStartup(IHostingEnvironmentenv,IConfigurationconfiguration,ILoggerFactoryloggerFactory){Configuration=configuration;_env=env;_loggerFactory=loggerFactory;}publicIConfigurationConfiguration{get;}privatereadonlyIHostingEnvironment_env;privatereadonlyILoggerFactory_loggerFactory;Program.cs
これまで見てきた Startupクラスは、Program.csのMainメソッドから呼び出されるCreateWebHostBuilderメソッドで指定されています。
チュートリアルで作成した Program.cs は、以下の通り。
usingSystem;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.Extensions.Hosting;usingMicrosoft.Extensions.Logging;usingMicrosoft.Extensions.DependencyInjection;usingRazorPagesMovie.Models;namespaceRazorPagesMovie{publicclassProgram{publicstaticvoidMain(string[]args){varhost=CreateHostBuilder(args).Build();using(varscope=host.Services.CreateScope()){varservices=scope.ServiceProvider;try{SeedData.Initialize(services);}catch(Exceptionex){varlogger=services.GetRequiredService<ILogger<Program>>();logger.LogError(ex,"An error occurred seeding the DB.");}}host.Run();}publicstaticIHostBuilderCreateHostBuilder(string[]args)=>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder=>{webBuilder.UseStartup<Startup>();});}}ドキュメントによると、Mainメソッドで、ホスト といわれるものを組み立てています。ホストとは、以下をカプセル化するオブジェクトです。
- HTTP サーバーの実装
- ミドルウェア コンポーネント
- ログの記録
- DI
- 構成
上記のコードでは、
- Web サーバーとして Kestrel を使用し、IIS 統合を有効にする。
- appsettings.json、"appsettings.{環境名}.json"、環境変数、コマンド ライン引数、およびその他の構成ソースから構成を読み込む。
- ログ出力をコンソールとデバッグ プロバイダーに送る。
というオプションとともにホストを構成しています。
なお、サービスの追加とリクエストパイプラインの構成以外の初期化が必要ならば、Program.cs で行うってことですね。
Mainメソッドでは、DIが利用できないので、
SeedData.Initialize(services);や
varlogger=services.GetRequiredService<ILogger<Program>>();のように IServiceProviderのインスタンスを使って、サービスにアクセスしているってことですね。
デバッグで確かめたところ
Main → CreateHostBuilder → Startup.ConfigureServices → SeedData.Initialize → host.Run() → Startup.Configure
の順で呼び出されていました。
Startup クラスがすこし理解できたように思います。