ASP.NET Core で Display Modes を使う

ASP.NET MVC 5 で作成したアプリでは Display Modes を使っていたのですが、ASP.NET Core MVC には Display Modes が実装されていません。その Webアプリを Core MVC にマイグレーションしたかったので、できるだけ手間をかけずに Display Modes を実装する方法を考えてみました。


マイグレーションの場合は、修正する箇所が多くなるので、少し工夫するかどうかで、作業効率が大きく変わってくると思います。

## ユーザーエージェントでの振り分

デバイス毎にどう振り分けるかですが、その Web アプリでは、タブレットは画面が大きいので デスクトップ PC と同じデザインということにしていて、スマートフォンとそれ以外の2つに区分しています。そして、ユーザーエージェントの文字列から、以下のようなルーチンで判断していました。現実には、Windows Phone と iPod は件数が殆どないので無視をして iPhone と Android Mobile だけでもいいのかもしれません。

``` MyUtilities.cs
public static bool IsSmartPhone(string userAgent)
{
    if (userAgent == null)
        return false;
    return userAgent.Contains("iPhone") ||
        (userAgent.Contains("Android") && userAgent.Contains("Mobile")) ||
        userAgent.Contains("Windows Phone") ||
        userAgent.Contains("iPod") ;
}
```

まず、最初に考えつくのは、下のようにアクションの中で`Request.Headers["User-Agent"]` を使ってユーザーエージェントを取得して View を切り替えてやる方法です。View で名前は`return View("Index.Mobile")` のように直接指定してもいいのですが、`ControllerContext.ActionDescriptor.ActionName` でアクション名を取得できるので、このようにしておくと名前の変更をしなくてもいいので貼り付けだけでよくなります。 

```csharp
namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            if (MyUtilities.IsMobile(Request.Headers["User-Agent"]))
                return View(ControllerContext.ActionDescriptor.ActionName + ".Mobile");
            return View();
        }
    }
}
```

## Controller クラスを拡張する

上の方法だと View に渡すオブジェクトがあるとその名前を指定する必要があるのが手間です。そこで、Controller クラスを拡張したクラスを以下のよう作ってやります。

``` MyController.cs
using Microsoft.AspNetCore.Mvc;

namespace timejnet.Models
{
    public class MyController: Controller
    {
        public ViewResult MyView()
        {
            if(MyUtilities.IsSmartPhone(Request.Headers["User-Agent"]))
                return View(ControllerContext.ActionDescriptor.ActionName + ".Mobile");
            return View();
        }

        public ViewResult MyView(object model)
        {
            if (MyUtilities.IsSmartPhone(Request.Headers["User-Agent"]))
                return View(ControllerContext.ActionDescriptor.ActionName + ".Mobile", model);
            return View(model);
        }

        public ViewResult MyView(string viewName)
        {
            if (MyUtilities.IsSmartPhone(Request.Headers["User-Agent"]))
                return View(viewName + ".Mobile");
            return View(viewName);
        }

        public ViewResult MyView(string viewName, object model)
        {
            if (MyUtilities.IsSmartPhone(Request.Headers["User-Agent"]))
                return View(viewName + ".Mobile", model);
            return View(viewName, model);
        }
    }
}
```

このクラスを作ってしまうと以下のように従来の Display Modes とかなり近い形で書く事ができるようになります。

```csharp
namespace MyApp.Controllers
{
    public class HomeController : MyController
    {
        public ActionResult Index()
        {
            return MyView();
        }
    }
}
```

View をオーバーライドすればアクションでの修正はなくなりますが、一方で、View の該当のディレクトリーに .Mobile という添字がある cshtml ファイルがあるかどうかをチェックするルーチンを追加する必要があるので、どちらがいいのかというのはケースによって違ってくると思います。

## View での振り分け

Controller から直接呼び出す View は共通にして、それより上位の View で振り分ける場合もあると思いますが、その場合は、View では、`Context.Request.Headers["User-Agent"]` でユーザーエージェントを取得できるので、以下の用にすれば振り分けができます。

```csharp
@{
    if (MyUtilities.IsSmartPhone(Context.Request.Headers["User-Agent"]))
    { Layout = "_Layout.iPhone"; }
    else
    { Layout = "_Layout"; }
}
```

これだと複数行になるので、以下のような関数を定義してやると作業が楽になります。

``` MyUtilities.cs
public static string GetDisplayName(Microsoft.AspNetCore.Http.HttpContext context)
{
    string userAgent = context.Request.Headers["User-Agent"];
    if (IsSmartPhone(userAgent))
        return ".Mobile";
    return "";
}
```

「 + MyUtilities.GetDisplayName(Context);」を貼り付けするだけです。

```csharp
@{
    Layout = "_Layout" + MyUtilities.GetDisplayName(Context); 
}
```