ASP.NET Core MVC - 部分ビューを再帰で呼び出す

部分ビューから自身の部分ビューを再帰で呼び出しできるよね?と思って試したらできましたというお話。

以下は次のul・liを作るサンプルです。

f:id:ichiroku11:20180817221154p:plain

モデルを準備

まずはノードを表すモデルとデータを用意します。

// ノード
public class Node {
    // 名前
    public string Name { get; set; }
    // 子ノード一覧
    public IEnumerable<Node> Children { get; set; } = Enumerable.Empty<Node>();
}

// サンプルデータ
public static class Sample {
    // 適当なデータを作る
    public static IEnumerable<Node> GetNodes() => new[] {
        new Node {
            Name = "A",
            // 子は2つ
            Children = new[] {
                new Node { Name = "A1", },
                new Node { Name = "A2", }
            },
        },
        new Node {
            Name = "B",
            // 子が3つ
            Children = new[] {
                new Node { Name = "B1", },
                new Node {
                    Name = "B2",
                    // 子が2つ
                    Children = new[] {
                        new Node { Name = "B2-1", },
                        new Node { Name = "B2-2", },
                    },
                },
                new Node { Name = "B3", }
            },
        },
        new Node { Name = "C", },
    };
}

partialタグを使って部分ビューを呼び出す

続いてindex.cshtml。

partialタグはHtml.PartialAsyncやHtml.RenderPartialAsyncに相当するタグヘルパーです。ASP.NET Core 2.1から使えるようになりました。

このpartialタグを使って部分ビューを呼び出してレンダリングしています。

<!-- index.cshtml -->
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
   <meta name="viewport" content="width=device-width" />
   <title>Index</title>
</head>
<body>
    // 部分ビューを呼び出す
    <partial name="_List" model="Sample.GetNodes()" />
</body>
</html>

部分ビューから部分ビューを呼び出す

続いて部分ビュー(_List.cshtml)です。自身の部分ビューのレンダリング再帰で呼び出しています。

<!-- _List.cshtml -->
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model IEnumerable<Node>

<ul>
    @foreach (var node in Model) {
        <li>
            @node.Name
            @if (node.Children.Any()) {
                // 自身の部分ビューを呼び出す(再帰)
                <partial name="_List" model="node.Children" />
            }
        </li>
    }
</ul>

これで想定通りのHTMLが生成されました。

ASP.NET Core MVC - クッキーを使う

ASP.NET Core MVCのクッキーの勉強メモです。

クッキーを設定する

レスポンスにクッキーを設定するには、IResponseCookies.Appendメソッドを使います。

// クッキーを設定する
HttpContext.Response.Cookies.Append("test", "Hoge");

// レスポンスに追加されるSet-Cookieヘッダ
//Set-Cookie: test=Hoge; path=/

ASP.NETのときは、1つのクッキーに複数のキーバリューを簡単に設定できましたが、Coreでは自分で実装する必要がありそうです。そういった使い方をあまりしなくなったのかな。

クッキーを取得する

リクエストからクッキーを取得するには、IRequestCookieCollectionインターフェイスを使います。

// 次のようなCookieヘッダが送られてくるとして
//Cookie: test=Hoge

// クッキーを取得
var value = HttpContext.Request.Cookies["test"];
Console.WriteLine(value);
// Hoge

// TryGetValueメソッドを使ってクッキーを取得するのもあり
if (HttpContext.Request.Cookies.TryGetValue("test", out var value)) {
    Console.WriteLine(value);
    // Hoge
}

インデクサで取得する場合、クッキーがないと戻り値はnullになります。

クッキーを削除する

クッキーを削除(=HTTPレスポンスに期限切れの空のクッキーを設定)するには、IResponseCookies.Deleteメソッドを使います。

// クッキーを削除する
HttpContext.Response.Cookies.Delete("test");

// レスポンスのSet-Cookieヘッダ
//Set-Cookie: test=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax

とりあえず基本はこんな感じですかね。

ASP.NET Core MVC - @addTagHelperは名前空間ではなくアセンブリ名を指定する

タグヘルパーを作ってみていきなりハマったのでもメモしておきます。

@addTagHelperディレクティブでタグヘルパーを使えるようにしますが、このときに指定するのは名前空間ではなくアセンブリ名です。

例えば次のようなTagHelperを作ったとして、

// アセンブリはWebAppとして
namespace WebApp.TagHelpers {
    // 適当なタグヘルパー
    public class SampleTagHelper : TagHelper {
        public override void Process(TagHelperContext context, TagHelperOutput output) {
            // 何かする
        }
    }
}

これをビューで使うためには次のようにアセンブリ名を指定します。

@* アセンブリ名を指定する *@
@addTagHelper *, WebApp

@*
こうではない
@addTagHelper *, WebApp.TagHelpers
@*

名前空間を指定してもエラーにならないようで小一時間悩みました。

参考

ASP.NET Core のタグ ヘルパー作成 | Microsoft Docs

T-SQL - 再帰CTEで指定した期間の日付一覧を作る

T-SQLのメモです。

開始日と終了日を指定して、その期間の日付一覧を取得したいと思います。

再帰CTE(共通テーブル式)を使って書くとこんな感じになるのかなと。

-- 指定した期間の日付一覧を取得するクエリ

-- @start 開始日(その日は含む)
-- @end 終了日(その日は含まない、とする)
declare @start date = N'2018-07-01';
declare @end date = N'2018-08-01';

with Dates(Date)
as (
    select @start
    union all
    select dateadd(day, 1, Date)
    from Dates
    where Date < dateadd(day, -1, @end)
)
select *
from Dates
-- 再帰回数を無制限にするなら
option (maxrecursion 0);

再帰CTEを使わない方法をちょっと考えてみたんですが思いつかなかったです。

参考

ASP.NET Core MVC - TempDataを使う

ASP.NET Core MVCの勉強の記録です。

今回はTempDataを使ってみたいと思います。いつものようにDocsを参考にしています。

ASP.NET Core でのセッションとアプリの状態 | Microsoft Docs

StartupクラスでTempDataを構成する

まずはTempDataを使うために準備します。と言ってもStartup.ConfigureServicesでAddMvcメソッドを呼び出すだけでいいみたいです。

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        services.AddMvc();

        services.Configure<CookieTempDataProviderOptions>(options => {
            // TempDataのクッキーの名前を変えるなら
            options.Cookie.Name = "temp";
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
        app.UseMvcWithDefaultRoute();
    }
}

コード中のコメントに「TempDataのクッキー」とありますが、CoreではないMVCを使っていた方は疑問に思うかもしれません。詳しくは後述します。

TempDataの読み書き

TempDataに文字列や数値を読み書きする場合は従来と変わりません。

// TempDataに文字列を書き込む
TempData["key"] = "Hoge";

// TempDataから文字列を読み込む
var value = (string)TempData["key"];

ただTempDataにオブジェクトを直接読み書きすることはできないようです。

セッションと同じようにJSON文字列として読み書きする拡張メソッドを用意すればいいのかな。

// TempDataにオブジェクトを読み書きする拡張メソッドを用意する
public static class TempDataDictionaryExtensions {
    // TempDataにオブジェクトを書き込む
    public static void SetObject<TObject>(this ITempDataDictionary tempData, string key, TObject obj) {
        var json = JsonConvert.SerializeObject(obj);

        // JSON文字列として書き込む
        tempData[key] = json;
    }

    // TempDataからオブジェクトを読み込む
    public static TObject GetObject<TObject>(this ITempDataDictionary tempData, string key) {
        // JSON文字列として読み込む
        var json = (string)tempData[key];

        return string.IsNullOrEmpty(json)
            ? default(TObject)
            : JsonConvert.DeserializeObject<TObject>(json);
    }
}

// 用意した拡張メソッドを使う
// TempDataにSampleクラスを書き込む
TempData.SetObject("key", new Sample());

// TempDataからSampleクラスを読み込む
TempData.GetObject<Sample>("key");

TempDataとクッキー

ASP.NET Core 2.0からデフォルトではTempDataのストア(格納先)にクッキーが使われるようになりました。

クッキーに格納されるデータは暗号化されるとのことです。

またTempDataとCookieのHTTPヘッダの関係は、

  • TempDataに書き込むとクッキーが追加される(Set-Cookieされる)
  • TempDataから読み込むとクッキーが削除される(空のクッキーでSet-Cookieされる)

といった動きになっていました。

TempDataのストアをセッションにする

TempDataのストア(格納先)をセッションにする場合は、Statupクラスでセッションを使うように構成しつつ、AddSessionStateTempDataProviderメソッドを使います。

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        // サービスにセッションを追加
        services.AddSession(options => {
            // セッションクッキーの名前を変えるなら
            options.Cookie.Name = "session";
        });

        // MVC関連のサービスを追加
        services.AddMvc()
            // TempDataのストアをセッションにする
            .AddSessionStateTempDataProvider();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
        // セッションを使う
        app.UseSession();

        app.UseMvcWithDefaultRoute();
    }
}

クッキーとセッションの使い分けはケースバイケースだろうなと思いますが、ちょっと悩みそう。

以上TempDataの基本的な使い方でした。