AutoMapper - コンストラクタを使ってマッピングする

ちょっと発見だったのでメモ。

AutoMapperでは、マッピング先のコンストラクタを呼び出してマッピングすることができます。マッピング先にはpublicなsetterプロパティも必要ありません。

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

モデルとプロファイルを用意して、

// マッピング元
public class SampleSrc {
    public int Id { get; set; }
    public string Value { get; set; }
}

// マッピング先
public class SampleDst {
    // このコンストラクタを使ってマッピングする
    public SampleDst(int id, string value) {
        Console.WriteLine($"{nameof(SampleDst)} constructor({id}, {value})");
        // SampleDst constructor(1, x)

        Id = id;
        Value = value;
    }

    // getterプロパティのみ
    public int Id { get; }
    public string Value { get; }
}

// マッピングのプロファイル
public class SampleProfile : Profile {
    public SampleProfile() {
        CreateMap<SampleSrc, SampleDst>();
    }
}

マッピングします。

// 準備
Mapper.Initialize(config => {
    config.AddProfile<SampleProfile>();
});
Mapper.AssertConfigurationIsValid();

// マッピング元
var src = new SampleSrc {
    Id = 1,
    Value = "x",
};

// SampleSrc => SampleDst
var dst = Mapper.Map<SampleDst>(src);

// 確認
Console.WriteLine(dst.Id);  // 1
Console.WriteLine(dst.Value);   // x

参考

ASP.NET Core MVC - ビューを相対パスで指定する

ASP.NET Core MVCではビュー名を指定してビューを探すと思いますが、ビュー名だけでなくて相対パスも指定できますよというメモ。

相対パス~/ではじまる文字列です。

Controller.Viewメソッドの引数viewName

ViewResultを生成するメソッドですね。このメソッドの引数viewNameに相対パスを指定できます。

// サンプルコントローラ
public class SampleController : Controller {
    public IActionResult Index() {
        // 引数に相対パスを指定できる
        return View("~/Views/Sample/Index.cshtml");
    }
}

部分ビューのタグヘルパーPartialTagHelperのname属性

PartialTagHelperはASP.NET Core 2.1で追加されました。部分ビューをレンダリングするタグヘルパーです。

name属性に相対パスを指定できます。

@* name属性に相対パスを指定できる *@
<partial name="~/Views/Sample/_Partial.cshtml" />

ViewComponent.Viewメソッドの引数viewName

ViewViewComponentResultを生成するメソッドの引数viewNameも相対パスを指定できます。

// サンプルビューコンポーネント
public class SampleViewComponent : ViewComponent {
    private Task ActionAsync() => Task.CompletedTask;

    public async Task<IViewComponentResult> InvokeAsync() {
        // なにかの処理
        await ActionAsync();

        // 引数に相対パスを指定できる
        return View("~/Views/Components/Sample/Default.cshtml");
    }
}

部分ビューやビューコンポーネントをSharedフォルダに置くのはちょっと気が引けるけど、いくつかのビューで使い回ししたいときがありましたが、そんなときに相対パスを使うのもありかなと。ケースバイケースでしょうが。

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が生成されました。