トレーニング
モジュール
minimal API、ASP.NET Core、.NET を使用して Web API をビルドする - Training
.NET を使用して Web API をビルドする方法について説明します。 また、読み取りと書き込みの両方を処理するさまざまなルートを設定する方法についても説明します。
このブラウザーはサポートされなくなりました。
Microsoft Edge にアップグレードすると、最新の機能、セキュリティ更新プログラム、およびテクニカル サポートを利用できます。
注意
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
このドキュメントでは、
Minimal API には次が含まれます。
次のコードが 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 クラスの新しいインスタンスを初期化します。
WebApplication
では、特定の条件に応じて、Minimal API applications
に次のミドルウェアが自動的に追加されます。
UseDeveloperExceptionPage
は、HostingEnvironment
が "Development"
である場合、最初に追加されます。UseRouting
は、ユーザー コードによって UseRouting
がまだ呼び出されておらず、エンドポイントが構成されている (app.MapGet
など) 場合、2 番目に追加されます。UseEndpoints
は、エンドポイントが構成されている場合、ミドルウェア パイプラインの最後に追加されます。UseAuthentication
は、ユーザー コードによって UseRouting
がまだ呼び出されておらず、サービス プロバイダーで UseAuthentication
が検出できる場合、IAuthenticationSchemeProvider
の直後に追加されます。 IAuthenticationSchemeProvider
は、AddAuthentication
を使用するときに既定で追加され、サービスは IServiceProviderIsService
を使用して検出されます。UseAuthorization
は、ユーザー コードによって UseAuthorization
がまだ呼び出されておらず、サービス プロバイダーで IAuthorizationHandlerProvider
が検出できる場合、次に追加されます。 IAuthorizationHandlerProvider
は、AddAuthorization
を使用するときに既定で追加され、サービスは IServiceProviderIsService
を使用して検出されます。UseRouting
と UseEndpoints
の間に追加されます。次のコードは、アプリに追加される自動ミドルウェアがどのようなものを生成するかを示しています。
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
場合によっては、既定のミドルウェア構成がアプリに対して正しくなく、変更が必要になることがあります。 たとえば、UseCors は UseAuthentication と UseAuthorization の前に呼び出される必要があります。 UseAuthentication
を呼び出す場合、アプリでは UseAuthorization
と UseCors
を呼び出す必要があります。
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
ルートの照合が発生する前にミドルウェアを実行する必要がある場合は、UseRouting を呼び出す必要があり、ミドルウェアは UseRouting
への呼び出しの前に配置する必要があります。 この場合、UseEndpoints は前述のように自動的に追加されるため、必要ありません。
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
ターミナル ミドルウェアを追加する場合:
UseEndpoints
の後に追加される必要があります。UseRouting
と UseEndpoints
を呼び出す必要があります。app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ターミナル ミドルウェアは、いずれのエンドポイントによっても要求が処理されない場合に実行されるミドルウェアです。
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
ポートに応答します。
次のコードの場合、アプリは 3000
と 4000
ポートに応答します。
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"
Kestrel ファイルで appsettings.json
エンドポイントも構成されている場合、 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=http://localhost:3000
ASPNETCORE_URLS
は複数の URL をサポートしています。
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
上記のサンプルでは、ASPNETCORE_URLS
を使用できます
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
上記のサンプルでは、ASPNETCORE_HTTPS_PORTS
および ASPNETCORE_HTTP_PORTS
を使用できます。
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
詳細については、「ASP.NET Core Kestrel Web サーバーのエンドポイントを構成する」を参照してください
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 開発証明書を信頼する) を参照してください。
次のセクションは、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();
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"] ?? "Config failed!";
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 コンテナーからサービスを取得する方法を示しています。
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();
次のコードは、[FromKeyedServices]
属性を使用して DI コンテナーからキーにアクセスする方法を示しています。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
DI の詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。
このセクションには、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);
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 の既存の拡張メソッドは、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 の拡張メソッドは、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 ルートは、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();
次の例では、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();
次の表に示されているのは、Minimal API でよく使用されるミドルウェアの一部です。
ミドルウェア | 説明 | API |
---|---|---|
認証 | 認証のサポートを提供します。 | UseAuthentication |
承認 | 承認のサポートを提供します。 | UseAuthorization |
CORS | クロス オリジン リソース共有を構成します。 | UseCors |
例外ハンドラー | ミドルウェア パイプラインがスローする例外をグローバルに処理します。 | UseExceptionHandler |
転送されるヘッダー | プロキシされたヘッダーを現在の要求に転送します。 | UseForwardedHeaders |
HTTPS リダイレクト | すべての HTTP 要求を HTTPS にリダイレクトします。 | UseHttpsRedirection |
HTTP Strict Transport Security (HSTS) | 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 | UseHsts |
要求ログ | HTTP 要求と応答のログのサポートを提供します。 | UseHttpLogging |
要求のタイムアウト | グローバルな既定値として、およびエンドポイントごとに、要求のタイムアウトを構成するサポートを提供します。 | UseRequestTimeouts |
W3C 要求ログ | W3C 形式の HTTP 要求と応答のログのサポートを提供します。 | UseW3CLogging |
応答キャッシュ | 応答のキャッシュのサポートを提供します。 | UseResponseCaching |
応答圧縮 | 応答の圧縮のサポートを提供します。 | UseResponseCompression |
セッション | ユーザー セッションの管理のサポートを提供します。 | UseSession |
静的ファイル | 静的ファイルとディレクトリ参照に対応するサポートを提供します。 | UseStaticFiles, UseFileServer |
WebSocket | WebSocket プロトコルを有効にします。 | UseWebSockets |
以下のセクションでは、要求処理、すなわちルーティング、パラメーター バインディング、応答について説明します。
構成された WebApplication
では、Map{Verb}
と MapMethods をサポートします。ここで {Verb}
は、Get
、Post
、Put
または Delete
などのキャメル ケースの HTTP メソッドです。
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();
これらのメソッドに渡される Delegate 引数は、"ルート ハンドラー" と呼ばれます。
ルート ハンドラーは、ルートが一致する場合に実行されるメソッドです。 ルート ハンドラーには、ラムダ式、ローカル関数、インスタンス メソッド、静的メソッドを指定できます。 ルート ハンドラーは同期でも非同期でもかまいません。
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";
}
}
最小 API は、Program.cs
に配置する必要はありません。
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
この記事で後述する「ルート グループ」も参照してください。
エンドポイントへの 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 route is /hello
エンドポイントから /
を表示します。
注: エンドポイント名では大文字と小文字が区別されます。
エンドポイント名:
ルート パラメーターは、ルート パターン定義の一部として捕捉できます。
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 The user id is 3 and book id is 7
にから /users/3/books/7
が返されます。
ルート ハンドラーは、捕捉するパラメーターを宣言できます。 キャプチャするように宣言されたパラメーターを持つルートに対して要求が実行されると、パラメーターが解析され、ハンドラーに渡されます。 これにより、タイプ セーフな方法で簡単に値を捕捉できるようになります。 上記のコードでは、userId
と bookId
は両方とも 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 拡張メソッドは、共通のプレフィックスを持つエンドポイントのグループを整理するのに役立ちます。 これにより、繰り返しのコードを減らし、RequireAuthorizationを追加する WithMetadata や のようなメソッドを 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;
}
このシナリオでは、Location
結果の 201 Created
ヘッダーに相対アドレスを使用できます。
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 メソッドとパラメーターの型に基づいて推測できます。
サポートされているバインディング ソース:
次の 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 メソッド GET
、HEAD
、OPTIONS
、DELETE
は、本文から暗黙的にバインドしません。 これらの 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" のヘッダー |
[FromForm]
属性によってフォーム値がバインドされます。
app.MapPost("/todos", async ([FromForm] string name,
[FromForm] Visibility visibility, IFormFile? attachment, TodoDb db) =>
{
var todo = new Todo
{
Name = name,
Visibility = visibility
};
if (attachment is not null)
{
var attachmentName = Path.GetRandomFileName();
using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
await attachment.CopyToAsync(stream);
}
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Ok();
});
// Remaining code removed for brevity.
別の方法には、[AsParameters]
で注釈がつけられたプロパティを持つカスタム型で [FromForm]
属性を使用する方法があります。 たとえば、次のコードによって、フォーム値から NewTodoRequest
レコード構造体のプロパティへのバインドが行われます。
app.MapPost("/ap/todos", async ([AsParameters] NewTodoRequest request, TodoDb db) =>
{
var todo = new Todo
{
Name = request.Name,
Visibility = request.Visibility
};
if (request.Attachment is not null)
{
var attachmentName = Path.GetRandomFileName();
using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
await request.Attachment.CopyToAsync(stream);
todo.Attachment = attachmentName;
}
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Ok();
});
// Remaining code removed for brevity.
public record struct NewTodoRequest([FromForm] string Name,
[FromForm] Visibility Visibility, IFormFile? Attachment);
詳細については、この記事で後述する AsParameters に関するセクションを参照してください。
完全なサンプル コードは、AspNetCore.Docs.Samples リポジトリにあります。
複雑なフォームのバインドは、IFormFile を使用する IFormFileCollection と [FromForm]
使用してサポートされます。
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
// Generate a form with an anti-forgery token and an /upload endpoint.
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
var html = MyUtils.GenerateHtmlForm(token.FormFieldName, token.RequestToken!);
return Results.Content(html, "text/html");
});
app.MapPost("/upload", async Task<Results<Ok<string>, BadRequest<string>>>
([FromForm] FileUploadForm fileUploadForm, HttpContext context,
IAntiforgery antiforgery) =>
{
await MyUtils.SaveFileWithName(fileUploadForm.FileDocument!,
fileUploadForm.Name!, app.Environment.ContentRootPath);
return TypedResults.Ok($"Your file with the description:" +
$" {fileUploadForm.Description} has been uploaded successfully");
});
app.Run();
[FromForm]
を使用して要求にバインドされたパラメーターには、偽造防止トークンが含まれます。 偽造防止トークンは、要求が処理されたときに検証されます。 詳細については、「最小限の API を使用した偽造防止」を参照してください。
詳細については、「最小限の API でのフォーム バインド」を参照してください。
完全なサンプル コードは、AspNetCore.Docs.Samples リポジトリにあります。
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"));
HttpRequest と HttpResponse: 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
としてバインドできます。
たとえば、データは 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();
Stream
は HttpRequest.Body
と同じオブジェクトです。Stream
と PipeReader
は使用できません。次のコードでは、IFormFile と IFormFileCollection を使用して、ファイルをアップロードしています。
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 ヘッダーを使用した認証されたファイルのアップロード要求がサポートされています。
IFormCollection、IFormFile、IFormFileCollection を使ったフォームベースのパラメーターからのバインディングがサポートされています。 Swagger UI との統合をサポートするために、フォーム パラメーターに対して OpenAPI メタデータが推論されます。
次のコードは、IFormFile
型から推論されたバインディングを使ってファイルをアップロードします。
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
Directory.CreateDirectory(directoryPath);
return Path.Combine(directoryPath, fileName);
}
async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
var filePath = GetOrCreateFilePath(fileSaveName);
await using var fileStream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(fileStream);
}
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
var html = $"""
<html>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
<input type="file" name="file" placeholder="Upload an image..." accept=".jpg,
.jpeg, .png" />
<input type="submit" />
</form>
</body>
</html>
""";
return Results.Content(html, "text/html");
});
app.MapPost("/upload", async Task<Results<Ok<string>,
BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
await UploadFileWithName(file, fileSaveName);
return TypedResults.Ok("File uploaded successfully!");
});
app.Run();
警告: フォームを実装するときは、アプリでクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃を防ぐ必要があります。 先ほどのコードでは、IAntiforgery サービスを使って、偽造防止トークンを生成して検証することで XSRF 攻撃を防いでいます。
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
Directory.CreateDirectory(directoryPath);
return Path.Combine(directoryPath, fileName);
}
async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
var filePath = GetOrCreateFilePath(fileSaveName);
await using var fileStream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(fileStream);
}
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
var html = $"""
<html>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
<input type="file" name="file" placeholder="Upload an image..." accept=".jpg,
.jpeg, .png" />
<input type="submit" />
</form>
</body>
</html>
""";
return Results.Content(html, "text/html");
});
app.MapPost("/upload", async Task<Results<Ok<string>,
BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
await UploadFileWithName(file, fileSaveName);
return TypedResults.Ok("File uploaded successfully!");
});
app.Run();
XSRF 攻撃の詳細については、「Minimal API を使用した偽造防止」を参照してください
詳細については、「最小限の API でのフォーム バインド」を参照してください。
バインディングは、次の場合にサポートされています。
Todo
または Project
など)このコードには、次の項目が示されています。
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
var html = $"""
<html><body>
<form action="/todo" method="POST" enctype="multipart/form-data">
<input name="{token.FormFieldName}"
type="hidden" value="{token.RequestToken}" />
<input type="text" name="name" />
<input type="date" name="dueDate" />
<input type="checkbox" name="isCompleted" value="true" />
<input type="submit" />
<input name="isCompleted" type="hidden" value="false" />
</form>
</body></html>
""";
return Results.Content(html, "text/html");
});
app.MapPost("/todo", async Task<Results<Ok<Todo>, BadRequest<string>>>
([FromForm] Todo todo, HttpContext context, IAntiforgery antiforgery) =>
{
try
{
await antiforgery.ValidateRequestAsync(context);
return TypedResults.Ok(todo);
}
catch (AntiforgeryValidationException e)
{
return TypedResults.BadRequest("Invalid antiforgery token");
}
});
app.Run();
class Todo
{
public string Name { get; set; } = string.Empty;
public bool IsCompleted { get; set; } = false;
public DateTime DueDate { get; set; } = DateTime.Now.Add(TimeSpan.FromDays(1));
}
上のコードでは以下の操作が行われます。
[FromForm]
。isCompleted
という名前の追加の非表示入力と、false
の値が表示されます。 フォームの送信時に isCompleted
チェック ボックスをオンにすると、値 true
と false
の両方が値として送信されます。 チェックボックスをオフにすると、非表示の入力値 false
のみが送信されます。 ASP.NET Core モデルバインド プロセスは、bool
値にバインドするときに最初の値のみを読み取ります。これにより、チェックボックスがオンの場合は true
になり、チェックボックスがオフの場合は false
になります。前のエンドポイントに送信されたフォーム データの例を次に示します。
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
次のコードは、クエリ文字列をプリミティブ型の配列、文字列配列、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);
});
HttpRepl
などのツールを使って、次のデータを上記のエンドポイントに渡します。
[
{
"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
にバインドし、一致する Todo
値を持つ Id
項目を返します。
// 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();
});
注意
クエリ文字列から string[]
をバインドするとき、一致するクエリ文字列がないと null 値ではなく空の配列になります。
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
エンドポイントでは、上記の struct
を AsParameters 属性と共に使用します。
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);
struct
を AsParameters
と共に使うと、record
型を使うよりもパフォーマンスが向上します。
完全なサンプル コードは AspNetCore.Docs.Samples リポジトリにあります。
パラメーター バインドは、2 つの方法でカスタマイズできます。
TryParse
メソッドを追加することにより、カスタムの型をバインドします。BindAsync
メソッドを実装することにより、バインディング プロセスを制御します。TryParse
には 2 つの API があります。
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
次のコードは、URI Point: 12.3, 10.1
に対して /map?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
には次の API があります。
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
次のコードは、URI SortBy:xyz, SortDirection:Desc, CurrentPage:99
に対して /products?SortBy=xyz&SortDir=Desc&Page=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}.TryParse は false を返します。 |
はい | ルート/クエリ/ヘッダー | 400 |
{ParameterType}.BindAsync は null を返します。 |
はい | custom | 400 |
{ParameterType}.BindAsync がスローされる |
問題ありません | custom | 500 |
JSON 本文を逆シリアル化できない | 問題ありません | body | 400 |
コンテンツの型が正しくない (application/json でない) |
問題ありません | body | 415 |
パラメーターからバインディング ソースを決定するルールは次の通りです。
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromForm]
[FromServices]
[AsParameters]
HttpContext
HttpRequest
(HttpContext.Request
)HttpResponse
(HttpContext.Response
)ClaimsPrincipal
(HttpContext.User
)CancellationToken
(HttpContext.RequestAborted
)IFormCollection
(HttpContext.Request.Form
)IFormFileCollection
(HttpContext.Request.Form.Files
)IFormFile
(HttpContext.Request.Form.Files[paramName]
)Stream
(HttpContext.Request.Body
)PipeReader
(HttpContext.Request.BodyReader
)BindAsync
メソッドがある。TryParse
メソッドがある。
app.Map("/todo/{id}", (int id) => {});
などのルート テンプレートにある場合、ルートからバインドされる。ボディ バインド ソースでは、System.Text.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
のインクルードを行うことができます。
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();
上記のコードでは次の操作が行われます。
ルート ハンドラーは、次の型の戻り値をサポートしています。
IResult
ベース - これには Task<IResult>
と ValueTask<IResult>
が含まれますstring
- これには Task<string>
と ValueTask<string>
が含まれます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");
app.MapGet("/hello", () => new { Message = "Hello World" });
次のコードは、TypedResults を返します。
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults
を返すより、Results を返すことをお勧めします。 詳しくは、「TypedResults と Results」をご覧ください。
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/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"));
共通の結果ヘルパーは、Results と TypedResults 静的クラスに含まれます。 TypedResults
を返すより、Results
を返すことをお勧めします。 詳しくは、「TypedResults と Results」をご覧ください。
応答ヘッダーを変更するには、HttpResponse
オブジェクトを使用します。
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
アプリケーションは、カスタムの 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
クラスを使います。 たとえば、応答状態コードを設定したり、別の 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 は、[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) を有効にする」を参照してください
ValidateScopes と ValidateOnBuild は、開発環境では既定で有効になっていますが、他の環境では無効になっています。
ValidateOnBuild
が true
の場合、DI コンテナーではビルド時にサービス構成を検証します。 サービス構成が無効な場合、サービスが要求されたときに実行時ではなく、アプリの起動時にビルドが失敗します。
ValidateScopes
が true
の場合、DI コンテナーでは、スコープ付きサービスがルート スコープから解決されていないことを検証します。 ルート スコープからスコープ付きサービスを解決すると、サービスが要求のスコープよりも長くメモリに保持されるため、メモリ リークが発生する可能性があります。
ValidateScopes
と ValidateOnBuild
は、パフォーマンス上の理由から、開発以外のモードでは既定では false です。
次のコードは、ValidateScopes
が開発モードでは既定で有効になっているが、リリース モードでは無効になっていることを示しています。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
// Intentionally getting service provider from app, not from the request
// This causes an exception from attempting to resolve a scoped service
// outside of a scope.
// Throws System.InvalidOperationException:
// 'Cannot resolve scoped service 'MyScopedService' from root provider.'
var service = app.Services.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved");
});
app.Run();
public class MyScopedService { }
次のコードは、ValidateOnBuild
が開発モードでは既定で有効になっているが、リリース モードでは無効になっていることを示しています。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();
// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
var service = context.RequestServices.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved correctly!");
});
app.Run();
public class MyScopedService { }
public class AnotherService
{
public AnotherService(BrokenService brokenService) { }
}
public class BrokenService { }
次のコードを使用すると、ValidateScopes
では ValidateOnBuild
と Development
は無効になります。
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
// Doesn't detect the validation problems because ValidateScopes is false.
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
options.ValidateOnBuild = false;
});
}
このドキュメントでは、
Minimal API には次が含まれます。
次のコードが 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 クラスの新しいインスタンスを初期化します。
WebApplication
では、特定の条件に応じて、Minimal API applications
に次のミドルウェアが自動的に追加されます。
UseDeveloperExceptionPage
は、HostingEnvironment
が "Development"
である場合、最初に追加されます。UseRouting
は、ユーザー コードによって UseRouting
がまだ呼び出されておらず、エンドポイントが構成されている (app.MapGet
など) 場合、2 番目に追加されます。UseEndpoints
は、エンドポイントが構成されている場合、ミドルウェア パイプラインの最後に追加されます。UseAuthentication
は、ユーザー コードによって UseRouting
がまだ呼び出されておらず、サービス プロバイダーで UseAuthentication
が検出できる場合、IAuthenticationSchemeProvider
の直後に追加されます。 IAuthenticationSchemeProvider
は、AddAuthentication
を使用するときに既定で追加され、サービスは IServiceProviderIsService
を使用して検出されます。UseAuthorization
は、ユーザー コードによって UseAuthorization
がまだ呼び出されておらず、サービス プロバイダーで IAuthorizationHandlerProvider
が検出できる場合、次に追加されます。 IAuthorizationHandlerProvider
は、AddAuthorization
を使用するときに既定で追加され、サービスは IServiceProviderIsService
を使用して検出されます。UseRouting
と UseEndpoints
の間に追加されます。次のコードは、アプリに追加される自動ミドルウェアがどのようなものを生成するかを示しています。
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
場合によっては、既定のミドルウェア構成がアプリに対して正しくなく、変更が必要になることがあります。 たとえば、UseCors は UseAuthentication と UseAuthorization の前に呼び出される必要があります。 UseAuthentication
を呼び出す場合、アプリでは UseAuthorization
と UseCors
を呼び出す必要があります。
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
ルートの照合が発生する前にミドルウェアを実行する必要がある場合は、UseRouting を呼び出す必要があり、ミドルウェアは UseRouting
への呼び出しの前に配置する必要があります。 この場合、UseEndpoints は前述のように自動的に追加されるため、必要ありません。
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
ターミナル ミドルウェアを追加する場合:
UseEndpoints
の後に追加される必要があります。UseRouting
と UseEndpoints
を呼び出す必要があります。app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ターミナル ミドルウェアは、いずれのエンドポイントによっても要求が処理されない場合に実行されるミドルウェアです。
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
ポートに応答します。
次のコードの場合、アプリは 3000
と 4000
ポートに応答します。
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"
Kestrel ファイルで appsettings.json
エンドポイントも構成されている場合、 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=http://localhost:3000
ASPNETCORE_URLS
は複数の URL をサポートしています。
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
環境の使用の詳細については、「ASP.NET Core で複数の環境を使用する」を参照してください
次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
上記のサンプルでは、ASPNETCORE_URLS
を使用できます
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
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 開発証明書を信頼する) を参照してください。
次のセクションは、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();
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);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
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 コンテナーからサービスを取得する方法を示しています。
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 を使用するサンプル コードが含まれています。
次のコードは、コンテンツ ルート、アプリケーション名、環境を設定します。
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
次のコードは構成から HelloKey
を読み取り、/
エンドポイントの値を表示します。 構成値が null 値の場合、message
には "Hello" が代入されます。
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
読み取る構成ソースの完全な一覧については、「ASP.NET Core の構成」の「既定の構成」を参照してください
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 の既存の拡張メソッドは、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 の拡張メソッドは、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 ルートは、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();
次の例では、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();
次の表に示されているのは、Minimal API でよく使用されるミドルウェアの一部です。
ミドルウェア | 説明 | API |
---|---|---|
認証 | 認証のサポートを提供します。 | UseAuthentication |
承認 | 承認のサポートを提供します。 | UseAuthorization |
CORS | クロス オリジン リソース共有を構成します。 | UseCors |
例外ハンドラー | ミドルウェア パイプラインがスローする例外をグローバルに処理します。 | UseExceptionHandler |
転送されるヘッダー | プロキシされたヘッダーを現在の要求に転送します。 | UseForwardedHeaders |
HTTPS リダイレクト | すべての HTTP 要求を HTTPS にリダイレクトします。 | UseHttpsRedirection |
HTTP Strict Transport Security (HSTS) | 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 | UseHsts |
要求ログ | HTTP 要求と応答のログのサポートを提供します。 | UseHttpLogging |
要求のタイムアウト | グローバルな既定値として、およびエンドポイントごとに、要求のタイムアウトを構成するサポートを提供します。 | UseRequestTimeouts |
W3C 要求ログ | W3C 形式の HTTP 要求と応答のログのサポートを提供します。 | UseW3CLogging |
応答キャッシュ | 応答のキャッシュのサポートを提供します。 | UseResponseCaching |
応答圧縮 | 応答の圧縮のサポートを提供します。 | UseResponseCompression |
セッション | ユーザー セッションの管理のサポートを提供します。 | UseSession |
静的ファイル | 静的ファイルとディレクトリ参照に対応するサポートを提供します。 | UseStaticFiles, UseFileServer |
WebSocket | WebSocket プロトコルを有効にします。 | UseWebSockets |
以下のセクションでは、要求処理、すなわちルーティング、パラメーター バインディング、応答について説明します。
構成された WebApplication
では、Map{Verb}
と MapMethods をサポートします。ここで {Verb}
は、Get
、Post
、Put
または Delete
などのキャメル ケースの HTTP メソッドです。
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();
これらのメソッドに渡される Delegate 引数は、"ルート ハンドラー" と呼ばれます。
ルート ハンドラーは、ルートが一致する場合に実行されるメソッドです。 ルート ハンドラーには、ラムダ式、ローカル関数、インスタンス メソッド、静的メソッドを指定できます。 ルート ハンドラーは同期でも非同期でもかまいません。
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";
}
}
最小 API は、Program.cs
に配置する必要はありません。
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
この記事で後述する「ルート グループ」も参照してください。
エンドポイントへの 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 route is /hello
エンドポイントから /
を表示します。
注: エンドポイント名では大文字と小文字が区別されます。
エンドポイント名:
ルート パラメーターは、ルート パターン定義の一部として捕捉できます。
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 The user id is 3 and book id is 7
にから /users/3/books/7
が返されます。
ルート ハンドラーは、捕捉するパラメーターを宣言できます。 キャプチャするように宣言されたパラメーターを持つルートに対して要求が実行されると、パラメーターが解析され、ハンドラーに渡されます。 これにより、タイプ セーフな方法で簡単に値を捕捉できるようになります。 上記のコードでは、userId
と bookId
は両方とも 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 拡張メソッドは、共通のプレフィックスを持つエンドポイントのグループを整理するのに役立ちます。 これにより、繰り返しのコードを減らし、RequireAuthorizationを追加する WithMetadata や のようなメソッドを 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;
}
このシナリオでは、Location
結果の 201 Created
ヘッダーに相対アドレスを使用できます。
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 メソッドとパラメーターの型に基づいて推測できます。
サポートされているバインディング ソース:
フォーム値からのバインドは、.NET 6 と 7 ではネイティブにサポートされて "いません"。
次の 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 メソッド GET
、HEAD
、OPTIONS
、DELETE
は、本文から暗黙的にバインドしません。 これらの 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 6 と 7 ではネイティブにサポートされて "いません"。
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"));
HttpRequest と HttpResponse: 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
としてバインドできます。
たとえば、データは 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();
Stream
は HttpRequest.Body
と同じオブジェクトです。Stream
と PipeReader
は使用できません。次のコードでは、IFormFile と IFormFileCollection を使用して、ファイルをアップロードしています。
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 ヘッダーを使用した認証されたファイルのアップロード要求がサポートされています。
ASP.NET Core 7.0 には、偽造防止のサポートが組み込まれていません。 偽造防止は、ASP.NET Core 8.0 以降でのみ使用できます。 ただし、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);
});
HttpRepl
などの API テスト ツールを使って、次のデータを上記のエンドポイントに渡します。
[
{
"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
にバインドし、一致する Todo
値を持つ Id
項目を返します。
// 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();
});
注意
クエリ文字列から string[]
をバインドするとき、一致するクエリ文字列がないと null 値ではなく空の配列になります。
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
エンドポイントでは、上記の struct
を AsParameters 属性と共に使用します。
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);
struct
を AsParameters
と共に使うと、record
型を使うよりもパフォーマンスが向上します。
完全なサンプル コードは AspNetCore.Docs.Samples リポジトリにあります。
パラメーター バインドは、2 つの方法でカスタマイズできます。
TryParse
メソッドを追加することにより、カスタムの型をバインドします。BindAsync
メソッドを実装することにより、バインディング プロセスを制御します。TryParse
には 2 つの API があります。
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
次のコードは、URI Point: 12.3, 10.1
に対して /map?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
には次の API があります。
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
次のコードは、URI SortBy:xyz, SortDirection:Desc, CurrentPage:99
に対して /products?SortBy=xyz&SortDir=Desc&Page=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}.TryParse は false を返します。 |
はい | ルート/クエリ/ヘッダー | 400 |
{ParameterType}.BindAsync は null を返します。 |
はい | custom | 400 |
{ParameterType}.BindAsync がスローされる |
どちらでもよい | custom | 500 |
JSON 本文を逆シリアル化できない | どちらでもよい | body | 400 |
コンテンツの型が正しくない (application/json でない) |
どちらでもよい | body | 415 |
パラメーターからバインディング ソースを決定するルールは次の通りです。
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromServices]
[AsParameters]
HttpContext
HttpRequest
(HttpContext.Request
)HttpResponse
(HttpContext.Response
)ClaimsPrincipal
(HttpContext.User
)CancellationToken
(HttpContext.RequestAborted
)IFormFileCollection
(HttpContext.Request.Form.Files
)IFormFile
(HttpContext.Request.Form.Files[paramName]
)Stream
(HttpContext.Request.Body
)PipeReader
(HttpContext.Request.BodyReader
)BindAsync
メソッドがある。TryParse
メソッドがある。
app.Map("/todo/{id}", (int id) => {});
では、id
はルートからバインドされます。ボディ バインド ソースでは、System.Text.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
のインクルードを行うことができます。
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();
上記のコードでは次の操作が行われます。
ルート ハンドラーは、次の型の戻り値をサポートしています。
IResult
ベース - これには Task<IResult>
と ValueTask<IResult>
が含まれますstring
- これには Task<string>
と ValueTask<string>
が含まれます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");
app.MapGet("/hello", () => new { Message = "Hello World" });
次のコードは、TypedResults を返します。
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults
を返すより、Results を返すことをお勧めします。 詳しくは、「TypedResults と Results」をご覧ください。
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/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"));
共通の結果ヘルパーは、Results と TypedResults 静的クラスに含まれます。 TypedResults
を返すより、Results
を返すことをお勧めします。 詳しくは、「TypedResults と Results」をご覧ください。
アプリケーションは、カスタムの 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
クラスを使います。 たとえば、応答状態コードを設定したり、別の 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 は、[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 には次が含まれます。
次のコードが 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
ポートに応答します。
次のコードの場合、アプリは 3000
と 4000
ポートに応答します。
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"
Kestrel ファイルで appsettings.json
エンドポイントも構成されている場合、 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=http://localhost:3000
ASPNETCORE_URLS
は複数の URL をサポートしています。
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
次のサンプルは、すべてのインターフェイスでリッスンする方法を示しています
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
上記のサンプルでは、ASPNETCORE_URLS
を使用できます
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
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 開発証明書を信頼する) を参照してください。
次のセクションは、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();
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 コンテナーからサービスを取得する方法を示しています。
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 を使用するサンプル コードが含まれています。
次のコードは、コンテンツ ルート、アプリケーション名、環境を設定します。
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 の既存の拡張メソッドは、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 の拡張メソッドは、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 ルートは、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();
次の例では、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();
次の表に示されているのは、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 |
以下のセクションでは、ルーティング、パラメーター バインディング、応答について説明します。
構成された WebApplication
は Map{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
エンドポイントから /
を表示します。
注: エンドポイント名では大文字と小文字が区別されます。
エンドポイント名:
ルート パラメーターは、ルート パターン定義の一部として捕捉できます。
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 The user id is 3 and book id is 7
にから /users/3/books/7
が返されます。
ルート ハンドラーは、捕捉するパラメーターを宣言できます。 捕捉するように宣言されたパラメーターを持つルートに対して要求が実行されると、パラメーターが解析され、ハンドラーに渡されます。 これにより、タイプ セーフな方法で簡単に値を捕捉できるようになります。 上記のコードでは、userId
と bookId
は両方とも 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 メソッドとパラメーターの型に基づいて推測できます。
サポートされているバインディング ソース:
注意
フォーム値からのバインドは、.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 メソッド GET
、HEAD
、OPTIONS
、DELETE
は、本文から暗黙的にバインドしません。 これらの 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"));
HttpRequest と HttpResponse: 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 つの方法でカスタマイズできます。
TryParse
メソッドを追加することにより、カスタムの型をバインドします。BindAsync
メソッドを実装することにより、バインディング プロセスを制御します。TryParse
には 2 つの API があります。
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
次のコードは、URI Point: 12.3, 10.1
に対して /map?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
には次の API があります。
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
次のコードは、URI SortBy:xyz, SortDirection:Desc, CurrentPage:99
に対して /products?SortBy=xyz&SortDir=Desc&Page=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}.TryParse は false を返します。 |
はい | ルート/クエリ/ヘッダー | 400 |
{ParameterType}.BindAsync は null を返します。 |
はい | custom | 400 |
{ParameterType}.BindAsync がスローされる |
どちらでもよい | custom | 500 |
JSON 本文を逆シリアル化できない | どちらでもよい | body | 400 |
コンテンツの型が正しくない (application/json でない) |
どちらでもよい | body | 415 |
パラメーターからバインディング ソースを決定するルールは次の通りです。
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromServices]
BindAsync
メソッドがある。TryParse
メソッドがある。
app.Map("/todo/{id}", (int id) => {});
では、id
はルートからバインドされます。本文のバインディング ソースは 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;
}
上記のコードでは次の操作が行われます。
{
"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();
上記のコードでは次の操作が行われます。
ルート ハンドラーは、次の型の戻り値をサポートしています。
IResult
ベース - これには Task<IResult>
と ValueTask<IResult>
が含まれますstring
- これには Task<string>
と ValueTask<string>
が含まれますT
(その他の型) - これには Task<T>
と ValueTask<T>
が含まれます戻り値 | 動作 | Content-Type |
---|---|---|
IResult |
フレームワークは IResult.ExecuteAsync を呼び出す | IResult の実装によって決まる |
string |
フレームワークは、文字列を直接応答に書き込む | text/plain |
T (その他の型) |
フレームワークは応答を JSON にシリアル化する | application/json |
app.MapGet("/hello", () => "Hello World");
app.MapGet("/hello", () => new { Message = "Hello World" });
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
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://contoso/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 は、[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) を有効にする」を参照してください
ASP.NET Core に関するフィードバック
ASP.NET Core はオープンソース プロジェクトです。 フィードバックを提供するにはリンクを選択します。
トレーニング
モジュール
minimal API、ASP.NET Core、.NET を使用して Web API をビルドする - Training
.NET を使用して Web API をビルドする方法について説明します。 また、読み取りと書き込みの両方を処理するさまざまなルートを設定する方法についても説明します。