今さらながらですが、ASP.NET Core に Minimal API という軽量・高速 HTTP API を構築する簡単な手法があるということを知ったので、少しだけですが試してみました。以下に備忘録として試したことを書いておきます。

Minimal API というのは、Microsoft のドキュメント「Minimal API の概要」に書いてありますように、Controller を使わないで最小のコードと構成で REST API を構築するもので、Controller のアクションメソッドに代えて Program.cs のミドルウェアで要求を処理して応答を返します。
基本的な作り方は Microsoft のチュートリアル「チュートリアル: ASP.NET Core を使って最小 API を作成する」にありましたので、それを参考に「API コードを追加する」のセクションのところまで作ってみました。
作り方については、プロジェクトの作成からサンプル REST API を構築するための詳しい方法までチュートリアルに述べられていますのでそちらを見てください。手抜きでスミマセンが、チュートリアルには十分な情報が提供されており、追加で書くことなどもありませんので。
チュートリアルの「API コードを追加する」のセクションまで進めば、GET, POST, PUT, DELETE 要求を受けて JSON 文字列を返す REST API が完成します。
上の画像は、検証のため別の ASP.NET Core MVC アプリに作成した REST API を呼び出すページを追加して、ボタンクリックで JavaScript の fetch を使って要求を出し、JSON 文字列として返される応答を表示したものです。そのコードはこの記事の下の方に参考に載せておきます。
また、検証に使用した呼び出し側のアプリはクロスオリジンになるので Minimal API では CORS の機能を有効にする必要がありますが、その方法を以下に書いておきます。
(1) CORS の機能を実装
先の記事「Web API に CORS 実装 (CORE)」に書いた Controller を使う普通の ASP.NET Core Web API と同様に、Program.cs に AddCors と UseCors を追加すれば CORS の機能は働くようになります。具体例は以下の通りです。
namespace MinimalAPI
{
public class Program
{
// CORS の機能を追加
const string MyAllowAnyOrigins = "_myAllowAnyOrigins";
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// CORS の機能を追加
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowAnyOrigins,
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// ・・・中略・・・
var app = builder.Build();
// CORS の機能を追加
app.UseCors(MyAllowAnyOrigins);
// ・・・中略・・・
}
}
Microsoft のドキュメント「CORS(異なるオリジン間でのリソース共有) 」にも説明がありますが、そのドキュメントには書いてない AllowAnyHeader() と AllowAnyMethod() は Preflight リクエストに必要なので注意してください。
また、そのドキュメントには "CORS は、[EnableCors] 属性により、または RequireCors メソッドを使用して宣言できます" と書いてありますが、自分が試した限りではそれらは不要でした。
(2) 呼び出し側の View のコード
検証のため、別の ASP.NET Core MVC アプリに作成した View のコードです。ボタンクリックで JavaScript の fetch を使って要求を出し、JSON 文字列として返される応答を表示します。
@{
ViewData["Title"] = "MinimalApiCors";
}
<h1>MinimalApiCors</h1>
<input type="button" value="READ ALL" onclick="minimalApiGet();" />
<input type="button" value="READ 1" onclick="minimalApiGet1();" />
<input type="button" value="READ COMPLETE" onclick="minimalApiGetCompleted();" />
<input type="button" value="UPDATE 1" onclick="minimalApiPut1();" />
<input type="button" value="DELETE 1" onclick="minimalApiDelete1();" />
<input type="button" value="CREATE" onclick="minimalApiPost();" />
<ul id="heroes"></ul>
@section Scripts {
<script type="text/javascript">
//<![CDATA[
const url = "https://localhost:44374/todoitems"; // IIS Express
const elem = document.querySelector("#heroes");
const minimalApiGet = async () => {
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
for (let i = 0; i < data.length; i++) {
elem.insertAdjacentHTML("beforeend",
`<li>${data[i].id}: ${data[i].name}, ${data[i].isComplete}</li>`);
}
} else {
elem.innerHTML = "失敗";
}
};
const minimalApiGet1 = async () => {
const response = await fetch(url + "/1");
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
elem.insertAdjacentHTML("beforeend",
`<li>${data.id}: ${data.name}, ${data.isComplete}</li>`);
} else {
elem.innerHTML = `失敗 (${response.status})`;
}
};
const minimalApiGetCompleted = async () => {
const response = await fetch(url + "/complete");
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
for (let i = 0; i < data.length; i++) {
elem.insertAdjacentHTML("beforeend",
`<li>${data[i].id}: ${data[i].name}, ${data[i].isComplete}</li>`);
}
} else {
elem.innerHTML = "失敗";
}
};
const minimalApiPut1 = async () => {
const params = {
method: "PUT",
body: '{"Id":1,"Name":"Updated animal","isComplete":true}',
headers: { 'Content-Type': 'application/json' }
}
const response = await fetch(url + "/1", params);
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
elem.insertAdjacentHTML("beforeend",
`<li>${data.id}: ${data.name}, ${data.isComplete} - Updated</li>`);
} else {
elem.innerHTML = `失敗 (${response.status})`;
}
};
const minimalApiDelete1 = async () => {
const params = {
method: "DELETE"
}
const response = await fetch(url + "/1", params);
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
elem.insertAdjacentHTML("beforeend",
`<li>${data.id}: ${data.name}, ${data.isComplete} - Deleted</li>`);
} else {
elem.innerHTML = `失敗 (${response.status})`;
}
};
const minimalApiPost = async () => {
const params = {
method: "POST",
body: '{"name":"Posted animal","isComplete":true}',
headers: { 'Content-Type': 'application/json' }
}
const response = await fetch(url, params);
if (response.ok) {
const data = await response.json();
elem.innerHTML = "";
elem.insertAdjacentHTML("beforeend",
`<li>${data.id}: ${data.name}, ${data.isComplete} - Inserted</li>`);
} else {
elem.innerHTML = "失敗";
}
};
//]]>
</script>
}