ヒント
このコンテンツは、ASP.NET Core と Azure を使用した最新の Web アプリケーションの設計に関する電子ブックからの抜粋です。 .NET Docs またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できます。
はじめから完璧にすることが重要ではありません。 しかし、最後に正しく理解していることは極めて重要です。" - Andrew Hunt と David Thomas
ASP.NET Core は、最新のクラウド最適化 Web アプリケーションを構築するためのクロスプラットフォームのオープンソース フレームワークです。 ASP.NET Core アプリは軽量でモジュール式であり、依存関係の挿入が組み込まれており、テスト容易性と保守容易性が向上します。 ビュー ベースのアプリに加えて最新の Web API の構築をサポートする MVC と組み合わせることで、ASP.NET Core はエンタープライズ Web アプリケーションを構築するための強力なフレームワークです。
MVC と Razor Pages
ASP.NET Core MVC には、Web ベースの API とアプリの構築に役立つ多くの機能が用意されています。 MVC という用語は、ユーザー要求に応答する責任を複数の部分に分割する UI パターンである "Model-View-Controller" を意味します。 このパターンに従うだけでなく、ASP.NET Core アプリに Razor Pages として機能を実装することもできます。
Razor Pages は ASP.NET Core MVC に組み込まれており、ルーティング、モデル バインド、フィルター、承認などに同じ機能を使用します。ただし、コントローラー、モデル、ビューなどに個別のフォルダーとファイルを配置する代わりに、属性ベースのルーティングを使用する代わりに、Razor Pages は 1 つのフォルダー ("/Pages") に配置され、このフォルダー内の相対位置に基づいてルーティングされ、コントローラー アクションではなくハンドラーで要求を処理します。 その結果、Razor Pages を使用する場合、必要なすべてのファイルとクラスは通常、Web プロジェクト全体に分散されずに併置されます。
MVC、Razor Pages、および関連するパターンが eShopOnWeb サンプル アプリケーションでどのように適用されるかについて説明します。
新しい ASP.NET Core アプリを作成するときは、ビルドするアプリの種類に関する計画を念頭に置く必要があります。 新しいプロジェクトを作成するとき、IDE で、または dotnet new CLI コマンドを使用して、いくつかのテンプレートから選択します。 最も一般的なプロジェクト テンプレートは、空、Web API、Web アプリ、Web アプリ (Model-View-Controller) です。 この決定は、最初にプロジェクトを作成するときにのみ行うことができますが、取り消し不可能な決定ではありません。 Web API プロジェクトでは、標準の ModelView-Controller コントローラーが使用されます。既定ではビューがありません。 同様に、既定の Web アプリ テンプレートでは Razor Pages が使用されるため、Views フォルダーも使用されません。 ビュー ベースの動作をサポートするために、後でこれらのプロジェクトに Views フォルダーを追加できます。 Web API と Model-View-Controller プロジェクトには既定では Pages フォルダーは含まれませんが、Razor Pages ベースの動作をサポートするために後で追加できます。 これらの 3 つのテンプレートは、データ (Web API)、ページベース、ビューベースの 3 種類の既定のユーザー操作をサポートしていると考えることができます。 ただし、必要に応じて、1 つのプロジェクト内でこれらのテンプレートの一部またはすべてを組み合わせることができます。
Razor Pages とは
Razor Pages は、Visual Studio の新しい Web アプリケーションの既定のアプローチです。 Razor Pages では、SPA 以外のフォームなど、ページ ベースのアプリケーション機能を簡単に構築できます。 コントローラーとビューを使用すると、アプリケーションには、さまざまな依存関係を処理し、モデルを表示し、多くの異なるビューを返す非常に大きなコントローラーが存在することが一般的でした。 その結果、複雑さが増し、多くの場合、単一責任原則またはオープン/クローズ原則に効果的に従わないコントローラーが発生しました。 Razor Pages では、特定の論理 "ページ" のサーバー側ロジックを、その Razor マークアップを使用して Web アプリケーションにカプセル化することで、この問題に対処します。 サーバー側のロジックを持たない Razor ページは、Razor ファイル ("Index.cshtml" など) のみで構成できます。 ただし、ほとんどの単純な Razor ページには、ページ モデル クラスが関連付けられます。慣例により、拡張子が ".cs" の Razor ファイルと同じ名前が付けられます (例: "Index.cshtml.cs")。
Razor ページのページ モデルは、MVC コントローラーとビューモデルの役割を組み合わせたものです。 コントローラー アクション メソッドを使用して要求を処理する代わりに、"OnGet()" などのページ モデル ハンドラーが実行され、関連付けられているページが既定でレンダリングされます。 Razor Pages を使用すると、ASP.NET Core アプリで個々のページを作成するプロセスが簡素化される一方で、ASP.NET Core MVC のすべてのアーキテクチャ機能が提供されます。 これらは、新しいページベースの機能に適した既定の選択肢です。
MVC を使用する場合
Web API を構築する場合は、Razor Pages を使用するよりも MVC パターンの方が理にかなっています。 プロジェクトが Web API エンドポイントのみを公開する場合は、Web API プロジェクト テンプレートから始めるのが理想的です。 それ以外の場合は、コントローラーと関連する API エンドポイントを任意の ASP.NET Core アプリに簡単に追加できます。 既存のアプリケーションを MVC 5 以前から ASP.NET ASP.NET Core MVC に移行し、最小限の労力で移行する場合は、ビューベースの MVC アプローチを使用します。 最初の移行を行ったら、新機能に Razor Pages を採用するのが理にかなっているか、それとも卸売移行として採用するのが理にかなっているかを評価できます。 .NET 4.x アプリを .NET 8 に移植する方法の詳細については、「 既存の ASP.NET アプリを ASP.NET Core 電子ブックに移植する」を参照してください。
Razor Pages ビューまたは MVC ビューを使用して Web アプリをビルドする場合でも、アプリのパフォーマンスは同様であり、依存関係の挿入、フィルター、モデル バインド、検証などのサポートが含まれます。
要求を応答にマッピングする
ASP.NET Core アプリケーションの中心的な機能は、受信したリクエストを送信するレスポンスにマッピングすることです。 低レベルでは、このマッピングはミドルウェアで行われ、単純な ASP.NET Core アプリとマイクロサービスはカスタム ミドルウェアのみで構成される場合があります。 ASP.NET Core MVC を使用する場合は、 ルート、 コントローラー、 アクションの観点から考えて、やや高いレベルで作業できます。 各受信要求はアプリケーションのルーティング テーブルと比較され、一致するルートが見つかった場合は、関連するアクション メソッド (コントローラーに属) が呼び出されて要求を処理します。 一致するルートが見つからない場合は、エラー ハンドラー (この場合は NotFound の結果を返す) が呼び出されます。
ASP.NET Core MVC アプリでは、従来のルート、属性ルート、またはその両方を使用できます。 従来のルートはコードで定義され、次の例のような構文を使用してルーティング規則を指定 します 。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
この例では、"default" という名前のルートがルーティング テーブルに追加されています。
controller、action、idのプレースホルダーを含むルート テンプレートを定義します。
controllerプレースホルダーとaction プレースホルダーには既定の指定 (それぞれHomeとIndex) があり、idプレースホルダーは省略可能です ("?" が適用されます)。 ここで定義されている規則では、要求の最初の部分はコントローラーの名前、2 番目の部分はアクションに対応する必要があり、必要に応じて 3 番目の部分は ID パラメーターを表します。 通常、従来のルートは、要求ミドルウェア パイプラインが構成 されているProgram.cs など、アプリケーションの 1 つの場所で定義されます。
属性ルートは、グローバルに指定されるのではなく、コントローラーとアクションに直接適用されます。 この方法には、特定の方法を見ているときに検出しやすくする利点がありますが、ルーティング情報がアプリケーション内の 1 か所に保持されないことを意味します。 属性ルートを使用すると、特定のアクションに対して複数のルートを簡単に指定したり、コントローラーとアクションの間でルートを結合したりできます。 例えば次が挙げられます。
[Route("Home")]
public class HomeController : Controller
{
[Route("")] // Combines to define the route template "Home"
[Route("Index")] // Combines to define route template "Home/Index"
[Route("/")] // Does not combine, defines the route template ""
public IActionResult Index() {}
}
ルートは [HttpGet] と同様の属性で指定できるため、個別の [ルート] 属性を追加する必要がなくなります。 属性ルートでは、次に示すように、トークンを使用してコントローラー名またはアクション名を繰り返す必要性を減らすこともできます。
[Route("[controller]")]
public class ProductsController : Controller
{
[Route("")] // Matches 'Products'
[Route("Index")] // Matches 'Products/Index'
public IActionResult Index() {}
}
Razor ページでは、属性ルーティングは使用されません。
@page ディレクティブの一部として、Razor ページに追加のルート テンプレート情報を指定できます。
@page "{id:int}"
前の例では、問題のページは、整数の id パラメーターを持つルートと一致します。 たとえば、のルートにある /Pages ページは、次のような要求に応答します。
/Products/123
特定の要求がルートに一致した後、アクション メソッドが呼び出される前に、ASP.NET Core MVC は要求に対して モデル バインド と モデル検証 を実行します。 モデル バインドは、呼び出されるアクション メソッドのパラメーターとして指定された .NET 型に受信 HTTP データを変換する役割を担います。 たとえば、アクション メソッドで int id パラメーターが必要な場合、モデル バインドは要求の一部として指定された値からこのパラメーターの指定を試みます。 これを行うために、モデル バインドでは、ポストされたフォーム内の値、ルート自体の値、およびクエリ文字列値が検索されます。
id値が見つかった場合、アクション メソッドに渡される前に整数に変換されます。
モデルをバインドした後、アクション メソッドを呼び出す前に、モデルの検証が行われます。 モデル検証では、モデルの種類に対して省略可能な属性が使用され、指定されたモデル オブジェクトが特定のデータ要件に準拠していることを確認するのに役立ちます。 特定の値は、必要に応じて指定することも、特定の長さまたは数値範囲に制限することもできます。検証属性が指定されているが、モデルが要件に準拠していない場合、ModelState.IsValid プロパティは false になり、失敗した検証規則のセットを使用して要求を行うクライアントに送信できます。
モデル検証を使用している場合は、無効なデータによってアプリが破損していないことを確認するために、状態変更コマンドを実行する前に、必ずモデルが有効であることを確認する必要があります。 フィルターを使用すると、すべてのアクションでこの検証のコードを追加する必要がなくなります。 ASP.NET Core MVC フィルターでは、要求のグループをインターセプトする方法が提供されるため、一般的なポリシーと横断的な懸念事項を対象に適用できます。 フィルターは、個々のアクション、コントローラー全体、またはアプリケーションのグローバルに適用できます。
Web API の場合、ASP.NET Core MVC は コンテンツ ネゴシエーションをサポートし、要求で応答の書式設定方法を指定できます。 要求で指定されたヘッダーに基づいて、データを返すアクションは、応答を XML、JSON、またはその他のサポートされている形式で書式設定します。 この機能により、異なるデータ形式要件を持つ複数のクライアントで同じ API を使用できます。
Web API プロジェクトでは、個々のコントローラー、基底コントローラー クラス、またはアセンブリ全体に適用できる [ApiController] 属性の使用を検討する必要があります。 この属性は自動モデル検証チェックを追加し、無効なモデルを持つアクションは、検証エラーの詳細を含む BadRequest を返します。 また、この属性では、すべてのアクションに、従来のルートを使用するのではなく、属性ルートが必要であり、エラーに応答してより詳細な ProblemDetails 情報が返されます。
コントローラーの制御を維持する
ページ ベースのアプリケーションの場合、Razor Pages はコントローラーが大きくなりすぎないようにする優れた作業を行います。 個々のページには、ハンドラー専用の独自のファイルとクラスが用意されています。 Razor Pages を導入する前は、多くのビュー中心のアプリケーションに、さまざまなアクションとビューを担当する大規模なコントローラー クラスが用意されています。 これらのクラスは、多くの責任と依存関係を持つように自然に成長し、保守が困難になります。 ビューベースのコントローラーのサイズが大きすぎる場合は、Razor Pages を使用するようにリファクタリングするか、メディエーターのようなパターンを導入することを検討してください。
メディエーター設計パターンは、クラス間の通信を可能にしながら、クラス間の結合を減らすために使用されます。 ASP.NET Core MVC アプリケーションでは、このパターンは、 ハンドラー を使用してアクション メソッドの処理を行うことで、コントローラーをより小さな部分に分割するために頻繁に使用されます。 一般的 な MediatR NuGet パッケージ は、多くの場合、これを実現するために使用されます。 通常、コントローラーにはさまざまなアクション メソッドが含まれており、それぞれに特定の依存関係が必要な場合があります。 任意のアクションで必要なすべての依存関係のセットをコントローラーのコンストラクターに渡す必要があります。 MediatR を使用する場合、コントローラーが通常持つ唯一の依存関係は、メディエーターのインスタンスです。 その後、各アクションはメディエーター インスタンスを使用してメッセージを送信し、ハンドラーによって処理されます。 ハンドラーは 1 つのアクションに固有であるため、そのアクションに必要な依存関係のみが必要です。 MediatR を使用するコントローラーの例を次に示します。
public class OrderController : Controller
{
private readonly IMediator _mediator;
public OrderController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> MyOrders()
{
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
return View(viewModel);
}
// other actions implemented similarly
}
MyOrders アクションでは、Send メッセージをGetMyOrdersする呼び出しは、次のクラスによって処理されます。
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
{
private readonly IOrderRepository _orderRepository;
public GetMyOrdersHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request, CancellationToken cancellationToken)
{
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
var orders = await _orderRepository.ListAsync(specification);
return orders.Select(o => new OrderViewModel
{
OrderDate = o.OrderDate,
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
{
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,
ProductName = oi.ItemOrdered.ProductName,
UnitPrice = oi.UnitPrice,
Units = oi.Units
}).ToList(),
OrderNumber = o.Id,
ShippingAddress = o.ShipToAddress,
Total = o.Total()
});
}
}
このアプローチの最終的な結果は、コントローラーがはるかに小さく、主にルーティングとモデル バインドに重点を置く一方で、個々のハンドラーが特定のエンドポイントで必要な特定のタスクを担当することです。 この方法は、ApiEndpoints NuGet パッケージを使用して MediatR なしでも実現できます。 ApiEndpoints NuGet パッケージは、Razor Pages がビュー ベースのコントローラーにもたらすのと同じ利点を API コントローラーに持ち込もうとします。
参照 – 要求を応答にマッピングする
- コントローラー アクションへのルーティング
https://learn.microsoft.com/aspnet/core/mvc/controllers/routing- モデル バインド
https://learn.microsoft.com/aspnet/core/mvc/models/model-binding- モデルの検証
https://learn.microsoft.com/aspnet/core/mvc/models/validation- フィルター
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- ApiController 属性
https://learn.microsoft.com/aspnet/core/web-api/
依存関係の取り扱い
ASP.NET Core には、 依存関係の挿入と呼ばれる手法が組み込まれており、内部的に使用されています。 依存関係の挿入は、アプリケーションのさまざまな部分間の疎結合を可能にする手法です。 疎結合が望ましいのは、アプリケーションの一部を分離しやすくし、テストや交換が可能になるためです。 また、アプリケーションの一部を変更すると、アプリケーション内の別の場所で予期しない影響を受ける可能性も低くなります。 依存関係の挿入は依存関係反転の原則に基づいており、多くの場合、オープン/クローズの原則を達成するための鍵となります。 アプリケーションによる依存関係の処理方法を評価するときは、静的な結び付きを持つコードのにおいに注意し、"new は接着剤である" という格言を思い出してください。
静的なクリングは、クラスが静的メソッドを呼び出すか、インフラストラクチャに副作用や依存関係がある静的プロパティにアクセスするときに発生します。 たとえば、静的メソッドを呼び出すメソッドがあり、そのメソッドがデータベースに書き込まれる場合、メソッドはデータベースに密結合されます。 そのデータベース呼び出しを中断するものはすべて、メソッドを中断します。 このようなメソッドのテストは、静的呼び出しをモックするために商用のモック ライブラリを必要とするか、テスト データベースを使用してのみテストできるため、悪名高い難しいテストです。 インフラストラクチャに依存しない静的呼び出し(特に完全にステートレスな呼び出し)は、呼び出しても問題なく、結合やテスト可能性に影響を与えません (コードを静的呼び出し自体に結合する以外)。
多くの開発者は静的なクリングとグローバル状態のリスクを理解していますが、直接インスタンス化を通じてコードを特定の実装に密に結合します。 "New is glue" は、new キーワードの使用に関する一般的な非難ではなく、この結合の概念を想起させるものです。 静的メソッド呼び出しと同様に、外部依存関係のない型の新しいインスタンスでは、通常、実装の詳細にコードを密に結合したり、テストを困難にしたりすることはありません。 ただし、クラスがインスタンス化されるたびに、その特定の場所の特定のインスタンスをハードコーディングするのが理にかなっているか、そのインスタンスを依存関係として要求する方が適切な設計になるかを、少し時間を取って検討してください。
依存関係を宣言する
ASP.NET Core は、メソッドとクラスが依存関係を宣言し、それらを引数として要求することを中心に構築されています。 ASP.NET アプリケーションは、通常、 Program.cs または Startup クラスで設定されます。
注
Program.cs でアプリを完全に構成することは、.NET 6 (以降) および Visual Studio 2022 (以降) アプリの既定のアプローチです。 この新しいアプローチを開始できるように、プロジェクト テンプレートが更新されました。 ASP.NET コア プロジェクトでは、必要に応じて、 Startup クラスを引き続き使用できます。
Program.cs でサービスを構成する
非常に単純なアプリの場合は、を使用してWebApplicationBuilder ファイル内で依存関係を直接結び付けることができます。 必要なすべてのサービスが追加されると、ビルダーを使用してアプリが作成されます。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
Startup.csでサービスを構成する
Startup.cs自体は、いくつかのポイントで依存関係の挿入をサポートするように構成されています。
Startup クラスを使用している場合は、コンストラクターを指定できます。また、次のように、それを介して依存関係を要求できます。
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
}
}
Startupクラスは、明示的な型要件がない点で興味深いです。 特殊な Startup 基底クラスから継承することも、特定のインターフェイスを実装することもありません。 コンストラクターを指定するか、指定しないか、コンストラクターに必要な数のパラメーターを指定できます。 アプリケーション用に構成した Web ホストが起動すると、 Startup クラスが呼び出され (使用するように指定した場合)、依存関係の挿入を使用して、 Startup クラスに必要な依存関係が設定されます。 もちろん、ASP.NET Core によって使用されるサービス コンテナーで構成されていないパラメーターを要求すると、例外が発生しますが、コンテナーが認識している依存関係に従っている限り、必要なものは何でも要求できます。
依存関係の挿入は、スタートアップ インスタンスを作成するときに、最初から ASP.NET Core アプリに組み込まれます。 Startup クラスでは停止しません。
Configure メソッドで依存関係を要求することもできます。
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
}
ConfigureServices メソッドは、この動作の例外です。 IServiceCollection型のパラメーターを 1 つだけ受け取る必要があります。 依存関係の挿入をサポートする必要はありません。一方では、サービス コンテナーにオブジェクトを追加し、もう一方では、 IServiceCollection パラメーターを使用して現在構成されているすべてのサービスにアクセスできるためです。 そのため、必要なサービスをパラメーターとして要求するか、StartupのIServiceCollectionを使用して、ConfigureServices クラスのすべての部分で、ASP.NET Core サービス コレクションで定義されている依存関係を操作できます。
注
Startup クラスで特定のサービスを使用できるようにする必要がある場合は、IWebHostBuilder呼び出し内でConfigureServicesとそのCreateDefaultBuilderメソッドを使用してサービスを構成できます。
Startup クラスは、コントローラーからミドルウェア、フィルター、独自のサービスまで、ASP.NET Core アプリケーションの他の部分を構成する方法のモデルです。 いずれの場合も、 明示的な依存関係の原則に従い、依存関係を直接作成するのではなく要求し、アプリケーション全体で依存関係の挿入を利用する必要があります。 実装を直接インスタンス化する場所と方法、特にインフラストラクチャで動作するサービスやオブジェクト、または副作用がある場合は注意してください。 特定の実装型をハードコーディングするよりも、アプリケーションコアで定義された抽象化を引数として渡して操作することを優先します。
アプリケーションの構成
モノリシック アプリケーションには通常、1 つのエントリ ポイントがあります。 ASP.NET Core Web アプリケーションの場合、エントリ ポイントは ASP.NET Core Web プロジェクトになります。 ただし、これは、ソリューションが 1 つのプロジェクトだけで構成される必要があることを意味するわけではありません。 懸念事項の分離に従うために、アプリケーションを異なる層に分割すると便利です。 レイヤーに分割したら、フォルダーを超えてプロジェクトを分離すると便利です。これは、カプセル化の向上に役立ちます。 ASP.NET Core アプリケーションでこれらの目標を達成するための最善のアプローチは、第 5 章で説明したクリーン アーキテクチャのバリエーションです。 このアプローチに従って、アプリケーションのソリューションは、UI、インフラストラクチャ、および ApplicationCore 用の個別のライブラリで構成されます。
これらのプロジェクトに加えて、個別のテスト プロジェクトも含まれています (テストについては第 9 章で説明します)。
アプリケーションのオブジェクト モデルとインターフェイスは、ApplicationCore プロジェクトに配置する必要があります。 このプロジェクトは、可能な限り少ない依存関係 (および特定のインフラストラクチャ上の懸念事項には関係ありません) を持ち、ソリューション内の他のプロジェクトで参照されます。 永続化する必要があるビジネス エンティティは、インフラストラクチャに直接依存しないサービスと同様に、ApplicationCore プロジェクトで定義されます。
永続化の実行方法やユーザーに通知を送信する方法などの実装の詳細は、インフラストラクチャ プロジェクトに保持されます。 このプロジェクトは Entity Framework Core などの実装固有のパッケージを参照しますが、プロジェクトの外部でこれらの実装に関する詳細を公開しないでください。 インフラストラクチャ サービスとリポジトリでは、ApplicationCore プロジェクトで定義されているインターフェイスを実装する必要があります。永続化の実装では、ApplicationCore で定義されているエンティティの取得と格納が行われます。
ASP.NET Core UI プロジェクトは、UI レベルの問題を担当しますが、ビジネス ロジックやインフラストラクチャの詳細を含めてはなりません。 実際、理想的にはインフラストラクチャ プロジェクトに依存する必要はありません。これは、2 つのプロジェクト間の依存関係が誤って導入されないようにするのに役立ちます。 これは、Autofac などのサードパーティの DI コンテナーを使用して実現できます。これにより、各プロジェクトのモジュール クラスで DI ルールを定義できます。
アプリケーションを実装の詳細から切り離すもう 1 つの方法は、アプリケーションがマイクロサービスを呼び出し、個々の Docker コンテナーにデプロイされるようにすることです。 これにより、2 つのプロジェクト間で DI を活用するよりも、懸念事項の分離と分離がさらに大きくなりますが、複雑さが増します。
機能の編成
既定では、ASP.NET Core アプリケーションは、コントローラーとビュー、および頻繁に ViewModel を含むようにフォルダー構造を整理します。 これらのサーバー側構造をサポートするクライアント側コードは、通常、wwwroot フォルダーに個別に格納されます。 ただし、大規模なアプリケーションでは、特定の機能に取り組むには多くの場合、これらのフォルダー間を移動する必要があるため、この組織で問題が発生する可能性があります。 これは、各フォルダー内のファイルとサブフォルダーの数が増えるにつれてますます困難になり、ソリューション エクスプローラーを大量にスクロールします。 この問題の解決策の 1 つは、ファイルの種類ではなく 機能 別にアプリケーション コードを整理することです。 この組織スタイルは、通常、機能フォルダーまたは 機能スライス と呼ばれます ( 「垂直スライス」も参照)。
ASP.NET Core MVC では、この目的のための領域がサポートされています。 領域を使用すると、各エリア フォルダーにコントローラーフォルダーとビュー フォルダー (および関連するモデル) のセットを個別に作成できます。 図 7-1 は、Areas を使用したフォルダー構造の例を示しています。
図 7-1 サンプルエリア組織化
領域を使用する場合は、属性を使用して、コントローラーが属する領域の名前でコントローラーを装飾する必要があります。
[Area("Catalog")]
public class HomeController
{}
また、ルートにエリアサポートを追加する必要があります。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "areaRoute", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
Areas の組み込みサポートに加えて、属性やカスタム ルートの代わりに独自のフォルダー構造と規則を使用することもできます。 これにより、ビューやコントローラーなどの個別のフォルダーが含まれていない機能フォルダーを作成でき、階層がフラットになり、各機能のすべての関連ファイルを 1 か所で簡単に表示できるようになります。 API の場合、フォルダーを使用してコントローラーを置き換えることができます。各フォルダーには、すべての API エンドポイントとそれに関連付けられている DTO を含めることができます。
ASP.NET Core では、組み込みの規則型を使用してその動作を制御します。 これらの規則は変更または置き換えることができます。 たとえば、名前空間に基づいて特定のコントローラーの機能名を自動的に取得する規則を作成できます (通常は、コントローラーが配置されているフォルダーに関連付けられます)。
public class FeatureConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
controller.Properties.Add("feature",
GetFeatureName(controller.ControllerType));
}
private string GetFeatureName(TypeInfo controllerType)
{
string[] tokens = controllerType.FullName.Split('.');
if (!tokens.Any(t => t == "Features")) return "";
string featureName = tokens
.SkipWhile(t => !t.Equals("features", StringComparison.CurrentCultureIgnoreCase))
.Skip(1)
.Take(1)
.FirstOrDefault();
return featureName;
}
}
次に、 ConfigureServices (または Program.cs) でアプリケーションに MVC のサポートを追加するときに、この規則をオプションとして指定します。
// ConfigureServices
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
// Program.cs
builder.Services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
ASP.NET Core MVC では、規則を使用してビューを検索することもできます。 ビューが機能フォルダーに配置されるように、カスタム規則でオーバーライドできます (上記の FeatureConvention によって提供される機能名を使用)。 このアプローチの詳細を確認し、MSDN マガジンの記事「 Feature Slices for ASP.NET Core MVC」から作業サンプルをダウンロードできます。
API と Blazor アプリケーション
アプリケーションにセキュリティで保護する必要がある一連の Web API が含まれている場合、これらの API は、View または Razor Pages アプリケーションとは別のプロジェクトとして構成することが理想的です。 API (特にパブリック API) をサーバー側の Web アプリケーションから分離することには、多くの利点があります。 多くの場合、これらのアプリケーションには固有のデプロイと読み込みの特性があります。 また、セキュリティを確保するためにさまざまなメカニズムを導入する可能性も非常に高く、標準的なフォームベースのアプリケーションではクッキーベースの認証が利用される一方、APIではトークンベースの認証が最も一般的です。
さらに、 Blazor アプリケーションは、 Blazor Server または BlazorWebAssembly のどちらを使用する場合でも、個別のプロジェクトとしてビルドする必要があります。 アプリケーションには、セキュリティ モデルだけでなく、異なるランタイム特性があります。 サーバー側の Web アプリケーション (または API プロジェクト) と共通の型を共有する可能性があり、これらの型は共通の共有プロジェクトで定義する必要があります。
eShopOnWeb に BlazorWebAssembly 管理インターフェイスを追加するには、いくつかの新しいプロジェクトを追加する必要があります。
Blazor
WebAssembly プロジェクト自体 (BlazorAdmin) です。
BlazorAdminによって使用され、トークン ベースの認証を使用するように構成された新しいパブリック API エンドポイントのセットが、PublicApi プロジェクトで定義されます。 また、これらの両方のプロジェクトで使用される特定の共有型は、新しい BlazorShared プロジェクトに保持されます。
BlazorSharedとApplicationCoreの両方で必要な型を共有するために使用できる共通のPublicApi プロジェクトが既にある場合に、別のBlazorAdmin プロジェクトを追加する理由が考えられます。 答えは、このプロジェクトにはアプリケーションのすべてのビジネス ロジックが含まれており、必要以上に大きく、サーバー上でセキュリティを維持する必要がある可能性が高いということです。
BlazorAdminによって参照されるすべてのライブラリは、Blazor アプリケーションを読み込むと、ユーザーのブラウザーにダウンロードされることに注意してください。
バックエンド -For-Frontends (BFF) パターンを使用しているかどうかに応じて、BlazorWebAssembly アプリで使用される API は、Blazorと型 100% を共有しない場合があります。 特に、多くの異なるクライアントが使用することを目的としたパブリック API では、クライアント固有の共有プロジェクトで共有するのではなく、独自の要求と結果の種類を定義できます。 eShopOnWeb サンプルでは、 PublicApi プロジェクトが実際にはパブリック API をホストしていることを前提としているため、すべての要求と応答の種類が BlazorShared プロジェクトから取得されるわけではありません。
横断的関心事
アプリケーションが成長するにつれて、重複を排除し、一貫性を維持するために、横断的な懸念を排除することがますます重要になります。 ASP.NET Core アプリケーションでの横断的な問題の例としては、認証、モデル検証規則、出力キャッシュ、エラー処理などがありますが、他にも多数あります。 ASP.NET Core MVC フィルター を使用すると、要求処理パイプラインの特定の手順の前または後にコードを実行できます。 たとえば、フィルターは、モデル バインドの前後、アクションの前後、またはアクションの結果の前後に実行できます。 承認フィルターを使用して、パイプラインの残りの部分へのアクセスを制御することもできます。 図 7-2 は、構成されている場合に要求実行がフィルターを通過する方法を示しています。
図 7-2 フィルターと要求パイプラインを使用して実行を要求します。
通常、フィルターは属性として実装されるため、コントローラーやアクション (またはグローバル) に適用できます。 この方法で追加すると、アクション レベルで指定されたフィルターがオーバーライドされるか、コントローラー レベルで指定されたフィルターに基づいて構築され、それ自体がグローバル フィルターをオーバーライドします。 たとえば、 [Route] 属性を使用して、コントローラーとアクションの間のルートを構築できます。 同様に、次の例に示すように、承認はコントローラー レベルで構成し、個々のアクションによってオーバーライドできます。
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous] // overrides the Authorize attribute
public async Task<IActionResult> Login() {}
public async Task<IActionResult> ForgotPassword() {}
}
最初のメソッド Login では、 [AllowAnonymous] フィルター (属性) を使用して、コントローラー レベルで設定された Authorize フィルターをオーバーライドします。
ForgotPasswordアクション (および AllowAnonymous 属性を持たないクラス内のその他のアクション) には、認証された要求が必要です。
フィルターを使用すると、API の一般的なエラー処理ポリシーの形式で重複を排除できます。 たとえば、一般的な API ポリシーは、存在しないキーを参照する要求に NotFound 応答を返し、モデルの検証が失敗した場合は BadRequest 応答を返します。 次の例では、これら 2 つのポリシーの動作を示します。
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
{
return NotFound(id);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
author.Id = id;
await _authorRepository.UpdateAsync(author);
return Ok();
}
このような条件付きコードでアクション メソッドが煩雑になることを許可しないでください。 代わりに、必要に応じて適用できるフィルターにポリシーをプルします。 この例では、API にコマンドが送信されるたびに発生するモデル検証チェックを、次の属性に置き換えることができます。
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
ValidateModelAttribute パッケージを含めることで、を NuGet 依存関係としてプロジェクトに追加できます。 API の場合は、 ApiController 属性を使用して、別の ValidateModel フィルターを必要とせずにこの動作を適用できます。
同様に、フィルターを使用してレコードが存在するかどうかを確認し、アクションが実行される前に 404 を返すことができます。これにより、アクションでこれらのチェックを実行する必要がなくなります。 一般的な規則を取り出し、UI からインフラストラクチャ コードとビジネス ロジックを分離するようにソリューションを整理したら、MVC アクション メソッドは非常に薄いはずです。
[HttpPut("{id}")]
[ValidateAuthorExists]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
await _authorRepository.UpdateAsync(author);
return Ok();
}
フィルターの実装の詳細については、MSDN Magazine の記事「実際の ASP.NET Core MVC フィルター」を参照してください。また、実際に動作するサンプルをダウンロードすることもできます。
検証エラー (無効な要求)、リソースが見つからない、サーバー エラーなどの一般的なシナリオに基づいて、API からの一般的な応答が多数ある場合は、 結果 の抽象化の使用を検討してください。 結果の抽象化は API エンドポイントによって使用されるサービスによって返され、コントローラーアクションまたはエンドポイントはフィルターを使用してこれらを IActionResultsに変換します。
参照 – アプリケーションの構造化
- 領域
https://learn.microsoft.com/aspnet/core/mvc/controllers/areas- MSDN Magazine – ASP.NET Core MVC のフィーチャースライス
https://learn.microsoft.com/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc- フィルター
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- MSDN Magazine – Real World ASP.NET Core MVC フィルター
https://learn.microsoft.com/archive/msdn-magazine/2016/august/asp-net-core-real-world-asp-net-core-mvc-filters- eShopOnWeb の結果
https://github.com/dotnet-architecture/eShopOnWeb/wiki/Patterns#result
セキュリティ
Web アプリケーションのセキュリティ保護は大きなトピックであり、多くの考慮事項があります。 最も基本的なレベルでは、セキュリティには、特定の要求のソースを確実に把握し、要求が必要なリソースにのみアクセスできるようにする必要があります。 認証とは、要求で提供された資格情報を信頼されたデータ ストア内の資格情報と比較して、要求が既知のエンティティからのものであるものとして扱う必要があるかどうかを確認するプロセスです。 承認は、ユーザー ID に基づいて特定のリソースへのアクセスを制限するプロセスです。 3 つ目のセキュリティ上の問題は、第三者による盗聴から要求を保護することです。この場合、少なくとも アプリケーションで SSL が使用されるようにする必要があります。
アイデンティティ
ASP.NET Core Identity は、アプリケーションのログイン機能をサポートするために使用できるメンバーシップ システムです。 ローカル ユーザー アカウントと、Microsoft アカウント、Twitter、Facebook、Google などのプロバイダーからの外部ログイン プロバイダーのサポートがサポートされています。 ASP.NET Core ID に加えて、アプリケーションでは Windows 認証または Identity Server などのサード パーティの ID プロバイダーを使用できます。
ASP.NET コア ID は、[個々のユーザー アカウント] オプションが選択されている場合、新しいプロジェクト テンプレートに含まれます。 このテンプレートには、登録、ログイン、外部ログイン、忘れたパスワード、追加機能のサポートが含まれています。
図 7-3 ID を事前に構成するには、[個々のユーザー アカウント] を選択します。
ID サポートは 、Program.cs または Startupで構成され、サービスとミドルウェアの構成が含まれます。
Program.csで ID を構成する
Program.csでは、WebHostBuilder インスタンスからサービスを構成し、アプリが作成されたら、そのミドルウェアを構成します。 重要な点は、必要なサービスの AddDefaultIdentity の呼び出しと、必要なミドルウェアを追加する UseAuthentication 呼び出しと UseAuthorization 呼び出しです。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
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>();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
アプリの起動時に ID を構成する
// Add framework services.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddMvc();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
UseAuthenticationの前にUseAuthorizationとMapRazorPagesを表示することが重要です。 ID サービスを構成すると、 AddDefaultTokenProvidersの呼び出しが表示されます。 これは、Web 通信をセキュリティで保護するために使用できるトークンとは関係ありませんが、代わりに、ユーザーが自分の ID を確認するために SMS または電子メールを使用してユーザーに送信できるプロンプトを作成するプロバイダーを指します。
2 要素認証の構成と外部ログイン プロバイダーの有効化の詳細については、公式の ASP.NET Core ドキュメントを参照してください。
認証
認証は、システムにアクセスするユーザーを決定するプロセスです。 ASP.NET Core Identity と前のセクションで示した構成方法を使用している場合は、アプリケーションで一部の認証の既定値が自動的に構成されます。 ただし、これらの既定値を手動で構成したり、AddIdentity によって設定された既定値をオーバーライドしたりすることもできます。 ID を使用している場合は、Cookie ベースの認証が既定の スキームとして構成されます。
Web ベースの認証では、通常、システムのクライアントを認証する過程で実行できるアクションは最大 5 つあります。 これらは:
- 認証。 クライアントから提供された情報を使用して、アプリケーション内で使用する ID を作成します。
- チャレンジ。 このアクションは、クライアントに自身の識別を要求するために使用されます。
- 禁じる。 アクションの実行が禁止されていることをクライアントに通知します。
- サインイン。 何らかの方法で既存のクライアントを保持します。
- サインアウトします。永続的なデータストレージからクライアントを削除します。
Web アプリケーションで認証を実行するには、いくつかの一般的な手法があります。 これらはスキームと呼ばれます。 特定のスキームでは、上記のオプションの一部またはすべてに対するアクションが定義されます。 一部のスキームではアクションのサブセットのみがサポートされ、サポートされていないアクションを実行するために別のスキームが必要になる場合があります。 たとえば、OpenId-Connect (OIDC) スキームはサインインまたはサインアウトをサポートしていませんが、この永続化に Cookie 認証を使用するように一般的に構成されています。
ASP.NET Core アプリケーションでは、上記の各アクションに対して、 DefaultAuthenticateScheme とオプションの特定のスキームを構成できます。 たとえば、DefaultChallengeScheme と DefaultForbidSchemeなどです。
AddIdentityを呼び出すと、アプリケーションのさまざまな側面が構成され、必要なサービスが多数追加されます。 また、認証スキームを構成するための次の呼び出しも含まれます。
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
これらのスキームでは、永続化に Cookie が使用され、既定では認証用のログイン ページへのリダイレクトが使用されます。 これらのスキームは、Web ブラウザーを介してユーザーと対話する Web アプリケーションには適していますが、API には推奨されません。 代わりに、API は通常、JWT ベアラー トークンなどの別の形式の認証を使用します。
Web API は、.NET アプリケーションの HttpClient や他のフレームワークの同等の型など、コードによって使用されます。 これらのクライアントは、API 呼び出しからの使用可能な応答、または問題が発生した場合に何が発生したかを示す状態コードを想定しています。 これらのクライアントはブラウザーを介して対話せず、API が返す可能性のある HTML をレンダリングしたり、操作したりすることはありません。 そのため、認証されていない場合、API エンドポイントがクライアントをログイン ページにリダイレクトすることは適切ではありません。 別のスキームの方が適切です。
API の認証を構成するには、eShopOnWeb 参照アプリケーションの PublicApi プロジェクトで使用される次のような認証を設定できます。
builder.Services
.AddAuthentication(config =>
{
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
1 つのプロジェクト内で複数の異なる認証スキームを構成することは可能ですが、1 つの既定のスキームを構成する方がはるかに簡単です。 このため、特に、eShopOnWeb 参照アプリケーションは、アプリケーションのビューと Razor Pages を含むメインのPublicApi プロジェクトとは別に、独自のプロジェクトWebに API を分離します。
Blazor アプリでの認証
Blazor サーバー アプリケーションは、他の ASP.NET Core アプリケーションと同じ認証機能を利用できます。 Blazor WebAssembly ただし、組み込みの ID および認証プロバイダーはブラウザーで実行されるため、アプリケーションでは使用できません。 Blazor WebAssembly アプリケーションは、ユーザー認証の状態をローカルに格納し、要求にアクセスして、ユーザーが実行できるアクションを決定できます。 ただし、ユーザーはアプリを簡単にバイパスして API を直接操作できるため、 BlazorWebAssembly アプリ内に実装されているロジックに関係なく、すべての認証と承認のチェックをサーバーで実行する必要があります。
参照 – 認証
- 認証アクションと既定値
https://stackoverflow.com/a/52493428- SPA の認証と承認
https://learn.microsoft.com/aspnet/core/security/authentication/identity-api-authorization- ASP.NET Core Blazor の認証と承認
https://learn.microsoft.com/aspnet/core/blazor/security/- セキュリティ: ASP.NET Web フォームでの認証と認可および Blazor
https://learn.microsoft.com/dotnet/architecture/blazor-for-web-forms-developers/security-authentication-authorization
認証
最も簡単な形式の承認では、匿名ユーザーへのアクセスを制限します。 この機能は、特定のコントローラーまたはアクションに [Authorize] 属性を適用することで実現できます。 ロールが使用されている場合は、次に示すように、属性をさらに拡張して、特定のロールに属するユーザーへのアクセスを制限できます。
[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
}
この場合、 HRManager ロールまたは Finance ロール (またはその両方) に属するユーザーは、SalaryController にアクセスできます。 ユーザーが複数のロール (複数のロールの 1 つだけでなく) に属することを要求するには、毎回必要なロールを指定して、属性を複数回適用できます。
さまざまなコントローラーやアクションで特定のロール セットを文字列として指定すると、望ましくない繰り返しが発生する可能性があります。 少なくとも、これらの文字列リテラルの定数を定義し、文字列を指定する必要がある任意の場所で定数を使用します。 承認規則をカプセル化する承認ポリシーを構成し、 [Authorize] 属性を適用するときに個々のロールの代わりにポリシーを指定することもできます。
[Authorize(Policy = "CanViewPrivateReport")]
public IActionResult ExecutiveSalaryReport()
{
return View();
}
この方法でポリシーを使用すると、制限されるアクションの種類を、適用される特定のロールまたはルールから分離できます。 後で、特定のリソースにアクセスできる必要がある新しいロールを作成する場合は、すべての [Authorize] 属性のすべてのロールの一覧を更新するのではなく、ポリシーを更新するだけで済みます。
請求
要求は、認証されたユーザーのプロパティを表す名前の値のペアです。 たとえば、ユーザーの従業員番号を要求として格納できます。 要求は、承認ポリシーの一部として使用できます。 次の例に示すように、"EmployeeOnly" という名前のポリシーを作成して、 "EmployeeNumber"という要求の存在を要求できます。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
}
このポリシーは、前述のように、 [Authorize] 属性と共に使用して、コントローラーやアクションを保護できます。
Web API のセキュリティ保護
ほとんどの Web API では、トークン ベースの認証システムを実装する必要があります。 トークン認証はステートレスであり、スケーラブルに設計されています。 トークン ベースの認証システムでは、クライアントは最初に認証プロバイダーで認証を行う必要があります。 成功した場合、クライアントはトークンを発行します。トークンは、単に暗号的に意味のある文字の文字列です。 トークンの最も一般的な形式は、JSON Web トークン (JWT) です (多くの場合、"jot" と発音されます)。 その後、クライアントは API に要求を発行する必要があるときに、このトークンを要求のヘッダーとして追加します。 サーバーは、要求を完了する前に、要求ヘッダーで見つかったトークンを検証します。 図 7-4 は、このプロセスを示しています。
図 7-4 Web API のトークン ベースの認証。
独自の認証サービスを作成したり、Azure AD や OAuth と統合したり、 IdentityServer などのオープンソース ツールを使用してサービスを実装したりできます。
JWT トークンは、クライアントまたはサーバーで読み取ることができるユーザーに関する要求を埋め込むことができます。 jwt.io などのツールを使用して、JWT トークンの内容を表示できます。 パスワードやキーなどの機密データは、簡単に読み取れるので、JTW トークンに格納しないでください。
SPA または BlazorWebAssembly アプリケーションで JWT トークンを使用する場合は、クライアントのどこかにトークンを格納し、すべての API 呼び出しに追加する必要があります。 このアクティビティは、通常、次のコードで示すようにヘッダーとして行われます。
// AuthService.cs in BlazorAdmin project of eShopOnWeb
private async Task SetAuthorizationHeader()
{
var token = await GetToken();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
上記のメソッドを呼び出した後、 _httpClient で行われた要求は、要求のヘッダーにトークンを埋め込み、サーバー側 API が要求を認証および承認できるようにします。
カスタム セキュリティ
注意事項
一般的な規則として、独自のカスタム セキュリティ実装の実装は避けてください。
暗号化、ユーザーメンバーシップ、またはトークン生成システムを独自に開発する場合は特に注意してください。 商用およびオープンソースの代替手段が多数用意されており、カスタム実装よりもほぼ確実にセキュリティが向上します。
参照 – セキュリティ
- Security Docs の概要
https://learn.microsoft.com/aspnet/core/security/- ASP.NET Core アプリでの SSL の適用
https://learn.microsoft.com/aspnet/core/security/enforcing-ssl- ID の概要
https://learn.microsoft.com/aspnet/core/security/authentication/identity- 承認の概要
https://learn.microsoft.com/aspnet/core/security/authorization/introduction- Azure App Service での API Apps に対する認証および承認
https://learn.microsoft.com/azure/app-service-api/app-service-api-authentication- Identity Server
https://github.com/IdentityServer
クライアント通信
ページを提供し、Web API 経由でデータの要求に応答するだけでなく、ASP.NET Core アプリは接続されたクライアントと直接通信できます。 この送信通信では、さまざまなトランスポート テクノロジを使用できます。最も一般的なのが WebSocket です。 ASP.NET Core SignalR は、リアルタイムのサーバー間通信機能をアプリケーションに簡単に追加できるライブラリです。 SignalR は、WebSocket を含むさまざまなトランスポート テクノロジをサポートし、開発者からの実装の詳細の多くを抽象化します。
WebSocket を直接使用するか、他の手法を使用するかに関係なく、リアルタイムのクライアント通信は、さまざまなアプリケーション シナリオで役立ちます。 いくつかの例を次に示します。
ライブチャットルームアプリケーション
アプリケーションの監視
業務進捗の更新
通知
対話型フォーム アプリケーション
アプリケーションにクライアント通信を構築するときは、通常、次の 2 つのコンポーネントがあります。
サーバー側接続マネージャー (SignalR Hub、WebSocketManager WebSocketHandler)
クライアント側ライブラリ
クライアントはブラウザーに限定されるものではなく、モバイル アプリ、コンソール アプリ、その他のネイティブ アプリも SignalR/WebSocket を使用して通信できます。 次の単純なプログラムは、WebSocketManager サンプル アプリケーションの一部として、チャット アプリケーションに送信されたすべてのコンテンツをコンソールにエコーします。
public class Program
{
private static Connection _connection;
public static void Main(string[] args)
{
StartConnectionAsync();
_connection.On("receiveMessage", (arguments) =>
{
Console.WriteLine($"{arguments[0]} said: {arguments[1]}");
});
Console.ReadLine();
StopConnectionAsync();
}
public static async Task StartConnectionAsync()
{
_connection = new Connection();
await _connection.StartConnectionAsync("ws://localhost:65110/chat");
}
public static async Task StopConnectionAsync()
{
await _connection.StopConnectionAsync();
}
}
アプリケーションがクライアント アプリケーションと直接通信する方法を検討し、リアルタイム通信によってアプリのユーザー エクスペリエンスが向上するかどうかを検討します。
参照 – クライアント通信
- ASP.NET コア SignalR
https://github.com/dotnet/aspnetcore/tree/main/src/SignalR- WebSocket Manager
https://github.com/radu-matei/websocket-manager
ドメイン駆動設計 – 適用する必要がありますか?
Domain-Driven デザイン (DDD) は、 ビジネス ドメインに重点を置いたソフトウェアを構築するためのアジャイル アプローチです。 実際のシステムのしくみを開発者に関連付けることができるビジネス ドメインの専門家とのコミュニケーションと対話に重点を置いています。 たとえば、株式取引を処理するシステムを構築している場合、ドメインの専門家は経験豊富な株式ブローカーである可能性があります。 DDD は、大規模で複雑なビジネス上の問題に対処するように設計されており、多くの場合、ドメインの理解とモデル化への投資は価値がないため、小規模でシンプルなアプリケーションには適していません。
DDD アプローチに従ってソフトウェアを構築する場合、チーム (技術的でない利害関係者や共同作成者を含む) は、問題領域用の ユビキタス言語 を開発する必要があります。 つまり、モデル化される実際の概念、同等のソフトウェア、および概念を保持するために存在する可能性のある構造 (データベース テーブルなど) には、同じ用語を使用する必要があります。 したがって、ユビキタス言語で説明されている概念は、 ドメイン モデルの基礎を形成する必要があります。
ドメイン モデルは、システムの動作を表すために相互に対話するオブジェクトで構成されます。 これらのオブジェクトは、次のカテゴリに分類される場合があります。
エンティティ。ID のスレッドを持つオブジェクトを表します。 エンティティは通常、後で取得できるキーを使用して永続化に格納されます。
集計。1 つの単位として永続化する必要があるオブジェクトのグループを表します。
値オブジェクト。プロパティ値の合計に基づいて比較できる概念を表します。 たとえば、開始日と終了日で構成される DateRange です。
ドメイン イベント。システム内で発生し、システムの他の部分に関心のある事柄を表します。
DDD ドメイン モデルでは、モデル内で複雑な動作をカプセル化する必要があります。 特に、エンティティは単にプロパティのコレクションであるべきではありません。 ドメイン モデルに動作がなく、単にシステムの状態を表している場合、DDD では望ましくない 貧血モデルと言われます。
DDD では、通常、これらのモデルの種類に加えて、さまざまなパターンが採用されています。
永続化の詳細を抽象化するためのリポジトリ。
複雑なオブジェクト作成をカプセル化するためのファクトリ。
複雑な動作やインフラストラクチャ実装の詳細をカプセル化するためのサービス。
コマンドは、コマンドの発行とその実行を分離するためのものです。
クエリの詳細をカプセル化するための仕様。
DDD では、前に説明したクリーン アーキテクチャの使用も推奨されており、単体テストを使用して簡単に検証できる疎結合、カプセル化、およびコードが可能になります。
DDD を適用するタイミング
DDD は、(技術的な) 複雑さだけでなく、ビジネスが大幅に複雑な大規模なアプリケーションに適しています。 アプリケーションには、ドメインの専門家の知識が必要です。 ドメイン モデル自体には、データ ストアからさまざまなレコードの現在の状態を格納して取得するだけでなく、ビジネス ルールと相互作用を表す重要な動作が必要です。
DDD を適用すべきではない場合
DDD には、基本的に CRUD (作成/読み取り/更新/削除) だけの小規模なアプリケーションやアプリケーションでは保証されないモデリング、アーキテクチャ、および通信への投資が含まれます。 DDD に従ってアプリケーションにアプローチすることを選択したが、ドメインに動作のない貧血モデルがある場合は、アプローチを再考する必要がある場合があります。 アプリケーションに DDD が必要ない場合や、データベースやユーザー インターフェイスではなく、ドメイン モデルにビジネス ロジックをカプセル化するためにアプリケーションをリファクタリングするサポートが必要な場合があります。
ハイブリッドアプローチは、アプリケーションのトランザクション領域またはより複雑な領域にのみ DDD を使用することですが、アプリケーションのより単純な CRUD または読み取り専用部分には使用しません。 たとえば、データにクエリを実行してレポートを表示したり、ダッシュボードのデータを視覚化したりする場合、集計の制約は必要ありません。 このような要件に対して、個別の単純な読み取りモデルを用意することは完全に許容されます。
参考文献 - Domain-Driven デザイン
- プレーン英語の DDD (StackOverflow Answer)
https://stackoverflow.com/questions/1222392/can-someone-explain-domain-driven-design-ddd-in-plain-english-please/1222488#1222488
デプロイメント
ASP.NET Core アプリケーションをデプロイするプロセスには、ホストされる場所に関係なく、いくつかの手順が関係します。 最初の手順は、 dotnet publish CLI コマンドを使用して実行できるアプリケーションを発行することです。 この手順では、アプリケーションをコンパイルし、アプリケーションの実行に必要なすべてのファイルを指定されたフォルダーに配置します。 Visual Studio からデプロイすると、この手順が自動的に実行されます。 発行フォルダーには、アプリケーションとその依存関係の .exe ファイルと .dll ファイルが含まれています。 自己完結型アプリケーションには、.NET ランタイムのバージョンも含まれます。 ASP.NET Core アプリケーションには、構成ファイル、静的クライアント資産、MVC ビューも含まれます。
ASP.NET Core アプリケーションはコンソール アプリケーションであり、アプリケーション (またはサーバー) がクラッシュした場合にサーバーの起動時と再起動時に起動する必要があります。 プロセス マネージャーを使用して、このプロセスを自動化できます。 ASP.NET Core の最も一般的なプロセス マネージャーは、Nginx と Apache on Linux、IIS、または Windows 上の Windows サービスです。
プロセス マネージャーに加えて、ASP.NET Core アプリケーションではリバース プロキシ サーバーを使用できます。 リバース プロキシ サーバーは、インターネットから HTTP 要求を受信し、いくつかの予備処理の後に Kestrel に転送します。 リバース プロキシ サーバーは、アプリケーションのセキュリティ層を提供します。 Kestrel は、同じポートで複数のアプリケーションをホストすることもサポートしていないため、ホスト ヘッダーなどの手法を使用して、同じポートと IP アドレスで複数のアプリケーションをホストすることはできません。
図 7-5. リバース プロキシ サーバーの背後にある Kestrel でホストされている ASP.NET
リバース プロキシが役立つもう 1 つのシナリオは、SSL/HTTPS を使用して複数のアプリケーションをセキュリティで保護することです。 この場合、リバース プロキシのみが SSL を構成する必要があります。 図 7-6 に示すように、リバース プロキシ サーバーと Kestrel の間の通信は HTTP 経由で行われる可能性があります。
図 7-6 HTTPS で保護されたリバース プロキシ サーバーの背後でホストされている ASP.NET
ますます一般的になるアプローチは、Docker コンテナーで ASP.NET Core アプリケーションをホストすることです。Docker コンテナーは、ローカルでホストすることも、クラウドベースのホスティングのために Azure にデプロイすることもできます。 Docker コンテナーには、Kestrel で実行されているアプリケーション コードが含まれている可能性があり、上記のようにリバース プロキシ サーバーの背後にデプロイされます。
Azure でアプリケーションをホストしている場合は、Microsoft Azure Application Gateway を専用の仮想アプライアンスとして使用して、複数のサービスを提供できます。 Application Gateway は、個々のアプリケーションのリバース プロキシとして機能するだけでなく、次の機能も提供できます。
HTTP の負荷分散
SSL オフロード (SSL はインターネットのみ)
エンド ツー エンドの SSL
マルチサイト ルーティング (1 つの Application Gateway に最大 20 サイトを統合)
Web アプリケーション ファイアウォール
Websocket のサポート
高度な診断
Azure デプロイ オプションの詳細については、 第 10 章を参照してください。
参照 – 展開
- ホスティングとデプロイの概要
https://learn.microsoft.com/aspnet/core/publishing/- リバース プロキシで Kestrel を使用する場合
https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel#when-to-use-kestrel-with-a-reverse-proxy- Docker で ASP.NET Core アプリをホストする
https://learn.microsoft.com/aspnet/core/publishing/docker- Azure Application Gateway の概要
https://learn.microsoft.com/azure/application-gateway/application-gateway-introduction
.NET