LINQ - シーケンスの最初(最後)に要素を追加する

今さら感いっぱいですが、気づいたらEnumerableに次の拡張メソッドが追加されていました。

  • Enumerable.Prependメソッド

    • シーケンスの最初に要素を追加する
  • Enumerable.Appendメソッド

    • シーケンスの最後に要素を追加する

今まではちょっと微妙かなと思いつつもConcatを使った記憶がありますが、今度からこっちを使おうと思います。

ということでサンプルコード。

シーケンスの最初に要素を追加する

// シーケンス
var source = new[] { 2, 3, 4 };

// Prepend:シーケンスの最初に1を追加する
foreach (var item in source.Prepend(1)) {
    Console.WriteLine(item);
}
// 1
// 2
// 3
// 4

シーケンスの最後に要素を追加する

// シーケンス
var source = new[] { 2, 3, 4 };

// Append:シーケンスの最後に5を追加する
foreach (var item in source.Append(5)) {
    Console.WriteLine(item);
}
// 2
// 3
// 4
// 5

ASP.NET Core MVC - Cookie認証を試す

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

こちらのドキュメントを参考にしてIdentityを使わないCookie認証を試しました。

ASP.NET Core Identity なしでの cookie 認証を使用します。 | Microsoft Docs

Cookie認証はいわゆるフォーム認証だと思うんですが、もしかして今はそう言わないのかも。

仕組みや流れを理解するために最小限のサンプルコードを目指します。

Startupクラス

まずはStartupクラスのConfigureServicesメソッドとConfigureメソッドから見てみましょう。

Startup.ConfigureServices

ConfigureServicesメソッドでは、AddAuthenticationメソッドとAddCookieメソッドを使ってクッキー認証に必要なサービスを登録します。

また、AddMvcメソッドを使ってMVCに必要なサービスを登録しつつ、サイト全体にアクセス制御を設定するグローバルフィルタも追加しています。

public void ConfigureServices(IServiceCollection services) {
    // クッキー認証に必要なサービスを登録
    services
        .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options => {
            // クッキーの名前を変える
            options.Cookie.Name = "auth";

            // リダイレクトするログインURLも小文字に変える
            // ~/Account/Login => ~/account/login
            options.LoginPath = CookieAuthenticationDefaults.LoginPath.ToString().ToLower();
        });

    // MVCで利用するサービスを登録
    services.AddMvc(options => {
        // グローバルフィルタに承認フィルタを追加
        // すべてのコントローラでログインが必要にしておく
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    });

    services.Configure<RouteOptions>(options => {
        // URLは小文字にする
        options.LowercaseUrls = true;
    });
}

正直なところAddAuthenticationに渡すスキーマについてはまだしっかり理解していない部分です。 ドキュメントにクッキー認証の複数のインスタンスとあったけど、外部IDでログインする場合も考慮するってこと?

Startup.Configure

Configureメソッドでは、UseAuthenticationメソッドで認証のミドルウェアをパイプラインを追加します。

このミドルウェアは、クッキーをもとに作ったプリンシパルをHttpContext.Userプロパティにセットしてくれるようです。

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    }

    // パイプラインに認証のミドルウェアを追加する
    // HttpContext.Userをセットしてくれる
    app.UseAuthentication();

    // パイプラインにMVCのミドルウェアを追加する
    app.UseMvcWithDefaultRoute();
}

HomeController

今回のサンプルではコントローラを2つ用意します。

  • HomeController...認証が必要なコントローラ
  • AccountController...認証が不要でログインなどを行うコントローラ

まずは認証が必要なHomeControllerです。 Indexアクションを用意して、ログインしたユーザ名を表示するようにしておきます。

認証はグローバルフィルタで設定してあるので、コントローラに属性を設定する必要はありません。

// ログインが必要なコントローラ
public class HomeController : Controller {
    public IActionResult Index() {
        // ユーザ名を表示する
        // User.Identity.Nameは"user01@example.jp"
        return Content(User.Identity.Name);
    }
}

AccountController

AccountControllerでは3つのアクションを用意します。

  • ログインビューを返すアクション
  • ログイン処理(POST)のアクション
  • ログアウト処理のアクション

1つずつ見ていきましょう。

ログインビューを返すアクションとログインビュー

グローバルフィルタで認証が設定されているので、アクションメソッドにAllowAnonymous属性を指定して匿名アクセスを許可します。

// ログインビュー
[AllowAnonymous]
public IActionResult Login() {
    return View();
}

ビューは本当ならログインIDとパスワードのinput要素があるのかなと思いますが、今回は省略して単純にPOSTするだけにしました。

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>

<html>
<head>
   <meta name="viewport" content="width=device-width" />
   <title>Login</title>
</head>
<body>
    <form asp-action="Login" method="post">
        @* ログインIDやパスワードのinput要素は省略 *@
        <button type="submit">ログイン</button>
    </form>
</body>
</html>
ログイン処理のアクション

ログイン処理のアクションの肝は、プリンシパルを作ってHttpContext.SignInAsyncメソッドを呼び出すところだと思います。

HttpContext.SignInAsyncメソッドを呼び出すと、認証クッキーをレスポンスに追加できます。

// ログインのポスト処理
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(IFormCollection fromValues) {
    // サインインに必要なプリンシパルを作る
    // 本当ならユーザIDとパスワードからユーザを特定して・・・という処理が入るはず
    var claims = new[] {
        // 適当なユーザ名を登録しておく
        new Claim(ClaimTypes.Name, "user01@example.jp"),
    };
    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var principal = new ClaimsPrincipal(identity);

    // サインイン
    // 認証クッキーをレスポンスに追加
    await HttpContext.SignInAsync(principal);

    // ログインが必要なアクションにリダイレクト
    return RedirectToAction("Index", "Home");
}

今回は適当なプリシンパルを作りました。 本当ならログインID/パスワードの検証やDBアクセスなどをすると思いますが省略しています。

ログアウト処理のアクション

最後にログアウト処理です。

HttpContext.SignOutAsyncメソッドを呼び出すと、レスポンスから認証クッキーを削除できます。

// ログアウト
[AllowAnonymous]
public async Task<IActionResult> Logout() {
    // サインアウト
    // 認証クッキーをレスポンスから削除
    await HttpContext.SignOutAsync();

    // ログインビューにリダイレクト
    return RedirectToAction("Login");
}

基本的なクッキー認証はこんな感じかなと思います。

動きとしては、まず~/home/indexにアクセスすると認証されていないので~/account/loginにリダイレクトされます。 ~/account/loginにPOSTするとサインインして~/home/indexにリダイレクトされて、user01@example.jp と表示されるようになります。

おしまい。

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