🟪 ASP.NET Coreで作るWeb API

はじめに
業務でASP.NET CoreでWebアプリケーションを開発する機会があったため、備忘録と情報整理のためにこのスクラップにまとめていきます。
対象読者
対象読者は次のような方を想定しています。
- Webについての基本的な知識と他の言語によるWebアプリケーションの開発経験がある人
- ASP.NET Coreについてサクっとキャッチアップしたい人
- C#の基本的な文法を把握している人
なお、.NETやASP.NET Coreについての事前知識がなくても導入できるように都度概念や用語の説明を入れてみたつもりです。
ASP.NET Coreとは
ASP.NET Coreは、Microsoftが開発したクロスプラットフォーム対応のWebアプリケーションフレームワークです。
ASP.NET Coreのいいところ(Pros)
- クロスプラットフォームであり、応答時間などのパフォーマンスに優れています。あくまでひとつの指標ではありますが、公式ページ で紹介されているTech Empower のベンチマーク結果が参考になります。
- DIに標準の方法が用意されているため、各コントローラやミドルウェアが疎結合になりテストの容易性を保つことができます。
- EFCoreを使ってSQL操作を抽象化し、LINQによって動的にデータを操作することができます。
- 認証機能はMicrosoft.AspNetCore.Identityおよびコード生成ツールを使うことでセキュアなWebアプリケーションを少ない手間で作ることができます。
- C#のモジュールシステムを使うことでDDDやClean Architectureなどの多層的なレイヤをもった構成を組みやすいため、業務アプリケーションの構築に特に適しているフレームワークのひとつです。「スケールアップしやすく、なおかつそこそこスモールスタートでも作りやすい」のがASP.NET Coreの良さなのだろうと個人的には思います。
- Visual StudioなどのIDEを使って開発することもできますが、基本的な操作は
dotnet
コマンドで完結します。VSCodeなどの一般的なエディタを使っての開発も十分にサポートされています。 - .NET SDKといくつかのツールチェインをインストール(もしくはVisual Studioをインストール)してテンプレートからプロジェクトを作成することで開発環境が整うため、環境構築は比較的容易な部類だと思います。
ASP.NET Coreのつらいところ(Cons)
- 一般的なWebに関する知識だけではなくフレームワークや周辺ツールチェイン固有の覚えるべきことが多いため学習コストが高く感じられるかもしれません。
- ASP.NET Coreのカバーする機能が非常に広く、プロジェクトの初期テンプレートもいろいろな種類があるため、キャッチアップする際にフレームワークの全体感を掴みにくいという問題があります。(以下にまとめています。)
ASP.NET Coreの関連技術や用語など
-
コントローラ: ASP.NET Coreにおける「コントローラ」とは、HTTPリクエストを処理しレスポンスを生成する一連の処理をハンドリングするためのクラスを表す。一般には、API用のコントローラを作るには
ControllerBase
を継承し、ビューを返すコントローラを作るにはController
を継承する。 - Razor View: MVCで使われるテンプレートエンジン。HTMLにC#のコードを埋め込む形で動的なビューをサーバーサイドで作成できる。
- Blazor Server: サーバー側でUI処理を行い、SignalRでクライアントとリアルタイム通信するSPAモデル。
- Blazor WebAssembly: ブラウザ上でC#コードをWebAssemblyとして実行するクライアントサイドSPAモデル。
- SignalR: Websocketによるリアルタイム通信を簡単に実装するためのライブラリ。
- ASP.NET Core Identity: ユーザー認証や権限管理をサポートする認証フレームワーク。
- Entity Framework Core (EF Core): .NET向けのフル機能のORM(Object-Relational Mapper)。O/Rマッピングだけではなく、差分抽出やマイグレーション機能を備えており、LINQにより型安全性を維持したまま動的にクエリを構築することができる。
- ASP.NET Core Code Generator: コントローラーやビューのテンプレートをモデルから自動生成(Scaffold)することができるツール。
- Microsoft.Extensions.Logging (ILogger): ロギングを統一的に扱うためのインタフェース。ログ出力の詳細を抽象化し、コンソールやファイルなどに柔軟に出力先を切り替えることができる。
- Microsoft.Extensions.Configuration (IConfiguration): 設定情報の読み込みを統一的に扱うためのインターフェース。
- Microsoft.Extensions.DependencyInjection: 依存性注入(DI: Dependency Injection)を行うための標準機能を提供するライブラリ。ASP.NET Coreでは、フレームワークが自動的に依存関係を解決しインスタンスを注入してくれるため、アプリケーション全体を疎結合でテストしやすい設計にすることができる。
ASP.NET Coreのプロジェクトテンプレート
ASP.NET Coreには数多くのテンプレートが用意されています。
公式ドキュメントにはそれぞれのテンプレートからアプリケーションを構築するための手順が載っていますが、
全体観が少し分かりにくいため整理すると次のようになります。
目的 | テンプレート名 | 説明 |
---|---|---|
APIのみ | webapi |
Web API用のテンプレート。コントローラ + ルーティングのみを提供する。 |
MVC Webアプリ | mvc |
コントローラ+ビューでサーバーサイドレンダリングするフル機能のWebアプリ。 |
Razor Pages Webアプリ | webapp |
ページ単位で作るシンプルなWebアプリ。 |
Blazor Serverアプリ | blazorserver |
サーバー上でレンダリングしSignalRで双方向通信ができるフルスタックWebアプリケーション。 |
Blazor WebAssemblyアプリ | blazorwasm |
ブラウザ上で動作するWebAssemblyアプリケーション。 |
バックグラウンドサービス | worker |
WindowsサービスやLinuxデーモンとして動作可能なバックグラウンドサービス。 |
gRPCサービス | grpc |
高速で型安全なRPCサービス。 |
なお、テンプレートの一覧は dotnet new list
コマンドで確認することができます。
ReactやVueなどで作成したSPAフロントエンドを載せる前提の場合は、Web APIテンプレートを使うことができます。
Minimal APIとコントローラクラス
Web APIテンプレートからプロジェクトを作成すると api.MapGet("weatherforecast", () => { ... })
という記述が出てきます。これは Minimal API というものです。これにより、コントローラクラスを作らずに、各エンドポイントのハンドリング処理を直接書くことができます。
コードの記述がシンプルで済むため、小規模なAPIないしはマイクロサービスを作る場合に活用すると良いでしょう。
一方で、APIの規模が大きくなってきた場合はコントローラクラスを作った方がルーティングをまとめたり責務を分担するのがやりやすいため最初からコントローラクラスを書いた方が楽になる場合も多いかもしれません。
この記事の構成
TODO

モデルの作成
TODO

データベースへのアクセス
モデル駆動とクエリ駆動
EF Coreの導入
Microsoft.EntityFrameworkCore (EF Core) はO/Rマッピングやマイグレーション・LINQとの統合などの機能を備えた、.NETで動作するフル機能のORMです。
まずは、EF Coreをインストールします。
dotnet tool install -g dotnet-ef
dotnet ef
を叩いてユニコーンくんが出てくればOKです。カッコいいですね!🦄
$ dotnet ef
dotnet ef
_/\__
---==/ \\
___ ___ |. \|\
| __|| __| | ) \\\
| _| | _| \_/ | //|\\
|___||_| / \\\/\\
Entity Framework Core .NET Command-line Tools 9.0.8
Usage: dotnet ef [options] [command]
Options:
--version Show version information
-h|--help Show help information
-v|--verbose Show verbose output.
--no-color Don't colorize output.
--prefix-output Prefix output with level.
Commands:
database Commands to manage the database.
dbcontext Commands to manage DbContext types.
migrations Commands to manage migrations.
プロジェクトへの追加
TODO
モデルの作成
データベースに格納するためのデータのモデルを定義していきます。
TODO
DbContextの作成
DbContext を継承したクラスを定義します。
これは、EF CoreにおいてDBのセッション管理とモデルやリレーションの定義を行うための基本単位となるクラスです。
DbContextの中には、それぞれのテーブルに対応する DbSet<T>
やカラム制約・リレーションの詳細設定などを書いていきます。
基本的には1アプリケーションにつきDbContextは1つ作れば事足りることが多いですが、複数のデータベースに問い合わせる必要がある場合やドメインの責務を分離するために複数のDbContextを用意する場合もあります。
分割する基準としては、1つの作業単位(Unit of Work)すなわちトランザクションの境界やライフサイクルの境界として考えると理解しやすいと思います。
TODO
マイグレーションを行う
EF Coreでは、モデルの変更差分をデータベースに反映させることを一般にマイグレーションといいます。
# 「InitialCreate」という名前で最初のマイグレーションファイルを作成し変更差分を記録する
dotnet ef migrasions add InitialCreate
# 現在のマイグレーション差分の一覧を確認する
dotnet ef migrations list
# 差分をデータベースに適用する
dotnet ef database update
マイグレーションしながら進める際の開発サイクル
EF Coreを使ってマイグレーションをしながら進める際の開発サイクルとしては、概ね次のようになると思います。
- 開発用DBおよびテスト用DBやCI/CDパイプラインの構築などの事前準備
- 開発
- DbContextやモデルなどの変更
- 差分の記録とマイグレーションファイルの作成(
dotnet ef migrations add "【マイグレーション名】"
) - 開発環境のデータベースへ変更差分を適用 (
dotnet ef database update
) - 動作確認
- マイグレーションファイルを含めてGitにコミット
- コードレビュー等々
- テスト環境や本番環境への適用 (
dotnet ef database update --context AppDbContext --connection "【接続文字列】"
)
なお、dotnet ef migrations add "【マイグレーション名】"
を実行するとマイグレーション用の差分ファイルが Migrations
ディレクトリ配下に積み上がっていきますが、ソースファイルと一緒にそれらのファイルごとGitで管理するのが一般的です。
マイグレーションを行うとデータベースの状態が大きく変わってしまうため、必ず開発用・検証用のDBを建てて差分を確認するなどして切り戻しや再構築ができるようにしておきましょう。

認証機能を実装する(ASP.NET Core Identity)
ASP.NET Core Idnetity は、ASP.NET Coreに組み込まれた認証・認可・ユーザー管理のためのフレームワークです。
分類 | 主な機能 |
---|---|
認証 (Authentication/AuthN) | Cookieや外部プロバイダー(Google, Facebook, Microsoft, Twitterなど)でのログイン |
認可 (Authorization/AuthZ) | ロールベース・クレームベースのアクセス制御 |
ユーザー管理 | ユーザー作成、削除、更新、パスワード変更、ロックアウト |
セキュリティ | パスワードハッシュ化、2要素認証、メール確認、ロックアウトポリシー |
データ永続化 | Entity Framework Coreを使ってユーザー情報やロールなどをRDBMSに保存 |
認証・認可・ユーザー管理等に関わる複雑でセキュリティ的に注意の必要な処理を一貫して任せられるため、セキュアなWebアプリケーションを比較的少ない手間で作成することができます。
また、コードジェネレータを使ってログイン用のRazorページを自動生成できるのも便利です。
パッケージの追加
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
ユーザーアカウントを表すモデルの作成とEFCoreによるマイグレーション
まず、ユーザーアカウントを表すモデルを追加します。
// Models/UserAccountModel.cs
using Microsoft.AspNetCore.Identity;
public class UserAccount : IdentityUser
{
// 追加したいフィールドがあればここに記述
}
次に、IdentityDbContext 基底としたDbContextを定義します。
// Infrastructure/AppDbContext.cs
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class AppDbContext : IdentityDbContext<UserAccount>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
}
次に、appsettings.json
に接続文字列を追加して Program.cs
で EF CoreとASP.NET Core Identityをセットアップします。
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=ApplicationData/app.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
// Program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// EFCore
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite(connectionString);
});
// Identity
builder.Services.AddDefaultIdentity<UserAccount>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddEntityFrameworkStores<AppDbContext>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers();
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.MapRazorPages();
app.Run();
マイグレーションを行うと、ASP.NET Core Identity用のテーブルが追加されていることが分かります。
dotnet ef migrations add AddIdentity
dotnet ef database update
▼SQLiteでマイグレーションした結果
ログインページ等のRazor Pagesの自動生成
ASP.NET Core Code Generatorを使ってログイン・アカウント登録などが行えるRazor Pagesを生成することができます。
dotnet aspnet-codegenerator identity -dc AppDbContext --files "Account.Register;Account.Manage.Index"
実行すると、【プロジェクトルート】/Areas/Identity/
ディレクトリ配下にRazor Pages (*.cshtml
, *.cshtml.cs
)が生成されます。
また、Razor Pagesを使うために Program.cs
に app.MapRazorPages();
を追加しておきましょう。
アプリケーションを起動して http://localhost:【ポート】/Identity/Account/Login
にアクセスするとログインページが表示されるはずです。
▼/Identity/Account/Login
にアクセスした結果
「Register as a new User」のリンクまたは http://localhost:【ポート】/Identity/Account/Register
からRegisterページを開き適当なユーザーを登録してDBの中身を確認してみると、AspNetUsers
テーブルにユーザー情報が格納されていることがわかります。(パスワードは当然ハッシュ化されています。)
再びログインページに戻ってそのユーザーでログインできればOKです。
$ sqlite3 ApplicationUser/app.db
sqlite> SELECT * FROM AspNetUsers
...> ;
7766d925-353c-4f80-980f-d2ee9133f2fc|0|33e92110-673f-4bb6-b203-11b60a6f4e42|test@example.com|0|1||TEST@EXAMPLE.COM|TEST@EXAMPLE.COM|AQAAAAIAAYagAAAAEBMpGO39tYkAa5Mj5DjyY4gG/OxtTsCCBKEWn4UAStgA2LR9JmdPfPf2V6Es2W0kog==||0|DUS66CDIE7QZQXO4RPWVNGKLIANY7YBY|0|test@example.com
コントローラにアクセス制御を付与する
コントローラにアクセス制御を付与するのは簡単で、コントローラとなるクラスに[Authorize]
属性を付与するだけで実現できます。
これによりログインしたユーザーのみがページにアクセスすることができます。
また、特定のロールのユーザーにのみアクセスを制限したい場合は [Authorize(Roles = "Admin")]
のように指定します。複数のロールを許可する場合は [Authorize(Roles = "Admin,Manager")]
のようにRolesの文字列値をカンマで区切ります。
ただし、事前にAspNetRolesテーブルでAdmin
Manager
などのロールが定義されており、ユーザーにそのロールが付与されている必要があります。
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using AspNetCoreIdentityTutorial.Models;
using Microsoft.AspNetCore.Authorization;
[Authorize]
public class HomeController : Controller
{
// ...
}

REST API機能の追加
MVC用のコントローラとAPI用のコントローラの違い
ASP.NET CoreにおけるコントローラはMVC用のものとAPI用のものでいろいろな違いがあります。(初めて触ったときにこのあたりを混同してしまっていたので説明しておきます…。)
項目 | MVC用コントローラ | API用コントローラ |
---|---|---|
基底クラス |
Controller が一般的 |
ControllerBase が一般的 |
属性 | ー |
[ApiController] 属性を付与 |
Razor View サポート | ✅ (View() , PartialView() ) |
❌ |
JSON / XML の応答 | 可能だがやや冗長 (return Json(obj); ) |
✅ デフォルト |
TempData / ViewBag | ✅ 利用可 | ❌ |
モデルバリデーション |
ModelState.IsValid を手動チェック |
[ApiController] 属性で自動的に400 BadRequestを返す |
返却値の型 |
IActionResult が一般的 |
ActionResult<T> が一般的 |
API用のコントローラは Controller
ではなく ControllerBase
を継承するのが一般的です。返却値の型は、IActionResult
ではなく ActionResult<T>
を使います。モデルの自動バリデーションやレスポンスのJSON/XMLシリアライズがデフォルトで使えるようになっており、一般的なWeb APIの用途に最適化されています。
実際のコードは次のようになります。
MVC用のコントローラ
public class HomeController : Controller
{
public IActionResult Index()
{
// Razor ビューを返す
return View();
}
[HttpPost]
public IActionResult SubmitForm(UserModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// データ処理
return RedirectToAction("Success");
}
}
API用のコントローラ
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult<UserDto> GetUser(int id)
{
var user = UserRepository.Find(id);
if (user == null)
{
return NotFound();
}
return user; // 自動で JSON にシリアライズ
}
[HttpPost]
public ActionResult<UserDto> Create(UserDto user)
{
if (user.Name == null)
{
return BadRequest();
}
var created = UserRepository.Add(user);
return CreatedAtAction(nameof(GetUser), new { id = created.Id }, created);
}
}
API用のコントローラの追加
TODO
OpenAPIドキュメントとScalarページの生成
OpenAPIドキュメントのUIとしては Swagger UI が使われることが多いですが、そのモダンな代替としてScalarを使うのが便利です。
Nugetパッケージ Scalar.AspNetCore と Microsoft.AspNetCore.OpenApi を追加します。
dotnet add package Microsoft.AspNetCore.OpenApi
dotnet add package Scalar.AspNetCore
Program.cs
でOpenAPIサービスを登録し、OpenAPIとScalarドキュメントのルーティングマップを追加します。
// Program.cs
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddControllers(); // コントローラをサービスに追加
builder.Services.AddOpenApi(); // OpenAPI のメタデータを生成するサービスを追加
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // OpenAPI仕様を提供
app.MapScalarApiReference(); // ScalarのUIを提供
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers(); // コントローラのルーティングマップを追加
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.Run();
dotnet run
でアプリケーションを起動します。ブラウザでhttp://localhost:【ポート】/scalar
にアクセスしてScalarドキュメントが表示されればOKです。
JWTベースのトークン認証を実装する
TODO

Reactフロントエンドとの連携
ASP.NET CoreアプリケーションのUI部分をReactを使ったSPAフロントエンドを作っていきます。
ただしフロントエンドの詳細は割愛し、バックエンドとの連携部分についてのみ説明します。
メモ: .NETにはASP.NET Core + React (dotnet new react
)というテンプレートが用意されていますが、 これはcreate-react-app
ベースになっています。
現時点では、新規プロジェクトではVite (もしくはRemixやNext.js等)を使うのが無難と思われます。
Vite+Reactによるプロジェクトの作成
Vite を使ってReactのテンプレートからプロジェクトを作成します。パッケージマネージャは pnpmを使うことにします。
npm install -g pnpm
pnpm create vite@latest
Frameworkは「React」、Variantは「TypeScript+SWC」を選択します。
◇ Project name:
│ 【プロジェクト名】
│
◇ Select a framework:
│ React
│
◇ Select a variant:
│ TypeScript + SWC
│
◇ Scaffolding project in *****...
│
└ Done. Now run:
cd 【プロジェクト名】
pnpm install
pnpm run dev
OpenAPIドキュメントからTypeScriptのクライアントコードを自動生成する
OrvalやHey API openapi-ts などのツールを使うと、OpenAPIドキュメントからTypeScriptのクライアントコードを自動生成することができます。
これにより、C#で定義したモデルからOpenAPIドキュメントを経由してTypeScriptに型情報を引き継ぐことができ便利です。
Hey APIは、プラグイン機能を使うことによりTanStack Queryクライアントの生成することができる、非同期状態管理ができるクライアントをシンプルに実装することができます。
pnpm dlx @hey-api/openapi-ts -p @tanstack/react-query -p @hey-api/client-fetch -i 【openapi.jsonのファイルまたはURL】 -o 【生成されるクライアントコードの出力先ディレクトリ】
SPAフロントエンドをASP.NET Coreアプリケーションからサーブする
Viteを使ってビルドしたフロントエンドアプリケーションをASP.NET Coreプロジェクトに載せて公開するには、wwwroot
にビルド成果物を配置して app.UseStaticFiles()
を使って静的ファイルとしてサーブします。また、React Router等を使ったルーティングに対応させるために、index.html
にフォールバックするよう構成しておくと良いでしょう。
pnpm run build
mv dist/* 【ASP.NET Coreのバックエンドプロジェクト】/wwwroot
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseDefaultFiles(); // index.html をデフォルトにする
app.UseStaticFiles(); // SPAフロントエンドを静的ファイルとしてサーブする
app.MapFallbackToFile("index.html"); // index.htmlにフォールバックさせてSPAのルーティングに対応させる

ビルドとデプロイ
.NETのビルドオプション
Dockerを使ったデプロイ

バックグラウンドジョブを統合的に管理する(Hangfire)
長時間稼働させる必要のあるシステムでは、メールの定期送信、外部API連携、データ集計やレポート生成などのバックグラウンドでなんらかのジョブ動作させたいといった要件が出てくると思います。
こうした処理にはWebリクエストの同期処理に含めるとレスポンスが遅くなりユーザー体験が悪化してしまうため、当然ながら非同期に実行する必要があります。しかし、cronやタスクスケジューラなどで素朴に実装すると処理の依存関係や実行状態の管理が難しくなります。また、外部のミドルウェアやサービスを使うこともできますがシステム全体が大掛かりになってしまう懸念もあります。
そういった場合には Hangfire というライブラリを使うのがおすすめです。
HangfireはDIを使ってバックグラウンド処理をサービスとして登録することで依存関係のあるいろいろなバックグラウンド処理をASP.NET Coreのアプリケーションに簡単に統合することができます。
また、HangfireにはWebベースのダッシュボード機能やジョブ履歴のDBへの永続化機能も備えており、ジョブが正常に動いているかどうかを簡単に追跡することができるため、不具合の切り分けの手助けになり運用者にも優しいライブラリだと言えます。

参考になる書籍
🟨 独習ASP.NET Core: 言わずと知れた「独習」シリーズのASP.NET Coreの解説書です。基本的にはMVCをベースに説明しており現場レベルですぐに使える知見がまとまっています。
🟦 Architecting ASP.NET Core Application: Packt Pubから出ている洋書であり、エンタープライズシステムに求められるアーキテクチャやデザインパターンについて詳しく解説してある本です。いい本なので和訳書が出てほしいな…。

構造化ログを出力する(Serilog)
TODO

設定の読み込み(IConfiguration)
.NETでは、設定ファイル・コマンドライン引数・環境変数などのいろいろなソースから読み取るための設定を統合的に扱うためのインタフェースとして IConfigurationが用意されています。
Microsoft Docsに掲載されているこの図がイメージしやすいです。
カスタム設定ファイルを読み込む
TODO