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認証を試す
こちらのドキュメントを参考にして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を作るサンプルです。
モデルを準備
まずはノードを表すモデルとデータを用意します。
// ノード 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 - クッキーを使う
クッキーを設定する
レスポンスにクッキーを設定するには、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 @*
名前空間を指定してもエラーにならないようで小一時間悩みました。