みなさんこんにちは!ヒロポンです!
社内システムから JSON を返すちょっとした API を作りたい。よくある話ですよね。別の画面から叩いたり、Excel マクロから叩いたり、隣のチームのバッチから叩いたり。
で、いざ手を動かそうとして固まる。「これ、いつもの MVC のコントローラでいいの?? それとも Web API ってやつ?」
ASP.NET の Web API と MVC の違い。名前が似てるうえに、MVC のコントローラでも JSON は返せる。だから最初はほんとに見分けがつかない。俺も昔、全部 MVC のコントローラで JSON を返してました。で、途中で「あれ、これ Web API のほうが楽だったのでは」って気づいたクチです。
今回はこの2つの役割の違いと、「JSON を返す社内 API はどっちで作るのが素直か」を、コピペで動く形で並べていきます。同じプロジェクトに両方混ぜてルーティングで詰まる、っていう最初のハマりどころも先に潰しておく。
忙しい人向けに最初にまとめ
- MVC コントローラは基本 View(HTML 画面)を返す係。
return Json(...)で JSON も返せるが、それは明示指定が要る - Web API(ApiController)は基本 データ(JSON / XML)を返す係。戻り値にデータを返すだけで、Accept ヘッダに応じてフレームワークが JSON か XML に変換する(コンテンツネゴシエーション)
- JSON を返す社内 API を作るなら Web API が素直。REST の作法(
Get/Post、404 を返す等)が最初から乗っているから - 最初のハマり: 同一プロジェクトに両方置くと、
api/プレフィックスで先に分けておかないと、リクエストが MVC 側に吸われる - ASP.NET Core では両者は統合済み。
[ApiController]属性で API として振る舞わせる
コードは中盤に全部あります。まずは「そもそも何が違うのか」から。
そもそも Web API と MVC コントローラは何が違うのか
一言でいうと、返すものが違う。出発点はここです。
MVC のコントローラは、画面(HTML)を返すために生まれたもの。System.Web.Mvc.Controller を継承して、アクションが View() を返し、Razor が .cshtml を HTML に組み立てる。人間がブラウザで見る画面の担当です。
Web API(ApiController)は、データを返すために生まれたもの。System.Web.Http.ApiController を継承して、アクションがオブジェクトをそのまま返すと、フレームワークがそれを JSON(や XML)にシリアライズする。人間じゃなくて、別のプログラムが叩く前提の担当です。
Web API は、ASP.NET でデータ(JSON / XML)を HTTP 経由で返すことに特化した、REST 作法をあらかじめ備えたフレームワークです。
.NET Framework の世界では、この2つは別々のフレームワークでした。名前空間も基底クラスもルーティングの仕組みも別。ここを「なんとなく似たもの」と思ってると、後でルーティングに足をすくわれます。
対応マップ — MVC コントローラと Web API を6観点で比較
まず全体像を一枚で、こんな感じにまとめました。どこがどう違うのか、観点ごとに並べます。

太字の「本命用途」の行が結論です。人が見る画面なら MVC、他のプログラムが叩く JSON なら Web API。ここさえ押さえておけば、細かい違いは後から効いてきます。
違い1: 返すもの — MVC は View、Web API はデータ
コードで見ると一発です。まずは MVC のコントローラ。
// ASP.NET MVC 5 (System.Web.Mvc)
public class ProductsController : Controller
{
// 既定は View (HTML) を返す
public ActionResult Index()
{
var products = _repo.GetAll();
return View(products); // Views/Products/Index.cshtml を描画
}
// JSON を返したいなら明示的に Json() を使う
public ActionResult ListJson()
{
var products = _repo.GetAll();
return Json(products, JsonRequestBehavior.AllowGet);
}
}
Index() は View を返す。JSON がほしい時は ListJson() みたいに毎回 Json(...) を明示する。「なんで GET だと怒られるの??」でおなじみの、JsonRequestBehavior.AllowGet の付け忘れも MVC あるあるです。
対して Web API はこう。
// ASP.NET Web API 2 (System.Web.Http)
public class ProductsController : ApiController
{
// 戻り値はデータそのもの。JSON/XML はフレームワークが決める
public IEnumerable<Product> Get()
{
return _repo.GetAll(); // Accept に応じて JSON か XML にシリアライズ
}
public IHttpActionResult Get(int id)
{
var p = _repo.Find(id);
if (p == null) return NotFound(); // 404 を素直に返せる
return Ok(p);
}
}
Get() は Product のリストをそのまま返してるだけ。Json() は書いてない。なのにクライアントには JSON が返る!! ここが Web API の気持ちよさで、「データを返す」ことに集中できる。NotFound() で 404、Ok() で 200 みたいに、HTTP のステータスを素直に扱える口も最初から付いてます。
いい感じに役割で分かれてるのが分かりますよね。MVC は「画面を組む」、Web API は「データを渡す」。
違い2: ルーティングの既定が違う
返すものと並んで大事なのがルーティング。混在時のハマりの震源地はここです。
- MVC:
{controller}/{action}/{id}。つまり/Products/Indexのようにアクション名が URL に出る - Web API:
api/{controller}/{id}。アクション名は URL に出ず、HTTP メソッド(GET / POST)でアクションを選ぶ
図にするとこう。同じ「products」でも、api/ が付くかどうかで行き先が変わります。

設定はそれぞれ別ファイルに書きます。App_Start の下に、MVC 用と Web API 用で分かれている。
// App_Start/WebApiConfig.cs — Web API は api/ プレフィックスで分ける
public static void Register(HttpConfiguration config)
{
config.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
// App_Start/RouteConfig.cs — MVC は従来どおりアクション名込み
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
肝は、Web API を api/ で先に分けておくこと。これをやっておかないと、次のハマりに直行します。
違い3: モデルバインディングとコンテンツネゴシエーション
もう1つ、地味だけど詰まる違い。入力(リクエストの受け取り方)と出力(レスポンスの形式決め)の既定が違います。
- 出力: MVC は「自分で
Json()を指定する」。Web API は「Accept ヘッダを見てフレームワークが JSON か XML を選ぶ」(コンテンツネゴシエーション) - 入力: Web API は、複雑な型(クラス)を1つだけ、リクエストボディから受け取るのが既定。複数のクラスをボディから受け取ろうとすると、片方が null になって「なんで値が入ってないの??」と詰まる
この入力の既定は、最初かなり驚きます。
public class ProductsController : ApiController
{
// ✅ ボディの複雑な型は1つだけ。これは素直に入る
public IHttpActionResult Post(Product product)
{
_repo.Add(product);
return Created($"api/products/{product.Id}", product);
}
// ⚠️ 複雑な型を2つボディから取ろうとすると、片方が null になりがち
// public IHttpActionResult Post(Product product, Category category) { ... }
}
MVC のノリで「引数にクラスを2つ並べれば両方フォームから拾ってくれる」と思ってると、Web API では素通りされる。ここは MVC と Web API でモデルバインディングの既定がそもそも違う、と割り切っておくのが早いです。
ハマりポイント: 同じプロジェクトに両方混ぜるとルーティングで詰まる
業務SEが最初にほぼ確実に踏むやつ、行きます。実体験ベースで書きます。
既存の MVC アプリに「あとから JSON API を足したい」ってなって、Web API のコントローラを1個追加する。よくある流れです。で、/api/products を叩く。すると……404 でビューが見つからないと言われる。あるいは MVC 側の別のアクションに吸い込まれる。
犯人はルート登録の順番と api/ プレフィックス。MVC の {controller}/{action}/{id} は貪欲なので、api を controller 名、products を action 名だと解釈して MVC 側に持っていってしまう。
避け方は2つ。
- Web API のルートを
api/プレフィックスで先に登録する(Global.asaxでGlobalConfiguration.Configure(WebApiConfig.Register)を MVC のRouteConfigより先に呼ぶ) - 同名の
ControllerとApiControllerを同じプロジェクトに置くなら、名前かフォルダで territory を明確に分ける
俺は昔これで、ProductsController(MVC)と ProductsController(Web API)を同名で置いてやらかしました。「Multiple types were found that match the controller named 'Products'」みたいな例外です。原因に気づくまで、ルーティングの登録順を行ったり来たり。小一時間、まるっと溶けました。名前を ProductsApiController に変えて、api/ プレフィックスを徹底したら、あっさり直る。
教訓は、MVC と Web API は同居できるが、URL の territory を先に分けておく。後から混ぜると、たいていここで足を取られます。
じゃあ、JSON を返す社内 API はどっちで作る?
判断軸をはっきりさせます。
他のプログラムが叩く JSON API を作るなら、Web API 一択でいい。理由はシンプル。Web API には「データを返す」「HTTP ステータスを素直に扱う」「Accept で形式を出し分ける」という REST の作法が最初から乗っている。MVC でも Json() を毎回書けば JSON は返せるけど、それは Web API が既定でやってくれることを手作業でなぞってるだけなんですよね。
逆に、人が見る画面(HTML)を返すなら MVC。そこは Web API の出る幕じゃない。
社内システムだと「画面もあるし、隣のチーム向けの API もほしい」という混在がよくあります。その時は、画面は MVC、API は Web APIで役割分担して、URL を api/ で分ける。これが .NET Framework 時代の素直な形です。
ASP.NET Core では、この2つは統合された
ここまで .NET Framework(System.Web 世代)の話をしてきました。あなたの現場が MVC 5 / Web API 2 なら、上の構図がそのまま効きます。
ただ、ASP.NET Core ではこの「2つの別フレームワーク」という構図は解消されています。Core では Controller も ApiController も区別がなくなり、どちらも ControllerBase(画面もやるなら Controller)を継承する。API として振る舞わせたいクラスには [ApiController] 属性を付けるだけ。ルーティングも [Route] 属性で統一されました。
いちばんシンプルな形だと、Core の最小 API はこれだけで JSON を返す社内 API になります。
// ASP.NET Core (最小 API): これだけで JSON を返す API になる
var app = WebApplication.Create();
app.MapGet("/api/products", () => new[]
{
new { id = 1, name = "ねじ", price = 120 },
new { id = 2, name = "ボルト", price = 240 },
});
app.Run();
実行して /api/products を叩くと、こんな感じで JSON が返ります。
実行結果:

Json() の明示も、api/ プレフィックスのルート登録も要らない。[ApiController] 属性付きのコントローラでも同じ発想で書けます。だから将来 Core に移るなら、Web API / MVC の壁は薄くなる。ただ、移行するその日まで、.NET Framework の現場では上の役割分担が現役です。
俺の現場メモ — 最初に決めておくと後で楽
キャッチアップして効いたコツを1つ。プロジェクトの最初に「API は全部 api/ の下」って決めてしまうことです。
画面と API が混ざる社内システムでは、後から「これ API だっけ画面だっけ」で迷子になる。最初に「/api/... は Web API、それ以外は MVC」という territory を引いておくと、ルーティングの事故がまず起きない。フォルダも Controllers/Api/ みたいに物理で分けておくと、レビューでも一目で分かる。
地味です。でも、これをやってるプロジェクトとやってないプロジェクトで、半年後のルーティング事故の数がぜんぜん違いました。最初の5分の決めごとで、後の小一時間のデバッグが消える。ほんとにおすすめです!!
まとめ
ASP.NET の Web API と MVC コントローラの違いを整理してきました。
- MVC コントローラは画面(HTML)を返す係。
Json()を明示すれば JSON も返せる - Web API(ApiController)はデータ(JSON / XML)を返す係。戻り値にデータを返すだけでシリアライズされ、REST の作法が最初から乗っている
- JSON を返す社内 API は Web API が素直。MVC で
Json()を毎回書くのは Web API の既定を手でなぞる作業 - 混在させるなら
api/プレフィックスで URL の territory を先に分ける。後から混ぜるとルーティングで詰まる - ASP.NET Core では両者は統合。
[ApiController]属性で API 化する
「MVC でも JSON 返せるじゃん」で全部 MVC に寄せると、コンテンツネゴシエーションも HTTP ステータスも手作業になって、じわじわ辛くなる。API は Web API で作る。この役割分担を最初に引いておくだけで、社内の別チームやバッチとの連携が、目に見えて素直になります。そこがこの使い分けの一番の効きどころです。
よくある質問
MVC の Controller と Web API の ApiController、名前が同じでも大丈夫ですか
同じプロジェクトで同名にすると事故ります。ルーティング解決時に「Products という名前のコントローラが複数見つかった」という例外になったり、意図しないほうに振り分けられたりします。ProductsApiController のように名前で分けるか、フォルダ(名前空間)で分けて、api/ プレフィックスのルートを徹底してください。
Web API で戻り値を IHttpActionResult にする意味は何ですか
HTTP ステータスを素直に扱えるようになります。return Ok(data) で 200、return NotFound() で 404、return BadRequest() で 400、というふうに、データだけでなく「どのステータスで返すか」を明示できます。データを直接返す(IEnumerable<Product> 等)書き方は常に 200 になるので、404 や 400 を返し分けたいアクションでは IHttpActionResult を使うのが定石です。
社内向けの小さな API でも Web API を使うべきですか
規模が小さくても Web API が素直です。JSON を返す・ステータスを返し分ける・Accept で形式を選ぶ、といった「API としての当たり前」を自前で書かずに済むからです。逆に、返すのが1画面ぶんの HTML だけならわざわざ API にせず MVC のままで構いません。判断軸は規模ではなく「返すのが人向けの画面か、プログラム向けのデータか」です。
動作確認メモ
この記事のうち、ASP.NET Core の最小 API(WebApplication.Create() で /api/products に JSON を返す例)はコンテナ上の .NET で実際に起動し、HTTP で叩いて JSON が返ることを確認しています(上の実行結果)。一方、MVC 5 / Web API 2(System.Web 系)のコードは Windows + IIS + .NET Framework が前提で、Linux コンテナでは実行できないため、構文と挙動は Microsoft Learn の公式ドキュメントで裏取りしたうえで掲載しています。手元の Visual Studio + .NET Framework でお試しください。
次に読むべき記事





以上!
同じ「MVC で全部 JSON 返してて後悔した」経験がある人、どんどんシェア待ってるぜ!!
執筆者
バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SIer の正社員からフリーランスに転じ、 複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る



