ASP.NET Core 3.0 Razor Pagesの公式チュートリアルをやって感じたのは、Startup.cs が一番の鬼門かな、ということ。
もう少し、Startup.csの理解を深めておく必要がありそうです。
僕もまだわかっていないことが多いので、もし間違い等あれば指摘していただけると嬉しいです。
Startup.cs
ということで、チュートリアルで作成した Startup.cs
を開いてみます。
このクラスでは、アプリの動作を構成するコードを記述するようです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RazorPagesMovie.Models;
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
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.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
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
のインスタンスを生成する必要はなくなります。
チュートリアルのコードでは、
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
{
_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
だけを受け取っています。
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
全てを受け取る場合は、以下のように書きます。
public Startup(IHostingEnvironment env, IConfiguration configuration, ILoggerFactory loggerFactory) {
Configuration = configuration;
_env = env;
_loggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
private readonly IHostingEnvironment _env;
private readonly ILoggerFactory _loggerFactory;
Program.cs
これまで見てきた Startup
クラスは、Program.csのMain
メソッドから呼び出されるCreateWebHostBuilder
メソッドで指定されています。
チュートリアルで作成した Program.cs は、以下の通り。
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Models;
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(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);
や
var logger = services.GetRequiredService<ILogger<Program>>();
のように IServiceProvider
のインスタンスを使って、サービスにアクセスしているってことですね。
デバッグで確かめたところ
Main → CreateHostBuilder → Startup.ConfigureServices → SeedData.Initialize → host.Run() → Startup.Configure
の順で呼び出されていました。
Startup クラスがすこし理解できたように思います。