動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

ASP.NET MVC5 から Core への移行、最初に変わる3つ(DI標準 / Startup / appsettings)

バイブス父さん
現役の業務SE
2026年9月11日16 min read広告 (PR) を含む場合があります
ASP.NET MVC5 から Core への移行、最初に変わる3つ(DI標準 / Startup / appsettings)

みなさんこんにちは!ヒロポンです!

「そろそろうちも 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 を前提に組まれた処理が、そのままでは動かないから。ここを全部潰すのは、相当な工数になります。

で、現場での定石はこうです。

  1. 既存の MVC5 アプリは、動いてるものはそのまま残す
  2. 新規の機能・新規の画面は Core で書き始める
  3. リバースプロキシや共通の認証基盤で、旧アプリと新アプリを並走させる
  4. 触る頻度の高いところ・改修が入るところから、少しずつ 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.csConfigureServicesConfigure の2メソッド)と Program.cs の2枚に分かれていました。.NET 6 で最小ホスティングモデルになり、Program.cs 1枚に統合。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++) { /* ... */ }
    }
}

ConfigurationManagerSystem.Configuration に依存してて、Core の標準の読み方ではありません。設定は JSON + Options で受け取る。ここは切り替えるのが基本です。

ここまでの3つを、旧 Framework 版と Core 版で並べると、対応関係はこうなります。

MVC5とASP.NET Coreの起動・DI・設定・HttpContext・実行環境の対応表

最初に詰まるポイント — 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;
        // ... 記録
    }
}

IHttpContextAccessorbuilder.Services.AddHttpContextAccessor(); で登録してから使います。

静的アクセス前提で書かれたコードほど、この変更の影響が広く出る。だから移行時は「HttpContext.Current を何箇所で使ってるか」を最初に grep しておく。これだけで見積もりがだいぶ立てやすくなります。

移行の段取りを図で

ここまでの話を、進め方の流れとしてまとめておきます。

MVC5アプリを残したまま新規機能をCoreで書き並走させ、改修頻度の高い部分から徐々にCoreへ寄せる段階移行フロー

いきなり全部やらない。動いてるものは残す。新規と、触る頻度の高いところから 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 で経歴詳細を見る


この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について