ASP.NET Core MVC Areaを利用する

何でエリアを利用するかについては省きます。

##バージョン
.net 5.0
Microsoft Visual Studio Community 2019 Version 16.8.6

##フォルダー構成
例えアプリにPersonnelとTreasurerという二つの論理グループがあるとします、エリアを利用するとフォルダー構造は次のようになります。
＊プロジェクト名
　|－Areas
　　|－Personnel
　　　|－Controllers
　　　　|－HomeController.cs
　　　|－Models
　　　　|－HomeModel.cs
　　　|－Views
　　　　|－Home
　　　　　|－Index.cshtml
　　　　　|－About.cshtml
　　|－Treasurer
　　　|－Controllers
　　　　|－HomeController.cs
　　　|－Models
　　　　|－HomeModel.cs
　　　|－Views
　　　　|－Home
　　　　　|－Index.cshtml
　　　　　|－About.cshtml
　|－Controllers
　|－Models
　|－Views
　|－wwwroot

エリアを利用する時の構造は一般的にこうなっているですが、MVCの規約ではViewファイルのみこの構造に従う必要がある、ModelとControllerは従わなくても問題がない、外だしは可能ということです。

##エリアを作成
プロジェクトで右クリック⇒追加⇒新規スキャフォールディングアイテム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1088099/5bf9422d-88f2-6b72-8912-f414b2617e4d.png)

MVCエリアを選んで追加、その後エリア名を入力して作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1088099/b8c3b603-5542-2b63-8ea7-bb5e09472474.png)

エリアを作成した後の実際の構造は次のようになります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1088099/02539c95-3c8d-aaaa-b8f8-82863c8eb36a.png)

##エリアルートを追加

```C#:Startup.cs
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: "areas",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

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

```
[詳細なエリアルーティングはここから参照](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#areas)

上記コードの「{area:exists}」ではルートが必ず一つのエリアと一致しなければならないという制約を掛けている
[詳細なルーティング制約はここから参照](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-5.0#route-constraint-reference)

##エリアのコントローラー作成

```C#:HomeController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //エリア属性によって、該当コントローラーを指定のエリアと関連付ける
    [Area("Personnel")]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
```

##_ViewStart.cshtml と _ViewImports.cshtml
この二ファイルの影響先はファイルを配置しているフォルダー及びその下級フォルダーのみ、なのでエリアじゃなく普通のViewsフォルダーにあるこの二ファイルはAreasに対しては影響を与えることはできない。
ViewsとAreasの両方に対して影響を与えたい場合はアプリケーションのルートフォルダ（Startup.csを含むフォルダ）にそのファイルを配置もしくはコピーすること。

[_ViewStart.cshtmlの詳細はここから参照](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/layout?view=aspnetcore-5.0#what-is-a-layout)
[_ViewImports.cshtmlの詳細はここから参照](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/layout?view=aspnetcore-5.0#importing-shared-directives)

##Areasのフォルダー名変更
既定のエリアフォルダー「Areas」を変更することはできます

```C#:Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        //既存のエリアビューの検出ルールをクリア
        options.AreaViewLocationFormats.Clear();
        //新しいエリアビューの検出ルールを登録する
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
    });

    services.AddControllersWithViews();
}
```

##Area属性の応用
前述のようにエリアとコントローラーを関連付けるため、ントローラーにArea属性を定義する必要が有ります、もしそのエリアに沢山のコントローラーがあったらそれ全部を定義する必要が有ります。そうなると問題が発生し易くなります。

```C#:Microsoft.AspNetCore.Mvc.Core.dll
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public class AreaAttribute : Microsoft.AspNetCore.Mvc.Routing.RouteValueAttribute
```

AreaAttributeクラスの定義から見るとArea属性が継承を許されていることがわかる、つまり親にArea属性を設定するとそのクラスを継承した子にもArea属性が適用されます。よってエリアコントローラーは次のように変更可能

```C#:PersonnelBaseController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //親コントローラーにArea属性を設定
    //Controllerが抽象クラスなので、PersonnelBaseControllerは抽象にしてもしなくても良いです
    [Area("Personnel")]
    public abstract class PersonnelBaseController: Controller
    {
    }
}
```

```C#:HomeController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //親コントローラーにエリア属性を設定しているので、該当子コントローラーには設定しなくてもPersonnelエリアと関連付けている
    public class HomeController : PersonnelBaseController
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
```

##まとめ
#####エリアのビューの検出順序

* /Areas/\<Area-Name>/Views/\<Controller-Name>/\<Action-Name>.cshtml
* /Areas/\<Area-Name>/Views/Shared/\<Action-Name>.cshtml
* /Views/Shared/\<Action-Name>.cshtml
* /Pages/Shared/\<Action-Name>.cshtml

#####長くなるので、エリア毎に認証スキーム設定に関しては別の記事でまとめる