Minimal API クイック リファレンス

このドキュメントでは、

Minimal API には次が含まれます。

WebApplication

次のコードが ASP.NET Core テンプレートによって生成されます。

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

app.MapGet("/", () => "Hello World!");

app.Run();

上記のコードは、コマンド ラインで dotnet new web を実行するか、Visual Studio で空の Web テンプレートを選択することによって作成できます。

次のコードにより、WebApplication (app) が作成されます。WebApplicationBuilder は明示的に作成されません。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create は、事前構成された既定値を使用して WebApplication クラスの新しいインスタンスを初期化します。

ポートの設定

Visual Studio または dotnet new で Web アプリが作成されると、アプリの応答先となるポートを指定する Properties/launchSettings.json ファイルが作成されます。 続くポート設定サンプルで、Visual Studio からアプリを実行すると Unable to connect to web server 'AppName' エラー ダイアログが返されます。 Properties/launchSettings.json で指定されたポートが予期されますが、アプリでは app.Run("http://localhost:3000") で指定されたポートが使用されているため、Visual Studio からエラーが返されます。 コマンド ラインから次のポート変更サンプルを実行してください。

次のセクションでは、アプリが応答するポートを設定します。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

上記のコードの場合、アプリは 3000 ポートに応答します。

複数のポート

次のコードの場合、アプリは 30004000 ポートに応答します。

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

コマンド ラインからポートを設定する

次のコマンドにより、アプリは 7777 ポートに応答するようになります。

dotnet run --urls="https://localhost:7777"

appsettings.json ファイルで Kestrel エンドポイントも構成されている場合、 appsettings.json ファイルで指定されている URL が使用されます。 詳細については、「Kestrelエンドポイント構成」を参照してください。

環境からポートを読み取る

次のコードでは、環境からポートを読み取ります。

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

環境からポートを設定する際の推奨される方法は、次のセクションに示されているように、ASPNETCORE_URLS 環境変数を使用することです。

ASPNETCORE_URLS 環境変数を使用してポートを設定する

ASPNETCORE_URLS 環境変数は、ポートを設定するために使用できます。

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS は複数の URL をサポートしています。

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

すべてのインターフェイスでリッスンする

次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

ASPNETCORE_URLS を使用して、すべてのインターフェイスでリッスンする

上記のサンプルでは、ASPNETCORE_URLS を使用できます

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

ASPNETCORE_HTTPS_PORTS を使用して、すべてのインターフェイスでリッスンする

上記のサンプルでは、ASPNETCORE_HTTPS_PORTS および ASPNETCORE_HTTP_PORTS を使用できます。

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

詳細については、「ASP.NET Core Kestrel Web サーバーのエンドポイントを構成する」を参照してください

開発証明書を使用して HTTPS を指定する

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

開発証明書の詳細については、「Trust the ASP.NET Core HTTPS development certificate on Windows and macOS」(Windows および macOS で ASP.NET Core HTTPS 開発証明書を信頼する) を参照してください。

カスタム証明書を使用して HTTPS を指定する

次のセクションは、appsettings.json ファイルを使用してカスタム証明書を指定する方法と、構成により指定する方法を示しています。

カスタム証明書を appsettings.json で指定する

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

構成によりカスタム証明書を指定する

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

証明書 API を使用する

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

環境を読み取る

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

環境の使用の詳細については、「ASP.NET Core で複数の環境を使用する」を参照してください

構成

次のコードでは、環境システムから読み取ります。

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

詳細については、「ASP.NET Core の構成」を参照してください

ログの記録

次のコードは、アプリケーションの起動時にログにメッセージを書き込みます。

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

詳細については、「.NET Core および ASP.NET Core でのログ記録」を参照してください

依存関係の挿入 (DI) コンテナーにアクセスする

次のコードは、アプリケーションの起動時に DI コンテナーからサービスを取得する方法を示しています。


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。

WebApplicationBuilder

このセクションには、WebApplicationBuilder を使用するサンプル コードが含まれています。

コンテンツ ルート、アプリケーション名、環境を変更する

次のコードは、コンテンツ ルート、アプリケーション名、環境を設定します。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder は、事前に構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。

詳細については、「ASP.NET Core の基礎の概要」を参照してください

環境変数またはコマンド ラインを使用したコンテンツ ルート、アプリ名、環境の変更

次の表は、コンテンツ ルート、アプリ名、環境を変更するために使用される環境変数とコマンド ライン引数を示しています。

の機能 環境変数 コマンドライン引数
アプリケーション名 ASPNETCORE_APPLICATIONNAME --applicationName
環境名 ASPNETCORE_ENVIRONMENT --environment
コンテンツ ルート ASPNETCORE_CONTENTROOT --contentRoot

構成プロバイダーの追加

次のサンプルでは、INI 構成プロバイダーが追加されます。

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

詳細については、「ASP.NET Core の構成」の「ファイル構成プロバイダー」を参照してください。

構成を読み取る

既定では、WebApplicationBuilder は次を含む複数のソースから構成を読み取ります。

  • appSettings.json および appSettings.{environment}.json
  • 環境変数
  • コマンド ライン

読み取る構成ソースの完全な一覧については、「ASP.NET Core の構成」の「既定の構成」を参照してください

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

環境を読み取る

次のコードは構成から HelloKey を読み取り、/ エンドポイントの値を表示します。 構成値が null 値の場合、message には "Hello" が代入されます。

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

ログ プロバイダーを追加する

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

サービスの追加

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

IHostBuilder をカスタマイズする

IHostBuilder の既存の拡張メソッドは、Host プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

IWebHostBuilder をカスタマイズする

IWebHostBuilder の拡張メソッドは、WebApplicationBuilder.WebHost プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Web ルートを変更する

既定では、Web ルートは、wwwroot フォルダーのコンテンツ ルートに対して相対的です。 静的ファイル ミドルウェアは、Web ルートで静的ファイルを探します。 Web ルートは、WebHostOptions、コマンド ライン、UseWebRoot メソッドを使用して変更できます。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

カスタムの依存関係の挿入 (DI) コンテナー

次の例では、Autofac を使用しています。

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

ミドルウェアを追加する

既存の ASP.NET Core のミドルウェアは、WebApplication で構成できます。

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

詳細については、「ASP.NET Core のミドルウェア」を参照してください

開発者例外ページ

WebApplication.CreateBuilder は、事前構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。 開発者例外ページは、事前に構成された既定値で有効化されています。 開発環境で次のコードを実行して、/ にアクセスすると、例外を示すページが表示されます。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

WebApplication

次のコードが ASP.NET Core テンプレートによって生成されます。

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

app.MapGet("/", () => "Hello World!");

app.Run();

上記のコードは、コマンド ラインで dotnet new web を実行するか、Visual Studio で空の Web テンプレートを選択することによって作成できます。

次のコードにより、WebApplication (app) が作成されます。WebApplicationBuilder は明示的に作成されません。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create は、事前構成された既定値を使用して WebApplication クラスの新しいインスタンスを初期化します。

ポートの設定

Visual Studio または dotnet new で Web アプリが作成されると、アプリの応答先となるポートを指定する Properties/launchSettings.json ファイルが作成されます。 続くポート設定サンプルで、Visual Studio からアプリを実行すると Unable to connect to web server 'AppName' エラー ダイアログが返されます。 Properties/launchSettings.json で指定されたポートが予期されますが、アプリでは app.Run("http://localhost:3000") で指定されたポートが使用されているため、Visual Studio からエラーが返されます。 コマンド ラインから次のポート変更サンプルを実行してください。

次のセクションでは、アプリが応答するポートを設定します。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

上記のコードの場合、アプリは 3000 ポートに応答します。

複数のポート

次のコードの場合、アプリは 30004000 ポートに応答します。

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

コマンド ラインからポートを設定する

次のコマンドにより、アプリは 7777 ポートに応答するようになります。

dotnet run --urls="https://localhost:7777"

appsettings.json ファイルで Kestrel エンドポイントも構成されている場合、 appsettings.json ファイルで指定されている URL が使用されます。 詳細については、「Kestrelエンドポイント構成」を参照してください。

環境からポートを読み取る

次のコードでは、環境からポートを読み取ります。

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

環境からポートを設定する際の推奨される方法は、次のセクションに示されているように、ASPNETCORE_URLS 環境変数を使用することです。

ASPNETCORE_URLS 環境変数を使用してポートを設定する

ASPNETCORE_URLS 環境変数は、ポートを設定するために使用できます。

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS は複数の URL をサポートしています。

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

すべてのインターフェイスでリッスンする

次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

ASPNETCORE_URLS を使用して、すべてのインターフェイスでリッスンする

上記のサンプルでは、ASPNETCORE_URLS を使用できます

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

開発証明書を使用して HTTPS を指定する

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

開発証明書の詳細については、「Trust the ASP.NET Core HTTPS development certificate on Windows and macOS」(Windows および macOS で ASP.NET Core HTTPS 開発証明書を信頼する) を参照してください。

カスタム証明書を使用して HTTPS を指定する

次のセクションは、appsettings.json ファイルを使用してカスタム証明書を指定する方法と、構成により指定する方法を示しています。

カスタム証明書を appsettings.json で指定する

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

構成によりカスタム証明書を指定する

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

証明書 API を使用する

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

環境を読み取る

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

環境の使用の詳細については、「ASP.NET Core で複数の環境を使用する」を参照してください

構成

次のコードでは、環境システムから読み取ります。

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

詳細については、「ASP.NET Core の構成」を参照してください

ログの記録

次のコードは、アプリケーションの起動時にログにメッセージを書き込みます。

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

詳細については、「.NET Core および ASP.NET Core でのログ記録」を参照してください

依存関係の挿入 (DI) コンテナーにアクセスする

次のコードは、アプリケーションの起動時に DI コンテナーからサービスを取得する方法を示しています。


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。

WebApplicationBuilder

このセクションには、WebApplicationBuilder を使用するサンプル コードが含まれています。

コンテンツ ルート、アプリケーション名、環境を変更する

次のコードは、コンテンツ ルート、アプリケーション名、環境を設定します。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder は、事前に構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。

詳細については、「ASP.NET Core の基礎の概要」を参照してください

環境変数またはコマンド ラインを使用したコンテンツ ルート、アプリ名、環境の変更

次の表は、コンテンツ ルート、アプリ名、環境を変更するために使用される環境変数とコマンド ライン引数を示しています。

の機能 環境変数 コマンドライン引数
アプリケーション名 ASPNETCORE_APPLICATIONNAME --applicationName
環境名 ASPNETCORE_ENVIRONMENT --environment
コンテンツ ルート ASPNETCORE_CONTENTROOT --contentRoot

構成プロバイダーの追加

次のサンプルでは、INI 構成プロバイダーが追加されます。

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

詳細については、「ASP.NET Core の構成」の「ファイル構成プロバイダー」を参照してください。

構成を読み取る

既定では、WebApplicationBuilder は次を含む複数のソースから構成を読み取ります。

  • appSettings.json および appSettings.{environment}.json
  • 環境変数
  • コマンド ライン

読み取る構成ソースの完全な一覧については、「ASP.NET Core の構成」の「既定の構成」を参照してください

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

環境を読み取る

次のコードは構成から HelloKey を読み取り、/ エンドポイントの値を表示します。 構成値が null 値の場合、message には "Hello" が代入されます。

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

ログ プロバイダーを追加する

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

サービスの追加

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

IHostBuilder をカスタマイズする

IHostBuilder の既存の拡張メソッドは、Host プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

IWebHostBuilder をカスタマイズする

IWebHostBuilder の拡張メソッドは、WebApplicationBuilder.WebHost プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Web ルートを変更する

既定では、Web ルートは、wwwroot フォルダーのコンテンツ ルートに対して相対的です。 静的ファイル ミドルウェアは、Web ルートで静的ファイルを探します。 Web ルートは、WebHostOptions、コマンド ライン、UseWebRoot メソッドを使用して変更できます。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

カスタムの依存関係の挿入 (DI) コンテナー

次の例では、Autofac を使用しています。

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

ミドルウェアを追加する

既存の ASP.NET Core のミドルウェアは、WebApplication で構成できます。

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

詳細については、「ASP.NET Core のミドルウェア」を参照してください

開発者例外ページ

WebApplication.CreateBuilder は、事前構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。 開発者例外ページは、事前に構成された既定値で有効化されています。 開発環境で次のコードを実行して、/ にアクセスすると、例外を示すページが表示されます。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

ASP.NET Core のミドルウェア

次の表に示されているのは、Minimal API でよく使用されるミドルウェアの一部です。

ミドルウェア 説明 API
認証 認証のサポートを提供します。 UseAuthentication
承認 承認のサポートを提供します。 UseAuthorization
CORS クロス オリジン リソース共有を構成します。 UseCors
例外ハンドラー ミドルウェア パイプラインがスローする例外をグローバルに処理します。 UseExceptionHandler
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 UseForwardedHeaders
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 UseHttpsRedirection
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 UseHsts
要求ログ HTTP 要求と応答のログのサポートを提供します。 UseHttpLogging
W3C 要求ログ W3C 形式の HTTP 要求と応答のログのサポートを提供します。 UseW3CLogging
応答キャッシュ 応答のキャッシュのサポートを提供します。 UseResponseCaching
応答圧縮 応答の圧縮のサポートを提供します。 UseResponseCompression
セッション ユーザー セッションの管理のサポートを提供します。 UseSession
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 UseStaticFiles, UseFileServer
WebSocket WebSocket プロトコルを有効にします。 UseWebSockets

要求処理

以下のセクションでは、ルーティング、パラメーター バインディング、応答について説明します。

ルーティング

構成された WebApplicationMap{Verb}MapMethods をサポートします。

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

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

ルート ハンドラー

ルート ハンドラーは、ルートが一致する場合に実行されるメソッドです。 ルート ハンドラーには、同期と非同期を含む任意の形式の関数を指定できます。 ルート ハンドラーには、ラムダ式、ローカル関数、インスタンス メソッド、静的メソッドを指定できます。

ラムダ式

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

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

ローカル関数

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

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

インスタンス メソッド

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

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

静的メソッド

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

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

エンドポイントへの URL を生成するためにエンドポイントに名前を付けることができます。 名前付きのエンドポイントを使用することで、アプリでパスをハード コーディングする必要がなくなります。

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

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

上記のコードは、/ エンドポイントから The link to the hello endpoint is /hello を表示します。

: エンドポイント名では大文字と小文字が区別されます。

エンドポイント名:

  • 名前はグローバルに一意である必要があります。
  • OpenAPI サポートが有効な場合、名前は OpneAPI 操作 ID として使用されます。 詳細については、OpenAPI に関する記事を参照してください。

ルート パラメーター

ルート パラメーターは、ルート パターン定義の一部として捕捉できます。

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

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

上記のコードでは、URI /users/3/books/7 にから The user id is 3 and book id is 7 が返されます。

ルート ハンドラーは、捕捉するパラメーターを宣言できます。 捕捉するように宣言されたパラメーターを持つルートに対して要求が実行されると、パラメーターが解析され、ハンドラーに渡されます。 これにより、タイプ セーフな方法で簡単に値を捕捉できるようになります。 上記のコードでは、userIdbookId は両方とも int です。

上記のコードで、どちらのルート値も int に変換できない場合、例外がスローされます。 /users/hello/books/3 への GET 要求は、次の例外をスローします。

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

ワイルドカードとキャッチ オール ルート

次のキャッチ オール ルートでは、 `/posts/hello' エンドポイントから Routing to hello が返されます。

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

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

ルート制約

ルート制約により、ルート一致時の挙動が制限されます。

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

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

次の表は、上記のルート テンプレートとその挙動を示しています。

ルート テンプレート 一致する URI の例
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

詳細については、「ASP.NET Core のルーティング」の「ルート制約参照」を参照してください。

ルート グループ

MapGroup 拡張メソッドは、共通のプレフィックスを持つエンドポイントのグループを整理するのに役立ちます。 これにより、繰り返しのコードを減らし、エンドポイント メタデータを追加する RequireAuthorizationWithMetadata のようなメソッドを 1 回呼び出すだけで、エンドポイントのグループ全体をカスタマイズできます。

たとえば、次のコードにより、2 つの似たエンドポイント グループが作成されます。

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

このシナリオでは、201 Created 結果の Location ヘッダーに相対アドレスを使用できます。

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

エンドポイントの最初のグループは、/public/todos のプレフィックスが付いた要求にのみ一致し、認証なしでアクセスできます。 エンドポイントの 2 番目のグループは、/private/todos のプレフィックスが付いた要求にのみ一致し、認証が必要です。

QueryPrivateTodosエンドポイント フィルター ファクトリは、プライベート todo データにアクセスして格納できるようにルート ハンドラーの TodoDb パラメーターを変更するローカル関数です。

ルート グループでは、ルート パラメーターと制約を含む入れ子になったグループと複雑なプレフィックス パターンもサポートされます。 次の例で、user グループにマップされたルート ハンドラーは、外部グループ プレフィックスで定義されている {org} および {group} ルート パラメーターをキャプチャできます。

プレフィックスは空にすることもできます。 これは、ルート パターンを変更せずにエンドポイントのグループにエンドポイント メタデータまたはフィルターを追加する場合に役立ちます。

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

フィルターまたはメタデータをグループに追加すると、内部グループまたは特定のエンドポイントに追加された可能性のある追加のフィルターまたはメタデータを追加する前に各エンドポイントに個別に追加する場合と同じように動作します。

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

上記の例では、外部フィルターは、2 番目に追加された場合でも、内部フィルターの前に受信要求をログに記録します。 フィルターは異なるグループに適用されているため、互いが相対的に追加された順序は関係ありません。 同じグループまたは特定のエンドポイントに適用されている場合、追加される順序フィルターは重要です。

/outer/inner/ に対する要求によって、次がログに記録されます。

/outer group filter
/inner group filter
MapGet filter

パラメーター バインド

パラメーター バインドとは、要求データを、ルート ハンドラーで表現された厳密に型指定されたパラメーターに変換するプロセスです。 バインディング ソースは、パラメーターのバインド元を指定します。 バインディング ソースは明示的に指定するか、HTTP メソッドとパラメーターの型に基づいて推測できます。

サポートされているバインディング ソース:

  • ルート値
  • クエリ文字列
  • ヘッダー
  • Body (JSON として)
  • 依存関係の挿入によって指定されるサービス
  • Custom

フォーム値からのバインドは、.NET ではネイティブにサポートされて "いません"。

次の GET ルート ハンドラーは、これらのパラメーター バインディング ソースの一部を使用しています。

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

次の表は、前の例で使用したパラメーターと、関連付けられているバインディング ソースとの関係を示しています。

パラメーター バインディング ソース
id ルート値
page クエリ文字列
customHeader header
service 依存関係の挿入によって指定

HTTP メソッド GETHEADOPTIONSDELETE は、本文から暗黙的にバインドしません。 これらの HTTP メソッドの本文から (JSON として) バインドするには、[FromBody]明示的にバインドするか、HttpRequest から読み取ります。

次の例の POST ルート ハンドラーは、person パラメーターに本文のバインディング ソースを (JSON として) 使用しています。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

前の例のパラメーターはすべて、要求データから自動的にバインドされます。 パラメーター バインディングが提供する便利さを示すために、次のルート ハンドラーは、要求からどのように直接要求データを読み取るかを示しています。

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

明示的なパラメーター バインド

属性を使用すると、パラメーターのバインド元を明示的に宣言できます。

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
パラメーター バインディング ソース
id 名前が id のルート値
page 名前が "p" のクエリ文字列
service 依存関係の挿入によって指定
contentType 名前が "Content-Type" のヘッダー

注意

フォーム値からのバインドは、.NET ではネイティブにサポートされて "いません"。

依存関係の挿入を使用したパラメーター バインド

Minimal API のパラメーター バインドでは、型がサービスとして構成されているときに、依存関係の挿入によってパラメーターをバインドします。 パラメーターに [FromServices] 属性を明示的に適用する必要はありません。 次のコードでは、どちらのアクションでも時刻が返されます。

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

省略可能なパラメーター

ルート ハンドラーで宣言されたパラメーターは、必要に応じて処理されます。

  • 要求がルートに一致する場合、すべての必須のパラメーターが要求で指定されている場合にのみルート ハンドラーが実行されます。
  • すべての必須のパラメーターが指定されていない場合は、エラーが発生します。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products BadHttpRequestException: 必須パラメーター "int pageNumber" が、クエリ文字列から指定されていません
/products/1 HTTP 404 エラー、一致するルートなし

pageNumber を省略可能にするには、型を省略可能として定義するか、既定値を指定します。

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products 1 が返される
/products2 1 が返される

上記の null 値の許容または既定値は、すべてのソースに適用されます。

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

app.MapPost("/products", (Product? product) => { });

app.Run();

上記のコードでは、要求本文が送信されていない場合、null 値の product でメソッドが呼び出されます。

: 無効なデータが指定され、パラメーターが null 値を許容する場合、ルート ハンドラーは実行されません

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products 1 が返される
/products?pageNumber=two BadHttpRequestException: "two" からパラメーター "Nullable<int> pageNumber" をバインドできませんでした。
/products/two HTTP 404 エラー、一致するルートなし

詳細については、「バインドの失敗」セクションを参照してください。

特殊な型

次の型は、明示的な属性なしでバインドされます。

  • HttpContext: 現在の HTTP 要求または応答に関するすべての情報を持つコンテキスト:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequestHttpResponse: HTTP 要求と HTTP 応答:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: 現在の HTTP 要求に関連付けられているキャンセル トークン:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: HttpContext.User からバインドされた、要求に関連付けられているユーザー:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

要求本文を Stream または PipeReader としてバインドする

ユーザーがデータを処理して次のようにする必要がある場合は、シナリオを効率的にサポートするために、要求本文を Stream または PipeReader としてバインドできます。

  • データを Blob Storage に格納するか、キュー プロバイダーにデータをエンキューします。
  • ワーカー プロセスまたはクラウド関数で、格納されたデータを処理します。

たとえば、データは Azure Queue Storage にエンキューされるか、Azure Blob Storage に格納される場合があります。

次のコードでは、バックグラウンド キューが実装されています。

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

次のコードでは、要求本文が Stream にバインドされています。

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

次に示すコードは、完全な Program.cs ファイルです。

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • データを読み取るとき、StreamHttpRequest.Body と同じオブジェクトです。
  • 要求本文は、既定ではバッファーされません。 読み取られた後の本文を巻き戻すことはできません。 ストリームを複数回読み取ることはできません。
  • 基になるバッファーが破棄または再利用されるため、最小アクション ハンドラーの外部では StreamPipeReader は使用できません。

IFormFile と IFormFileCollection を使用したファイルのアップロード

次のコードでは、IFormFileIFormFileCollection を使用して、ファイルをアップロードしています。

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

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

承認ヘッダークライアント証明書、または cookie ヘッダーを使用した認証されたファイルのアップロード要求がサポートされています。

偽造防止の組み込みでのサポートはありません。 ただし、IAntiforgery サービスを使用して実装することはできます。

ヘッダーとクエリ文字列から配列と文字列値をバインドする

次のコードは、クエリ文字列をプリミティブ型の配列、文字列配列、StringValues にバインドする方法を示しています。

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

クエリ文字列またはヘッダー値を複合型の配列にバインドすることは、その型で TryParse が実装されている場合にサポートされます。 次のコードでは、文字列配列にバインドし、指定したタグを持つすべての項目を返します。

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

次のコードは、モデルと必要な TryParse の実装を示しています。

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

次のコードでは、int 配列にバインドします。

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

上記のコードをテストするには、次のエンドポイントを追加して、データベースに Todo 項目を入力します。

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Postman などのツールを使って、次のデータを上記のエンドポイントに渡します。

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

次のコードでは、ヘッダー キー X-Todo-Id にバインドし、一致する Id 値を持つ Todo 項目を返します。

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

[AsParameters] を使用した引数リストのパラメーター バインド

AsParametersAttribute を使うと、複雑な、または再帰的なモデル バインドではなく、型へのシンプルなパラメーター バインドが可能になります。

次のコードがあるとします。

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

次の GET エンドポイントを考えてみます。

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

次の struct を使って、上記の強調表示されたパラメーターを置き換えることができます。

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

リファクタリングされた GET エンドポイントでは、上記の structAsParameters 属性と共に使用します。

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

次のコードは、アプリ内の追加のエンドポイントを示しています。

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

次のクラスは、パラメーター リストをリファクタリングするために使用されます。

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

次のコードは、AsParameters と上記の struct とクラスを使ってリファクタリングされたエンドポイントを示しています。

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

次の record 型を使って、上記のパラメーターを置き換えることができます。

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

structAsParameters と共に使うと、record 型を使うよりもパフォーマンスが向上します。

完全なサンプル コードAspNetCore.Docs.Samples リポジトリにあります。

カスタム バインド

パラメーター バインドは、2 つの方法でカスタマイズできます。

  1. ルート、クエリ、ヘッダーのバインディング ソースの場合、型の静的な TryParse メソッドを追加することにより、カスタムの型をバインドします。
  2. 型に対して BindAsync メソッドを実装することにより、バインディング プロセスを制御します。

TryParse

TryParse には 2 つの API があります。

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

次のコードは、URI /map?Point=12.3,10.1 に対して Point: 12.3, 10.1 を表示します。

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

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync には次の API があります。

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

次のコードは、URI /products?SortBy=xyz&SortDir=Desc&Page=99 に対して SortBy:xyz, SortDirection:Desc, CurrentPage:99 を表示します。

using System.Reflection;

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

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

バインドの失敗

バインドが失敗すると、フレームワークはデバッグ メッセージをログし、失敗の種類に応じてクライアントに様々さまざまな状態コードを返します。

障害モード null 値を許容するパラメーター型 バインディング ソース 状態コード
{ParameterType}.TryParsefalse を返します。 はい ルート/クエリ/ヘッダー 400
{ParameterType}.BindAsyncnull を返します。 はい custom 400
{ParameterType}.BindAsync がスローされる どちらでもよい custom 500
JSON 本文を逆シリアル化できない どちらでもよい body 400
コンテンツの型が正しくない (application/json でない) どちらでもよい body 415

バインディングの優先順位

パラメーターからバインディング ソースを決定するルールは次の通りです。

  1. パラメーターに対して定義されている明示的な属性 (From* 属性)、次の順序:
    1. ルート値: [FromRoute]
    2. クエリ文字列: [FromQuery]
    3. ヘッダー: [FromHeader]
    4. 本文: [FromBody]
    5. サービス: [FromServices]
    6. パラメーター値: [AsParameters]
  2. 特殊な型
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. PipeReader (HttpContext.Request.BodyReader)
  3. パラメーターの型に有効な静的 BindAsync メソッドがある。
  4. パラメーターの型が文字列であるか、有効な静的 TryParse メソッドがある。
    1. パラメーター名が app.Map("/todo/{id}", (int id) => {}); などのルート テンプレートにある場合、ルートからバインドされる。
    2. クエリ文字列からバインドされる。
  5. パラメーターの型が依存関係の挿入によって提供されるサービスである場合、そのサービスがソースとして使用される。
  6. パラメーターが本文からのものである。

ボディ バインドの JSON 逆シリアル化オプションを構成する

ボディ バインディング ソースは System.Text.Json を使用して逆シリアル化を行います。 この既定値を変更 することはできません が、JSON シリアル化と逆シリアル化のオプションを構成できます。

JSON 逆シリアル化オプションをグローバルに構成する

アプリにグローバルに適用されるオプションは、ConfigureHttpJsonOptions を呼び出すことによって構成できます。 次の例には、パブリック フィールドと JSON 出力形式が含まれています。

var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/", (Todo todo) => {
    if (todo is not null) {
        todo.Name = todo.NameField;
    }
    return todo;
});

app.Run();

class Todo {
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "nameField":"Walk dog",
//    "isComplete":false
// }

サンプル コードではシリアル化と逆シリアル化の両方を構成するため、出力 JSON に読み取り NameField とインクルード NameField を行うことができます。

エンドポイントの JSON 逆シリアル化オプションを構成する

ReadFromJsonAsync には、JsonSerializerOptions オブジェクトを受け入れるオーバーロードが用意されています。 次の例には、パブリック フィールドと JSON 出力形式が含まれています。

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

app.Run();

class Todo
{
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "isComplete":false
// }

上記のコードでは、カスタマイズされたオプションが逆シリアル化にのみ適用されるため、出力 JSON では NameField が除外されます。

要求本文を読み取る

要求本文を直接読み取るには、HttpContext または HttpRequest パラメーターを使用します。

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

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

上記のコードでは次の操作が行われます。

  • HttpRequest.BodyReader を使用して要求本文にアクセスします。
  • 要求本文をローカル ファイルにコピーします。

応答

ルート ハンドラーは、次の型の戻り値をサポートしています。

  1. IResult ベース - これには Task<IResult>ValueTask<IResult> が含まれます
  2. string - これには Task<string>ValueTask<string> が含まれます
  3. T (その他の型) - これには Task<T>ValueTask<T> が含まれます
戻り値 動作 Content-Type
IResult フレームワークは IResult.ExecuteAsync を呼び出す IResult の実装によって決まる
string フレームワークは、文字列を直接応答に書き込む text/plain
T (その他の型) フレームワークが応答を JSON にシリアル化する application/json

ルート ハンドラーの戻り値の詳細なガイドについては、Minimal API アプリケーションで応答を作成する方法に関するページを参照してください

戻り値の例

文字列の戻り値

app.MapGet("/hello", () => "Hello World");

JSON の戻り値

app.MapGet("/hello", () => new { Message = "Hello World" });

IResult の戻り値

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

次の例は、組み込みの結果の型を使用して応答をカスタマイズします。

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
カスタムの状態コード
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));

ストリーム
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://consoto/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

その他の例については、Minimal API アプリで応答を作成する方法に関するページを参照してください。

リダイレクト
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
ファイル
app.MapGet("/download", () => Results.File("myfile.text"));

組み込みの結果

共通の結果ヘルパーは、Microsoft.AspNetCore.Http.Results 静的クラスに含まれています。

説明 応答の種類 状態コード API
状態コード 200 と、省略可能な JSON 応答を設定します。 application/json 200 Results.Ok
JSON の応答と、省略可能なシリアル化オプションを書き込みます。 application/json (既定値)、構成可能 200 Results.Json
テキストの応答を書き込みます。 text/plain (既定値)、構成可能 200 Results.Text
応答をバイトとして書き込みます。 application/octet-stream (既定値)、構成可能 200 Results.Bytes
バイトのストリームを応答に書き込みます。 application/octet-stream (既定値)、構成可能 200 Results.Stream
content-disposition ヘッダーを含むダウンロードのため、応答にファイルをストリーミングします。 application/octet-stream (既定値)、構成可能 200 Results.File
状態コード 201 と、省略可能な JSON 応答および location ヘッダーを設定します。 application/json 201 Results.Created
状態コード 202 と、location ヘッダーを設定します。 application/json 202 Results.Accepted
状態コードを 204 に設定します。 該当なし 204 Results.NoContent
状態コード 400 と、省略可能な JSON 応答を設定します。 該当なし 400 Results.BadRequest
問題の詳細の JSON オブジェクトを検証エラーと共に応答に書き込みます。 該当なし 400 Results.ValidationProblem
状態コードを 401 に設定します。 該当なし 401 Results.Unauthorized
状態コード 404 と、省略可能な JSON 応答を設定します。 該当なし 404 Results.NotFound
状態コード 409 と、省略可能な JSON 応答を設定します。 該当なし 409 Results.Conflict
状態コード 422 と、省略可能な JSON 応答を設定します。 該当なし 422 Results.UnprocessableEntity
問題の詳細の JSON オブジェクトを応答に書き込みます。 該当なし 500 (既定値)、構成可能 Results.Problem

Microsoft.AspNetCore.Http.TypedResults 静的クラスでも同じ結果ヘルパーを使用できます。

結果のカスタマイズ

アプリケーションは、カスタムの IResult 型を実装することにより、応答を制御します。 次のコードは、HTML 結果型の例です。

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

拡張メソッドを Microsoft.AspNetCore.Http.IResultExtensions に追加して、これらのカスタムの結果をより見つけやすくすることを推奨します。

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

型指定された結果

IResult インターフェイスは、返されたオブジェクトを HTTP 応答にシリアル化する JSON の暗黙的なサポートを利用しない Minimal API から返される値を表すことができます。 異なる型の応答を表すさまざまな IResult オブジェクトを作成するには、静的な Results クラスを使います。 たとえば、応答状態コードを設定したり、別の URL にリダイレクトしたりします。

IResult を実装する型はパブリックであり、テスト時に型のアサーションを使用できます。 次に例を示します。

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

静的な TypedResults クラスの対応するメソッドの戻り値の型を調べて、キャスト先の正しいパブリック IResult 型を見つけることができます。

その他の例については、Minimal API アプリで応答を作成する方法に関するページを参照してください。

フィルター

Minimal API アプリのフィルター」を参照してください。

承認

ルートは、承認ポリシーを使用して保護できます。 これらは、[Authorize] 属性により、または RequireAuthorization メソッドを使用して宣言できます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

上記のコードは RequireAuthorization を使用して記述できます。

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

次のサンプルは、ポリシーベースの認可を使用しています。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

認証されていないユーザーがエンドポイントにアクセスできるようにする

[AllowAnonymous] は、認証されていないユーザーにエンドポイントへのアクセスを許可します。

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

CORS ポリシーを使用することにより、ルートに対して CORS を有効にできます。 CORS は、[EnableCors] 属性により、または RequireCors メソッドを使用して宣言できます。 次のサンプルにより、CORS が有効になります。

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」を参照してください

関連項目

Minimal API での OpenAPI のサポート

このドキュメントでは、

Minimal API には次が含まれます。

WebApplication

次のコードが ASP.NET Core テンプレートによって生成されます。

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

app.MapGet("/", () => "Hello World!");

app.Run();

上記のコードは、コマンド ラインで dotnet new web を実行するか、Visual Studio で空の Web テンプレートを選択することによって作成できます。

次のコードにより、WebApplication (app) が作成されます。WebApplicationBuilder は明示的に作成されません。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create は、事前構成された既定値を使用して WebApplication クラスの新しいインスタンスを初期化します。

ポートの設定

Visual Studio または dotnet new で Web アプリが作成されると、アプリの応答先となるポートを指定する Properties/launchSettings.json ファイルが作成されます。 続くポート設定サンプルで、Visual Studio からアプリを実行すると Unable to connect to web server 'AppName' エラー ダイアログが返されます。 コマンド ラインから次のポート変更サンプルを実行してください。

次のセクションでは、アプリが応答するポートを設定します。

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

上記のコードの場合、アプリは 3000 ポートに応答します。

複数のポート

次のコードの場合、アプリは 30004000 ポートに応答します。

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

コマンド ラインからポートを設定する

次のコマンドにより、アプリは 7777 ポートに応答するようになります。

dotnet run --urls="https://localhost:7777"

appsettings.json ファイルで Kestrel エンドポイントも構成されている場合、 appsettings.json ファイルで指定されている URL が使用されます。 詳細については、「Kestrelエンドポイント構成」を参照してください。

環境からポートを読み取る

次のコードでは、環境からポートを読み取ります。

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

環境からポートを設定する際の推奨される方法は、次のセクションに示されているように、ASPNETCORE_URLS 環境変数を使用することです。

ASPNETCORE_URLS 環境変数を使用してポートを設定する

ASPNETCORE_URLS 環境変数は、ポートを設定するために使用できます。

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS は複数の URL をサポートしています。

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

すべてのインターフェイスでリッスンする

次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

ASPNETCORE_URLS を使用して、すべてのインターフェイスでリッスンする

上記のサンプルでは、ASPNETCORE_URLS を使用できます

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

開発証明書を使用して HTTPS を指定する

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

開発証明書の詳細については、「Trust the ASP.NET Core HTTPS development certificate on Windows and macOS」(Windows および macOS で ASP.NET Core HTTPS 開発証明書を信頼する) を参照してください。

カスタム証明書を使用して HTTPS を指定する

次のセクションは、appsettings.json ファイルを使用してカスタム証明書を指定する方法と、構成により指定する方法を示しています。

カスタム証明書を appsettings.json で指定する

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

構成によりカスタム証明書を指定する

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

証明書 API を使用する

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

環境を読み取る

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

環境の使用の詳細については、「ASP.NET Core で複数の環境を使用する」を参照してください

構成

次のコードでは、環境システムから読み取ります。

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

詳細については、「ASP.NET Core の構成」を参照してください

ログの記録

次のコードは、アプリケーションの起動時にログにメッセージを書き込みます。

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

詳細については、「.NET Core および ASP.NET Core でのログ記録」を参照してください

依存関係の挿入 (DI) コンテナーにアクセスする

次のコードは、アプリケーションの起動時に DI コンテナーからサービスを取得する方法を示しています。


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。

WebApplicationBuilder

このセクションには、WebApplicationBuilder を使用するサンプル コードが含まれています。

コンテンツ ルート、アプリケーション名、環境を変更する

次のコードは、コンテンツ ルート、アプリケーション名、環境を設定します。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder は、事前に構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。

詳細については、「ASP.NET Core の基礎の概要」を参照してください

環境変数またはコマンド ラインを使用したコンテンツ ルート、アプリ名、環境の変更

次の表は、コンテンツ ルート、アプリ名、環境を変更するために使用される環境変数とコマンド ライン引数を示しています。

の機能 環境変数 コマンドライン引数
アプリケーション名 ASPNETCORE_APPLICATIONNAME --applicationName
環境名 ASPNETCORE_ENVIRONMENT --environment
コンテンツ ルート ASPNETCORE_CONTENTROOT --contentRoot

構成プロバイダーの追加

次のサンプルでは、INI 構成プロバイダーが追加されます。

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

詳細については、「ASP.NET Core の構成」の「ファイル構成プロバイダー」を参照してください。

構成を読み取る

既定では、WebApplicationBuilder は次を含む複数のソースから構成を読み取ります。

  • appSettings.json および appSettings.{environment}.json
  • 環境変数
  • コマンド ライン

読み取る構成ソースの完全な一覧については、「ASP.NET Core の構成」の「既定の構成」を参照してください

次のコードは構成から HelloKey を読み取り、/ エンドポイントの値を表示します。 構成値が null 値の場合、message には "Hello" が代入されます。

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

環境を読み取る

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

ログ プロバイダーを追加する

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

サービスの追加

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

IHostBuilder をカスタマイズする

IHostBuilder の既存の拡張メソッドは、Host プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

IWebHostBuilder をカスタマイズする

IWebHostBuilder の拡張メソッドは、WebApplicationBuilder.WebHost プロパティを使用してアクセスできます。

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Web ルートを変更する

既定では、Web ルートは、wwwroot フォルダーのコンテンツ ルートに対して相対的です。 静的ファイル ミドルウェアは、Web ルートで静的ファイルを探します。 Web ルートは、WebHostOptions、コマンド ライン、UseWebRoot メソッドを使用して変更できます。

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

カスタムの依存関係の挿入 (DI) コンテナー

次の例では、Autofac を使用しています。

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

ミドルウェアを追加する

既存の ASP.NET Core のミドルウェアは、WebApplication で構成できます。

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

詳細については、「ASP.NET Core のミドルウェア」を参照してください

開発者例外ページ

WebApplication.CreateBuilder は、事前構成された既定値を使用して WebApplicationBuilder クラスの新しいインスタンスを初期化します。 開発者例外ページは、事前に構成された既定値で有効化されています。 開発環境で次のコードを実行して、/ にアクセスすると、例外を示すページが表示されます。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

ASP.NET Core のミドルウェア

次の表に示されているのは、Minimal API でよく使用されるミドルウェアの一部です。

ミドルウェア 説明 API
認証 認証のサポートを提供します。 UseAuthentication
承認 承認のサポートを提供します。 UseAuthorization
CORS クロス オリジン リソース共有を構成します。 UseCors
例外ハンドラー ミドルウェア パイプラインがスローする例外をグローバルに処理します。 UseExceptionHandler
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 UseForwardedHeaders
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 UseHttpsRedirection
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 UseHsts
要求ログ HTTP 要求と応答のログのサポートを提供します。 UseHttpLogging
W3C 要求ログ W3C 形式の HTTP 要求と応答のログのサポートを提供します。 UseW3CLogging
応答キャッシュ 応答のキャッシュのサポートを提供します。 UseResponseCaching
応答圧縮 応答の圧縮のサポートを提供します。 UseResponseCompression
セッション ユーザー セッションの管理のサポートを提供します。 UseSession
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 UseStaticFiles, UseFileServer
WebSocket WebSocket プロトコルを有効にします。 UseWebSockets

要求処理

以下のセクションでは、ルーティング、パラメーター バインディング、応答について説明します。

ルーティング

構成された WebApplicationMap{Verb}MapMethods をサポートします。

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

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

ルート ハンドラー

ルート ハンドラーは、ルートが一致する場合に実行されるメソッドです。 ルート ハンドラーには、同期と非同期を含む任意の形式の関数を指定できます。 ルート ハンドラーには、ラムダ式、ローカル関数、インスタンス メソッド、静的メソッドを指定できます。

ラムダ式

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

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

ローカル関数

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

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

インスタンス メソッド

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

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

静的メソッド

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

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

エンドポイントへの URL を生成するためにエンドポイントに名前を付けることができます。 名前付きのエンドポイントを使用することで、アプリでパスをハード コーディングする必要がなくなります。

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

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

上記のコードは、/ エンドポイントから The link to the hello endpoint is /hello を表示します。

: エンドポイント名では大文字と小文字が区別されます。

エンドポイント名:

  • 名前はグローバルに一意である必要があります。
  • OpenAPI サポートが有効な場合、名前は OpneAPI 操作 ID として使用されます。 詳細については、OpenAPI に関する記事を参照してください。

ルート パラメーター

ルート パラメーターは、ルート パターン定義の一部として捕捉できます。

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

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

上記のコードでは、URI /users/3/books/7 にから The user id is 3 and book id is 7 が返されます。

ルート ハンドラーは、捕捉するパラメーターを宣言できます。 捕捉するように宣言されたパラメーターを持つルートに対して要求が実行されると、パラメーターが解析され、ハンドラーに渡されます。 これにより、タイプ セーフな方法で簡単に値を捕捉できるようになります。 上記のコードでは、userIdbookId は両方とも int です。

上記のコードで、どちらのルート値も int に変換できない場合、例外がスローされます。 /users/hello/books/3 への GET 要求は、次の例外をスローします。

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

ワイルドカードとキャッチ オール ルート

次のキャッチ オール ルートでは、 `/posts/hello' エンドポイントから Routing to hello が返されます。

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

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

ルート制約

ルート制約により、ルート一致時の挙動が制限されます。

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

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text)));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

次の表は、上記のルート テンプレートとその挙動を示しています。

ルート テンプレート 一致する URI の例
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

詳細については、「ASP.NET Core のルーティング」の「ルート制約参照」を参照してください。

パラメーター バインド

パラメーター バインドとは、要求データを、ルート ハンドラーで表現された厳密に型指定されたパラメーターに変換するプロセスです。 バインディング ソースは、パラメーターのバインド元を指定します。 バインディング ソースは明示的に指定するか、HTTP メソッドとパラメーターの型に基づいて推測できます。

サポートされているバインディング ソース:

  • ルート値
  • クエリ文字列
  • ヘッダー
  • Body (JSON として)
  • 依存関係の挿入によって指定されるサービス
  • Custom

注意

フォーム値からのバインドは、.NET ではネイティブにサポートされて "いません"。

次の例の GET ルート ハンドラーは、これらのパラメーター バインディング ソースの一部を使用しています。

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

次の表は、前の例で使用したパラメーターと、関連付けられているバインディング ソースとの関係を示しています。

パラメーター バインディング ソース
id ルート値
page クエリ文字列
customHeader header
service 依存関係の挿入によって指定

HTTP メソッド GETHEADOPTIONSDELETE は、本文から暗黙的にバインドしません。 これらの HTTP メソッドの本文から (JSON として) バインドするには、[FromBody]明示的にバインドするか、HttpRequest から読み取ります。

次の例の POST ルート ハンドラーは、person パラメーターに本文のバインディング ソースを (JSON として) 使用しています。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

前の例のパラメーターはすべて、要求データから自動的にバインドされます。 パラメーター バインディングが提供する便利さを示すために、次の例のルート ハンドラーは、要求からどのように直接要求データを読み取るかを示しています。

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

明示的なパラメーター バインド

属性を使用すると、パラメーターのバインド元を明示的に宣言できます。

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
パラメーター バインディング ソース
id 名前が id のルート値
page 名前が "p" のクエリ文字列
service 依存関係の挿入によって指定
contentType 名前が "Content-Type" のヘッダー

注意

フォーム値からのバインドは、.NET ではネイティブにサポートされて "いません"。

DI によるパラメーター バインド

Minimal API のパラメーター バインドでは、型がサービスとして構成されているときに、依存関係の挿入によってパラメーターをバインドします。 パラメーターに [FromServices] 属性を明示的に適用する必要はありません。 次のコードでは、どちらのアクションでも時刻が返されます。

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

省略可能なパラメーター

ルート ハンドラーで宣言されたパラメーターは、必要に応じて処理されます。

  • 要求がルートに一致する場合、すべての必須のパラメーターが要求で指定されている場合にのみルート ハンドラーが実行されます。
  • すべての必須のパラメーターが指定されていない場合は、エラーが発生します。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products BadHttpRequestException: 必須パラメーター "int pageNumber" が、クエリ文字列から指定されていません
/products/1 HTTP 404 エラー、一致するルートなし

pageNumber を省略可能にするには、型を省略可能として定義するか、既定値を指定します。

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products 1 が返される
/products2 1 が返される

上記の null 値の許容または既定値は、すべてのソースに適用されます。

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

app.MapPost("/products", (Product? product) => { });

app.Run();

上記のコードでは、要求本文が送信されていない場合、null 値の product でメソッドが呼び出されます。

: 無効なデータが指定され、パラメーターが null 値を許容する場合、ルート ハンドラーは実行されません

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI 結果
/products?pageNumber=3 3 が返される
/products 1 が返される
/products?pageNumber=two BadHttpRequestException: "two" からパラメーター "Nullable<int> pageNumber" をバインドできませんでした。
/products/two HTTP 404 エラー、一致するルートなし

詳細については、「バインドの失敗」セクションを参照してください。

特殊な型

次の型は、明示的な属性なしでバインドされます。

  • HttpContext: 現在の HTTP 要求または応答に関するすべての情報を持つコンテキスト:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequestHttpResponse: HTTP 要求と HTTP 応答:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: 現在の HTTP 要求に関連付けられているキャンセル トークン:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: HttpContext.User からバインドされた、要求に関連付けられているユーザー:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

カスタム バインド

パラメーター バインドは、2 つの方法でカスタマイズできます。

  1. ルート、クエリ、ヘッダーのバインディング ソースの場合、型の静的な TryParse メソッドを追加することにより、カスタムの型をバインドします。
  2. 型に対して BindAsync メソッドを実装することにより、バインディング プロセスを制御します。

TryParse

TryParse には 2 つの API があります。

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

次のコードは、URI /map?Point=12.3,10.1 に対して Point: 12.3, 10.1 を表示します。

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

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync には次の API があります。

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

次のコードは、URI /products?SortBy=xyz&SortDir=Desc&Page=99 に対して SortBy:xyz, SortDirection:Desc, CurrentPage:99 を表示します。

using System.Reflection;

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

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

バインドの失敗

バインドが失敗すると、フレームワークはデバッグ メッセージをログし、失敗の種類に応じてクライアントに様々さまざまな状態コードを返します。

障害モード null 値を許容するパラメーター型 バインディング ソース 状態コード
{ParameterType}.TryParsefalse を返します。 はい ルート/クエリ/ヘッダー 400
{ParameterType}.BindAsyncnull を返します。 はい custom 400
{ParameterType}.BindAsync がスローされる どちらでもよい custom 500
JSON 本文を逆シリアル化できない どちらでもよい body 400
コンテンツの型が正しくない (application/json でない) どちらでもよい body 415

バインディングの優先順位

パラメーターからバインディング ソースを決定するルールは次の通りです。

  1. パラメーターに対して定義されている明示的な属性 (From* 属性)、次の順序:
    1. ルート値: [FromRoute]
    2. クエリ文字列: [FromQuery]
    3. ヘッダー: [FromHeader]
    4. 本文: [FromBody]
    5. サービス: [FromServices]
  2. 特殊な型
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. パラメーターの型に有効な BindAsync メソッドがある。
  4. パラメーターの型が文字列であるか、有効な TryParse メソッドがある。
    1. パラメーター名が app.Map("/todo/{id}", (int id) => {}); などのルート テンプレートにある場合、ルートからバインドされる。
    2. クエリ文字列からバインドされる。
  5. パラメーターの型が依存関係の挿入によって提供されるサービスである場合、そのサービスがソースとして使用される。
  6. パラメーターが本文からのものである。

JSON バインドをカスタマイズする

本文のバインディング ソースは System.Text.Json を使用して逆シリアル化を行います。 この既定値を変更することは "できません" が、既に説明した他の方法を使ってバインディングをカスタマイズすることはできます。 JSON シリアライザー オプションをカスタマイズするには、次のようなコードを使用します。

using Microsoft.AspNetCore.Http.Json;

var builder = WebApplication.CreateBuilder(args);

// Configure JSON options.
builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/products", (Product product) => product);

app.Run();

class Product
{
    // These are public fields, not properties.
    public int Id;
    public string? Name;
}

上記のコードでは次の操作が行われます。

  • 入力および出力の既定の JSON オプションが構成されます。
  • 次の JSON が返されます
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    POST の場合
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

要求本文を読み取る

要求本文を直接読み取るには、HttpContext または HttpRequest パラメーターを使用します。

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

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

上記のコードでは次の操作が行われます。

  • HttpRequest.BodyReader を使用して要求本文にアクセスします。
  • 要求本文をローカル ファイルにコピーします。

応答

ルート ハンドラーは、次の型の戻り値をサポートしています。

  1. IResult ベース - これには Task<IResult>ValueTask<IResult> が含まれます
  2. string - これには Task<string>ValueTask<string> が含まれます
  3. T (その他の型) - これには Task<T>ValueTask<T> が含まれます
戻り値 動作 Content-Type
IResult フレームワークは IResult.ExecuteAsync を呼び出す IResult の実装によって決まる
string フレームワークは、文字列を直接応答に書き込む text/plain
T (その他の型) フレームワークが応答を JSON にシリアル化する application/json

戻り値の例

文字列の戻り値

app.MapGet("/hello", () => "Hello World");

JSON の戻り値

app.MapGet("/hello", () => new { Message = "Hello World" });

IResult の戻り値

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

次の例は、組み込みの結果の型を使用して応答をカスタマイズします。

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
カスタムの状態コード
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
ストリーム
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://consoto/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

app.Run();
リダイレクト
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
ファイル
app.MapGet("/download", () => Results.File("myfile.text"));

組み込みの結果

共通の結果ヘルパーは、Microsoft.AspNetCore.Http.Results 静的クラスに含まれています。

説明 応答の種類 状態コード API
詳細なオプションを使用した JSON 応答の書き込み application/json 200 Results.Json
JSON 応答の書き込み application/json 200 Results.Ok
テキスト応答の書き込み text/plain (既定値)、構成可能 200 Results.Text
応答をバイトとして書き込む application/octet-stream (既定値)、構成可能 200 Results.Bytes
応答にバイトのストリームを書き込む application/octet-stream (既定値)、構成可能 200 Results.Stream
content-disposition ヘッダーによるダウンロードのために、応答に対してファイルをストリーミングする application/octet-stream (既定値)、構成可能 200 Results.File
状態コードを 404 に設定し、オプションの JSON 応答を指定する 該当なし 404 Results.NotFound
状態コードを 204 に設定する 該当なし 204 Results.NoContent
状態コードを 422 に設定し、オプションの JSON 応答を指定する 該当なし 422 Results.UnprocessableEntity
状態コードを 400 に設定し、オプションの JSON 応答を指定する 該当なし 400 Results.BadRequest
状態コードを 409 に設定し、オプションの JSON 応答を指定する 該当なし 409 Results.Conflict
問題の詳細の JSON オブジェクトを応答に書き込む 該当なし 500 (既定値)、構成可能 Results.Problem
問題の詳細の JSON オブジェクトを検証エラーと共に応答に書き込む 該当なし 該当なし、構成可能 Results.ValidationProblem

結果のカスタマイズ

アプリケーションは、カスタムの IResult 型を実装することにより、応答を制御します。 次のコードは、HTML 結果型の例です。

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

拡張メソッドを Microsoft.AspNetCore.Http.IResultExtensions に追加して、これらのカスタムの結果をより見つけやすくすることを推奨します。

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

承認

ルートは、承認ポリシーを使用して保護できます。 これらは、[Authorize] 属性により、または RequireAuthorization メソッドを使用して宣言できます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

上記のコードは RequireAuthorization を使用して記述できます。

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

次のサンプルは、ポリシーベースの認可を使用しています。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

認証されていないユーザーがエンドポイントにアクセスできるようにする

[AllowAnonymous] は、認証されていないユーザーにエンドポイントへのアクセスを許可します。

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

CORS ポリシーを使用することにより、ルートに対して CORS を有効にできます。 CORS は、[EnableCors] 属性により、または RequireCors メソッドを使用して宣言できます。 次のサンプルにより、CORS が有効になります。

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」を参照してください

関連項目

Minimal API での OpenAPI のサポート