ASP.NET MVC を ASP.NET Core MVC にしてみる

ふと気になったので ASP.NET MVC を ASP.NET Core MVC にしてみようと思います。とりあえずいきなり巨大なものをコンバートすると心が折れるので ASP.NET MVC のプロジェクトを新規作成したものを、ASP.NET Core MVC に変換するということをやってみようと思います。

とりあえずどれくらい大変なのかを体験するために ASP.NET MVC から ASP.NET Core MVC への移行ドキュメントなどはチェックせずにやってみます。

新規作成したプロジェクトはこんな感じです。packages.config などがあるのがドキっとしますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/a58dfb51-1201-d003-5039-5aab6d063736.png)

とりあえず、後から参照が無い系エラーになったとき用に packages.config を消すのは最後にしようと思います。

まずは、プロジェクトファイルを以下の内容に書き換えます。一度プロジェクトをアンロードしてから読み込みましょう。

```xml:WebApplication4.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>
```

再オープンしてリビルドをしてみると以下のように 22 個のコンパイルエラーが出ました。

```
error CS0579: 'System.Reflection.AssemblyCompanyAttribute' 属性が重複しています
error CS0579: 'System.Reflection.AssemblyConfigurationAttribute' 属性が重複しています
error CS0579: 'System.Reflection.AssemblyFileVersionAttribute' 属性が重複しています
error CS0579: 'System.Reflection.AssemblyProductAttribute' 属性が重複しています
error CS0579: 'System.Reflection.AssemblyTitleAttribute' 属性が重複しています
error CS0579: 'System.Reflection.AssemblyVersionAttribute' 属性が重複しています
error CS0234: 型または名前空間の名前 'Optimization' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Mvc' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Mvc' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Routing' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Mvc' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Mvc' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Optimization' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'Routing' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0234: 型または名前空間の名前 'HttpApplication' が名前空間 'System.Web' に存在しません (アセンブリ参照があることを確認してください)
error CS0246: 型または名前空間の名前 'Controller' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'GlobalFilterCollection' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'ActionResult' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'BundleCollection' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'RouteCollection' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'ActionResult' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
error CS0246: 型または名前空間の名前 'ActionResult' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
```

`AssemblyCompanyAttribute` などの最初の 6 個のエラーは WPF のプロジェクトを変換したときに見たことがあるエラーですね。AssemblyInfo.cs が自動生成されるようになったため、.NET Framework のプロジェクトにある AssemblyInfo.cs と内容が重複してしまうために起きるエラーですね。

これはプロジェクトファイルに `GenerateAssemblyInfo` タグを追加して `false` を設定しましょう。以下のようになります。

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">

	<PropertyGroup>
		<TargetFramework>netcoreapp3.1</TargetFramework>
		<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
	</PropertyGroup>

</Project>
```

不要な using などがあったりする部分もあるので、一旦プロジェクトに対してコードのクリーンナップを実行して綺麗にします。

`HomeController` クラスには `using Microsoft.AspNetCore.Mvc;` を追加します。

```csharp:HomeController.cs
using Microsoft.AspNetCore.Mvc;

namespace WebApplication4.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}
```

残りのエラーが 4 つになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/aa9e6b19-d312-2d8c-fca8-04701fb8f4cf.png)

どれもアプリの構成系のファイルで起きています。というか Global.asax.cs ってありましたね。懐かしい…

`BundleConfig.cs` ではスクリプトのバンドルを作ってますが、この機能は ASP.NET Core MVC では見た記憶が無いので無いのでしょう。普通にスクリプトをそのまま参照します。

`FilterConfig.cs` では `HandleErrorAttribute` をフィルターに追加しています。`RouteConfig.cs` はルートの構成ですね。ここらへんは ASP.NET Core MVC では `Startup.cs` で書くので `Startup.cs` と エントリーポイントの `Program.cs` を追加して `App_Start` フォルダーと `Global.asax` は消してしまいます。

追加したファイルは以下の 2 つです。手っ取り早く ASP.NET Core MVC のプロジェクトを新規作成して、そこからコピペしてきました。

```csharp:Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace WebApplication4
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
```

```csharp:Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WebApplication4
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}
```

この状態でビルドするとエラーが以下の内容に変化しました！何かのエラーを解決する調査をしているときもそうですけど、エラー内容が変わると進捗を感じますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/0ba99b7b-2465-b9f2-46d8-31bcec987453.png)

これはスクリプトバンドルが無くなったせいですね。なので、`Conetnt` フォルダーの中身を `wwwroot/css` に移動させて `Scripts` フォルダーを `wwwroot/js` フォルダーに移動させて、`fonts` フォルダーを `wwwroot/fonts` に移動させて `favicon.ico` を `wwwroot` フォルダーに移動させます。

以下のようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/5f5d9e2a-388f-aa0b-ad94-cb983bb009b2.png)

そしてエラーの出ている `_Layout.cshtml` を以下の内容から

```html:_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - マイ ASP.NET アプリケーション</title>

    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("アプリケーション名", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("ホーム", "Index", "Home")</li>
                    <li>@Html.ActionLink("詳細", "About", "Home")</li>
                    <li>@Html.ActionLink("問い合わせ", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - マイ ASP.NET アプリケーション</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>
```

直接ファイルを参照するように変更します。

```html:_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - マイ ASP.NET アプリケーション</title>

    <link rel="stylesheet" href="~/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/bootstrap-theme.min.css" />
    <link rel="stylesheet" href="~/css/Site.css" />
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("アプリケーション名", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("ホーム", "Index", "Home")</li>
                    <li>@Html.ActionLink("詳細", "About", "Home")</li>
                    <li>@Html.ActionLink("問い合わせ", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - マイ ASP.NET アプリケーション</p>
        </footer>
    </div>

    <script src="~/js/jquery-3.4.1.min.js"></script>
    <script src="~/js/jquery.validate.min.js"></script>
    <script src="~/js/jquery.validate.unobtrusive.min.js"></script>
    <script src="~/js/bootstrap.min.js"></script>
    <script src="~/js/modernizr-2.8.3.js"></script>
    @RenderSection("scripts", required: false)
</body>
</html>
```

この時点でビルドするとエラーが消えます。とりあえずデバッグ実行してみましょう。

あっ、動いた…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/9b2143f9-ecec-8888-fd30-a74ceb9cc0d7.png)

詳細ページや問い合わせページも動きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/83269fc9-d4a0-99a1-8331-ed16ba66e659.png)

もうちょっと詰まるかと思ったら思ったよりあっさりいってびっくりしました。

ASP.NET から ASP.NET Core への移行ドキュメントを見てもアプローチは異なりますが、やっていることは大体同じでした。.NET Framework でなくなった機能を踏んでいない限りは割と機械的に移植できそうですね。

[ASP.NET MVC から ASP.NET Core MVC への移行](https://docs.microsoft.com/ja-jp/aspnet/core/migration/mvc?view=aspnetcore-5.0)