白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

ASP.NETアプリケーションをDocker用にマイグレーションする(.NET 7)

コンソールアプリケーションのマイグレーションをしたので、今度はWebのマイグレーションをします。
あるあるな気がしますが、記事にしてないだけでだいぶ前に実施したのでもう記憶が…。

とりあえずプロジェクトを作る

VisualStudioからASP.NET Core Web アプリ(Model-View-Controller)を選んで(MVCなのは元がMVCだからです)、

フレームワークに.NET 7.0を選びます。コンソールアプリと一緒ですね。

こんな感じになります。

プロジェクトファイルがこんな感じで、

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

</Project>

Program.csがこんな感じ。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

実行するとこんな感じ。

汎用ホストだ

あくまでマイグレーションなので、Webアプリケーションどうやって作るかみたいな話はしません。
ポイントはProgram.csの書き方くらいですね。

汎用ホストの記事を読むとわかるかと思うのですが、このProgram.csの内容が何となく汎用ホストの書き方に似ています。
追ってみるとWebApplication.CreateBuilderが作るWebApplicationBuilderがIHostBuilderの実装クラスであるConfigureHostBuilderを持っているのがわかります。
つまりこの仕組みも汎用ホストで、それをさらにWeb用にラップしているようです。

そういえば汎用ホストを試していたときにバックグラウンドで動いて、自分で停止しない限り動き続けるみたいなことを書きましたね。
よく考えてみるとこの仕様はまさにWeb用にぴったりです。
つまり汎用ホストによってコンソールアプリケーションとWebアプリケーション、バックグラウンドサービスみたいなのが全部同じ仕組みで動くわけですね。

で、汎用ホストと似たようなものだと思えばそんなに難しさもありません。
BuildされるものがIHostじゃなくてWebApplicationになっただけで、それをRunするのは一緒ですね。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// いろいろする

app.Run();

nginx用にごにょごにょ

Dockerではnginxの裏側にアプリケーションを置く想定なのがちょっとややこしいところです。
今回は「/sample」のパスでこのアプリケーションを動かすことにしましょう。

appsettings.jsonにPathBaseという設定を足します。(名前はご自由に)

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "PathBase": "/sample"
}

コンソールアプリのときと同じようにASPNETCORE_ENVIRONMENTにDevelopmentを設定しておいて、appsettings.Development.jsonには書かなければデバッグ時には効かなくなります。
これでデバック時にはリバプロの存在を気にしなくてOKになります。

で、これをProgram.csで既定のパスに設定します。

var pathBase = app.Configuration.GetSection("PathBase").Get<string>();
if (!string.IsNullOrEmpty(pathBase))
{
    app.UsePathBase(pathBase);
}

nginx側の設定はこうなります。(default.conf)
同じネットワークに設定すればコンテナ名で参照できます。

location /sample/ {
    proxy_pass http://SampleContainer/sample/;
}

静的ファイルはnginx側で配信するのでとりあえずUseStaticFilesはいらないです。
認証するのであれば必要ですが、今回はUseAuthorizationもいらないです。

で、最終的にこうなります。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
using var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
}
var pathBase = app.Configuration.GetSection("PathBase").Get<string>();
if (!string.IsNullOrEmpty(pathBase))
{
    app.UsePathBase(pathBase);
}

app.UseRouting();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

設定をControllerに渡そう

Controllerの定義を見るとこんな感じになっています。

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        return View();
    }

    // 略
}

これも汎用ホストのときに見たDIと一緒なので、設定を渡したい場合はこうなります。(thisはただの私の好みです)

public class HomeController : Controller
{
    private readonly IConfiguration configuration;
    private readonly ILogger<HomeController> logger;

    public HomeController(IConfiguration configuration, ILogger<HomeController> logger)
    {
        this.configuration = configuration;
        this.logger = logger;
    }

    public IActionResult Index()
    {
        return View();
    }

    // 略
}

似てる

他はあんまり特別なことをする必要はないかと思います。
汎用ホストを一回見てるととてもわかりやすいですね。