しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core MVC を使ってアプリケーションを書いた際に悩んだポイント

サンプルとか超小規模以外の ASP.NET Core MVC アプリケーションを書いてますが、いろいろと悩んだりはまったりしたポイントがあったのでまとめました。

まだまだありそうなので、ちょっとずつまとめようかと思います。

addTagHelper にはアセンブリ名を指定する

これに気が付かずに 1 時間以上無駄にしました。最初から書かれている Tag Helpers の追加コードが名前空間に見えたので、ずっと名前空間を指定していました。

ドキュメントをちゃんと読めばアセンブリと書いてあったのですが、完全に見落としていました。

f:id:shiba-yan:20170926232200p:plain

このプロジェクトの場合は Tag Helper は WebApplication14.TagHelpers 名前空間にいますが、アセンブリ名を指定しないといけないので WebApplication14 とだけ書きます。

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WebApplication14

これで Razor の編集時に Tag Helper として認識されて使えるようになります。簡単なことなのに、とても時間を無駄にしてしまいました。

ModelState は ViewContext が持っている

MVC 5 までは Razor 内でも ModelState を直接触れましたが、Core MVC の Razor では ViewContext 経由で触る必要があるみたいです。

@if (!ViewContext.ModelState.IsValid)
{
    <p>モデルバインドでエラーがあった時に表示したいメッセージとか</p>
}

Tag Helper でも同様に ViewContext 経由で触れば良いみたいなので、属性を付けて DI に任せます。

public class SampleTagHelper : TagHelper
{
    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (ViewContext.ModelState.IsValid)
        {
            output.SuppressOutput();
        }
    }
}

場所さえわかってしまえば、後はこれまで通り Model Binding の結果を利用して色々出来ますね。

TempData には独自クラスが入らない

TempData にこれまで通り独自のクラスを入れようとしたら、シリアライズのエラーが出ました。TempDataDictionary の実装を調べたら、プリミティブ型以外はエラーにするようになっていました。

独自のクラスを入れる場合には、予め JSON などでシリアライズしておく必要があります。

public static class TempDataExtensions
{
    public static TValue Get<TValue>(this ITempDataDictionary tempData, string key)
    {
        var value = tempData[key] as string;

        return value == null ? default(TValue) : JsonConvert.DeserializeObject<TValue>(value);
    }

    public static void Set(this ITempDataDictionary tempData, string key, object value)
    {
        tempData[key] = JsonConvert.SerializeObject(value);
    }
}

なので適当に拡張メソッドを作って解決しました。これで TempData にクラスが入ります。

Area 単位で _ViewStart.cshtml / _ViewImports.cshtml が必要

Core MVC で Area を追加すると、大体は以下のような構造になるはずです。構造自体は MVC 5 の時と変わりませんが、このままだとレイアウトと Tag Helpers が使えませんでした。

f:id:shiba-yan:20170926233813p:plain

実行してブラウザで表示すると、以下のようにレイアウトの当たっていない画面になりました。

f:id:shiba-yan:20170926233746p:plain

MVC 5 の場合はルートにある _ViewStart.cshtml が自動で反映されていましたが、Core MVC の場合は Area 向けにも _ViewStart.cshtml と _ViewImports.cshtml を用意する必要がありました。

2 つを追加して表示し直すと、ちゃんとレイアウトが反映されました。

f:id:shiba-yan:20170926233910p:plain

細かい部分で MVC 5 と Core MVC の挙動に差が出ているようです。地味にはまりました。

select にはビューで予め option を追加出来る

これまで Html Helper で特殊対応していた select での空項目ですが、Core MVC の Razor では以下のように非常にシンプルに書くことが出来ます。

<select asp-for="Item" asp-items="AllItems">
  <option value="">選択してください</option>
</select>

asp-items で指定した項目は予め追加しておいた項目の後に追加されるようになってます。

Core MVC の Razor は控えめに言ってかなり快適に書くことが出来ますね。ループ周りも綺麗に書ければ嬉しいのですが、Template 的な機能がまだ用意されていないので難しいです。