みなさんこんにちは!ヒロポンです!
「そろそろうちも Core に上げたいんだよね」。ある日、現場でそんな話がふっと持ち上がる。
あるいは新規案件の要件に「ASP.NET Core で」と書かれてて、MVC5 しか触ってこなかった自分がそこにアサインされる。
そういう朝、まず気になるのは「で、何がどう変わるん??」ってところですよね。
今回は ASP.NET MVC5 Core 移行で最初に体感する変化を、3つに絞ります。旧 Framework 版と Core 版を、実コードで並べて見ていく。
起動の書き方、依存性注入、設定ファイル。この3つが分かると、移行の話が降ってきても「あー、あそこが変わるやつね」と全体像がスッとつかめます。
先に言っておくと、全部を一度に移行する必要はないです。そこも含めて、現実的な進め方を書きます。
忙しい人向けに最初にまとめ
- ASP.NET MVC5 から Core で最初に変わるのは (1) 起動 (Global.asax → Program.cs) (2) 依存性注入が標準搭載 (3) 設定ファイル (web.config → appsettings.json) の3つ。
- 移行は全面一括ではなく、新規機能から Core で書くのが現実的。既存 MVC5 はそのまま残す。
- 最初に詰まるのは System.Web 依存のライブラリが動かないことと、HttpContext.Current が使えないこと。
- この記事の Core 側コードは .NET 9 コンテナで動作確認済み。MVC5 側は .NET Framework 専用なので、対比用の参照コードとして載せています。
全部を一度に移行しない — これが現実解
いきなり結論に近い話をします。大事なので最初に。
既存の MVC5 業務アプリを、ある日えいやで全部 Core に書き換える。これはマジで現実的じゃないです。
理由は後半で詳しく触れます。ざっくり言うと、System.Web 依存のコードや、HttpContext.Current を前提に組まれた処理が、そのままでは動かないから。ここを全部潰すのは、相当な工数になります。
で、現場での定石はこうです。
- 既存の MVC5 アプリは、動いてるものはそのまま残す
- 新規の機能・新規の画面は Core で書き始める
- リバースプロキシや共通の認証基盤で、旧アプリと新アプリを並走させる
- 触る頻度の高いところ・改修が入るところから、少しずつ Core 側に寄せていく
この「新規から Core」戦略なら、Core の書き方を実案件で少しずつ覚えられる。しかも既存の動いてるものを壊さずに済む。いい感じに移行の足場を作れるわけです。
じゃあ、その「新規を Core で書く」とき、MVC5 と何が違うのか。ここから3つ見ていきます。
変化1: 起動が Global.asax から Program.cs に変わる
MVC5 のアプリは、起動時の設定を Global.asax(正確には Global.asax.cs)に書いていました。ルーティング登録やフィルター登録を、ここでやる。
// 【旧・MVC5 (.NET Framework)】Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Core(.NET 6 以降)では、この起動処理が Program.cs 1枚にまとまりました。最小ホスティングモデルと呼ばれる形です。
// 【新・ASP.NET Core (.NET 6+)】Program.cs
var builder = WebApplication.CreateBuilder(args);
// サービス登録 (旧 ConfigureServices 相当)
builder.Services.AddControllersWithViews();
var app = builder.Build();
// ミドルウェア・ルーティング (旧 Configure 相当)
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
ポイントは、WebApplication.CreateBuilder で「サービスを登録するフェーズ」と、app.Build() の後で「ミドルウェアを組み立てるフェーズ」に分かれていること。
こんな感じで2フェーズに分かれてる。これが Core の起動の基本形です。
バージョン注記: .NET 5 まではこの起動処理が
Startup.cs(ConfigureServicesとConfigureの2メソッド)とProgram.csの2枚に分かれていました。.NET 6 で最小ホスティングモデルになり、Program.cs1枚に統合。Startup.cs 形式も引き続き動きますが、新規テンプレートは Program.cs 形式です。移行先が .NET 8 LTS なら、この Program.cs 形式で覚えておけばOK。
ここまでで、「起動は Global.asax から Program.cs の2フェーズ構成に変わる」が押さえられました。
変化2: 依存性注入(DI)が標準搭載になった
MVC5 には、DI コンテナが標準では付いていませんでした。
使いたければ Unity や Autofac、Ninject といったライブラリを自分で入れる。そのうえでコントローラーのファクトリを差し替える、という手間が必要だった。
Core は違います。DI が最初から組み込まれてる。builder.Services に登録して、コンストラクタで受け取るだけ。
// 変化2: DI は標準。Program.cs で登録するだけ
builder.Services.AddScoped<IOrderService, OrderService>();
// コントローラーはコンストラクタで受け取る (Core が自動で注入)
public class OrderController : Controller
{
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService; // Core が解決して渡してくれる
}
public IActionResult Index()
{
var orders = _orderService.GetAll();
return View(orders);
}
}
登録の寿命(ライフタイム)は3種類。AddScoped(リクエストごとに1個)、AddTransient(要求のたびに新規)、AddSingleton(アプリで1個)を使い分けます。
業務のサービスクラスは、基本 AddScoped で困らないです。
MVC5 時代に「DI 入れるかどうか」で会議してた現場からすると、標準で付いてくるのはマジで楽なんですよね!!
この辺は「そもそも MVC5 で DI 要るのか」を別記事で書いてます。DI 自体にまだピンと来てない人は、そっちも合わせてどうぞ。
ここまでで、「Core では DI が標準・登録して受け取るだけ」が分かりました。
変化3: web.config から appsettings.json に変わる
設定の持ち方も変わります。MVC5 は web.config の <appSettings> に書いて、ConfigurationManager.AppSettings で読んでいました。
// 【旧・MVC5】web.config の <appSettings> を読む
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
// 値は全部 string で返る → 数値は自分で Parse
int retry = int.Parse(ConfigurationManager.AppSettings["RetryCount"]);
Core は appsettings.json に JSON で書きます。
{
"MyApp": {
"ApiKey": "abc123",
"RetryCount": 3
}
}
読み方は IConfiguration を DI で受け取る形。
さらに Options パターンを使うと、設定を型付きのクラスにまとめて受け取れます。これがいい感じに型安全で、業務では特に効く。
// 設定を受け取るクラス
public class MyAppOptions
{
public string ApiKey { get; set; }
public int RetryCount { get; set; } // int のまま受け取れる
}
// Program.cs で appsettings.json のセクションを紐づけ
builder.Services.Configure<MyAppOptions>(
builder.Configuration.GetSection("MyApp"));
// 使う側は IOptions<T> で受け取る
public class NotifyService
{
private readonly MyAppOptions _opt;
public NotifyService(IOptions<MyAppOptions> opt) => _opt = opt.Value;
public void Run()
{
// _opt.RetryCount は int。Parse 不要
for (int i = 0; i < _opt.RetryCount; i++) { /* ... */ }
}
}
ConfigurationManager は System.Configuration に依存してて、Core の標準の読み方ではありません。設定は JSON + Options で受け取る。ここは切り替えるのが基本です。
ここまでの3つを、旧 Framework 版と Core 版で並べると、対応関係はこうなります。

最初に詰まるポイント — System.Web が動かない
さて、ここが移行で一番最初にぶつかる壁です。実際に手を動かすと、たいていここで「あれ??」ってなる。
ハマりポイント①: System.Web に依存したコードは Core でそのまま動かない。
MVC5 は System.Web という .NET Framework 専用のアセンブリの上に乗っていました。
Core は System.Web を持っていません。だから System.Web の型(HttpContext, HttpRequest, MvcHtmlString など Framework 版)を直接触っているコードや、それに依存したライブラリは、Core プロジェクトに持っていってもコンパイルが通らない。
正直、私も初めて既存コードを Core 側に移そうとしたとき、これで固まりました。
共通ユーティリティが System.Web をがっつり参照してて、Core プロジェクトに載せた瞬間コンパイルエラーの山。画面が赤で埋まった。「そうか、土台のアセンブリごと別物なのか」と腹落ちするまで、地味に時間を溶かした。
これが「全部を一度に移行しない」を強く勧める理由です。System.Web 依存を全部剥がすのは、それ自体が一大プロジェクトになる。
ハマりポイント②: HttpContext.Current が使えない。
MVC5 では、どこからでも HttpContext.Current で現在のリクエストのコンテキストを静的に取れました。
共通クラスやヘルパーの奥からユーザー情報を引っ張る、みたいな書き方をしてた現場、多いはず。
// 【旧・MVC5】どこからでも静的に取れた
var user = HttpContext.Current.User.Identity.Name;
Core にはこの静的な HttpContext.Current がありません。代わりに IHttpContextAccessor を DI で注入して使います。
// 【新・Core】DI で IHttpContextAccessor を受け取る
public class AuditLogger
{
private readonly IHttpContextAccessor _accessor;
public AuditLogger(IHttpContextAccessor accessor) => _accessor = accessor;
public void Log()
{
var user = _accessor.HttpContext?.User?.Identity?.Name;
// ... 記録
}
}
IHttpContextAccessor は builder.Services.AddHttpContextAccessor(); で登録してから使います。
静的アクセス前提で書かれたコードほど、この変更の影響が広く出る。だから移行時は「HttpContext.Current を何箇所で使ってるか」を最初に grep しておく。これだけで見積もりがだいぶ立てやすくなります。
移行の段取りを図で
ここまでの話を、進め方の流れとしてまとめておきます。

いきなり全部やらない。動いてるものは残す。新規と、触る頻度の高いところから Core に寄せる。
この段取りが頭に入ってると、移行の話が降ってきても慌てずに済みます!!
まとめ
ASP.NET MVC5 から Core への移行で、最初に体感する変化は3つでした。
- 起動: Global.asax から Program.cs へ。サービス登録とミドルウェア組み立ての2フェーズ構成。
- 依存性注入: 標準搭載に。
builder.Servicesに登録してコンストラクタで受け取る。 - 設定: web.config から appsettings.json へ。IConfiguration か Options パターンで型付きに読む。
そして、詰まりどころは System.Web 依存が動かないことと、HttpContext.Current が使えないこと。だからこそ、全面一括ではなく「新規から Core・既存は残して並走」で進める。
この全体像を一度押さえておくと、現場で「Core に上げよう」の話が出たときに、何がどれだけ変わるかを自分の言葉で説明できます。
押し出されてから慌てて調べるのと、地図を持って臨むのと。同じ移行でも、精神的な余裕が全然違いますよ。
よくある質問
MVC5 と Core は同じプロジェクトに混在できますか?
同じプロジェクトファイル内での混在はできません。MVC5(.NET Framework)と Core(.NET)はランタイムも SDK も別物だからです。現実的には、MVC5 アプリと Core アプリを別プロジェクト・別プロセスとして並走させ、リバースプロキシや共通認証で束ねる形になります。
Startup.cs のままでも移行できますか?
できます。.NET 6 以降でも Startup.cs 形式は引き続きサポートされています。ただし新規テンプレートは Program.cs 1枚の最小ホスティングモデルなので、これから覚えるなら Program.cs 形式に寄せておくほうが、公式ドキュメントやサンプルと足並みがそろって楽です。
DI を入れると既存の new での生成は全部書き換えですか?
一度に全部書き換える必要はありません。DI に載せるのは、テストしたい・差し替えたいサービス層から。ロジックを持たない単純なデータクラスまで無理に DI に載せる必要はないです。移行と同じで、効くところから段階的に寄せれば十分です。
appsettings.json に接続文字列も書いていいですか?
書けます。ただし本番の接続文字列やシークレットは appsettings.json に直書きせず、環境変数や Secret Manager、Azure Key Vault などに逃がすのが定石です。Core の構成は複数のソース(json・環境変数など)を重ねて読めるので、環境ごとの上書きがしやすくなっています。
移行しないとそのうち動かなくなりますか?
.NET Framework 4.8 自体は Windows に同梱で長期サポートが続くので、既存アプリが急に動かなくなるわけではありません。ただし新しい言語機能やライブラリ、パフォーマンス改善は Core 側に来るので、新規開発の選択肢としては Core が前提になりつつあります。「動くから触らない」既存と、「新規は Core」を分けて考えるのが現実的です。
次に読むべき記事





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



