##ユーザマスタの作成
ユーザ名を表示させるにあたり、Identityで作成したテーブルに追加することも可能だとは思うのですが、あまり触りたくない・・・。怖い。
ってことで、別途ユーザマスタを作成する方針で、以下処理を行います。
- ユーザマスタを作成
- 登録画面に項目追加
- 共通ヘッダーの表示を変更
##ユーザEntityクラスの作成
MVCモデルで正しいのかはわからないのですが、Models配下にEntityフォルダを作成し、ユーザクラスを作成していきます。
キーは、identityで作成されてデータが作成されている、[AspNetUsers].[Id] / nvarchar(450) を使用します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace sample2_1.Models.Entity
{
[Table("M_USER")]
public class MUser
{
// ユーザID
[Key]
[MaxLength(450)]
[Column("USER_ID")]
public string UserId { get; set; }
// ユーザ名
[Column("USER_NM")]
[MaxLength(100)]
public string UserNm { get; set; }
}
}
##DbContextファイルの作成
DbContextを作成することによって、テーブルとしての扱いが行われます。
App用のコンテキストということで、「AppDbContext」を作成しました。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using sample2_1.Models.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace sample2_1.Models
{
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<AppDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
/* 作成するテーブル一覧 */
public DbSet<MUser> MUsers { get; set; }
}
}
これで、Entityの設定はOK!
##テーブルの作成
本ブログの2_2で[migration]コマンドでSQLを作成すると説明いたしましたが、やっと説明できる!
2_2での説明:
1.テーブルの構成情報をEntity形式で作成
2.Migrationsコマンドで、SQL文を生成
3.Migrationsコマンドで、DBへ適用
##テーブルの作成(1)
上の「ユーザEntityクラスの作成」で実施済み
##テーブルの作成(2)
「パッケージマネージャ コンソール」から以下コマンドを実行
※ -Contextを指定しないと、Identity側のDbContextも把握されているので、エラーになります。
PM> add-migration -Context AppDbContext 20191023v1
これを行うと、プロジェクトに[Migrations]フォルダ及び、ユーザテーブルが作成できるSQLソースが生成されます。
作成されたソースファイル:
using Microsoft.EntityFrameworkCore.Migrations;
namespace sample2_1.Migrations
{
public partial class _20191023v1 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "M_USER",
columns: table => new
{
USER_ID = table.Column<string>(maxLength: 450, nullable: false),
USER_NM = table.Column<string>(maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_M_USER", x => x.USER_ID);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "M_USER");
}
}
}
##テーブルの作成(3)
最後に、DBへ更新します。
PM> Update-Database -Context AppDbContext
Applying migration '20191022160421_20191023v1'.
Done.
正常終了!これで、テーブル情報の登録はOK!
SQLServerでの確認をしてみましょう!
想定通り、M_USERテーブルが作成されたことを確認できました。
##[閑話休題]私がよく使うmigrationコマンド
SQLソースファイルを作成する(xxxxを今日日付+v1にして、実行)
PM> add-migration -Context AppDbContext xxxx
DBへ更新を反映
PM> Update-Database -Context AppDbContext
実際にDBへ実行するSQL文を生成します。(サーバへのリリース時によく使う)
PM> Script-Migration -Context AppDbContext
##登録画面への項目追加
以下のファイルを編集します。
Areas\Identity\Pages\Account\Register.cshtml
変更前の状態(Register.cshtml):
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
ここに、ユーザ名を追加します。
まず、このフィールドは別ファイルで管理されてます。
Areas\Identity\Pages\Account\Register.cshtml.cs
変更前の状態(Register.cshtml.cs):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace sample2_1.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
このInputクラスに含まれている項目が画面に入力項目になっていることが確認できます。
ここに、ユーザ名を追加します。
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
/* ユーザ名追加 */
[Required]
[Display(Name = "ユーザ名")]
public string UserNm { get; set; }
}
次に、画面上に追加します。
@* ユーザ名の追加 *@
<div class="form-group">
<label asp-for="Input.UserNm"></label>
<input asp-for="Input.UserNm" class="form-control" />
<span asp-validation-for="Input.UserNm" class="text-danger"></span>
</div>
これで、画面上にユーザ名のフィールドが追加されました。
Inputの情報を取得して、DBへ登録している箇所にも追加が必要ですので、追加します。
##登録画面からのDB登録処理
元々あったメールアドレスの登録後に、処理を追加します。
/* ユーザ名の登録 */
var mUser = new MUser { UserId = user.Id, UserNm = Input.UserNm };
var con = new AppDbContext();
con.Add(mUser);
var result2 = con.SaveChanges();
if (result2 == 0)
{
_logger.LogInformation("ユーザ名の登録も完了しました。");
}
else
{
_logger.LogError("ユーザ名の登録は失敗しました。");
foreach (var error in result.Errors)
{
_logger.LogError(error.Description);
}
}
これで、登録はOK
実際に登録してみましょう!
画面入力から。
問題なく、登録OK。
DBへ正しく追加されているかを確認。
OK!
##共通部の実装解析
最後に、ログイン後の上部にメールアドレスではなく、ユーザ名を表示できるようにしましょう!
上部に表示されているのは、テンプレートのHTMLが使用されております。
\Views\Shared_LoginPartial.cshtml
実際に中身を見てみましょう。
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
ログインされていれば、「Hello @User.Identity.Name!」を表示するように実装されております。
ログインされていない場合、Register、Loginのリンクボタンを表示するようにしております。
ということで、まず画面上にユーザ名を取得する必要がございます。
本当は一箇所だけ直せはユーザ名が表示されるようになるのが理想なんですが、いい方法が見つかりませんでした。
詳しい方、コメントで修正方法を教えて下さい。orz
ってことで対応したのが、Controllerにユーザ名を共通して取得しておく方法。
\Controllers\HomeController.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
namespace sample2_1.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
##ログイン後に、各Controllerで取得できるユーザ情報について
Controllerでは、Userオブジェクトがアクセスできる状態になっております。
そのUserオブジェクトから、ユーザIDを取得し、今回作ったユーザ名を取得してみましょう!
ログイン後にデバッグ実行し、Userオブジェクトの中身を見てみると、
User.Identities内の、配列内にユーザIDを確認できましたので、取得します。
##ユーザ情報の取得→画面へ伝達
配列のタイプを判定し、ユーザID取得する子ができました。
そのままDBからユーザ名を取得し、画面に返します。
public IActionResult Index()
{
/* ログインされている場合、共通受け渡しデータにユーザ名を入れておく */
if (User.Identity.IsAuthenticated)
{
var user_id = "";
var user_nm = "";
// ユーザIDの特定
foreach (var i in this.User.Identities)
{
foreach (var n in i.Claims)
{
if (n.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")
{
user_id = n.Value;
}
if (user_id != "") break;
}
if (user_id != "") break;
}
// ユーザマスタから情報取得
using (var db = new AppDbContext())
{
// [出力]
foreach (var mUser in db.MUsers.Where(m => m.UserId == user_id))
{
user_nm = mUser.UserNm;
break;
}
}
ViewData["UserNm"] = user_nm;
}
return View();
}
最後に、共通部分の修正を加えて、完成となります。
もう疲れたので、TanaToruくらいには労ってもらいましょう。
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">お疲れさまです、 @ViewData["user_nm"] さん</a>
では、ログイン後の画面へ
化けとるがな・・・。
色々見たところ、なぜかファイルの文字コードがSJISになってました。
UTF-8,BOM付き へサクラエディタで変更し、再読み込み!
OK!
いやー、長かった。
ほんとにお疲れさまです!
##修正後のソースファイル一式
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace sample2_1.Models.Entity
{
[Table("M_USER")]
public class MUser
{
// ユーザID
[Key]
[MaxLength(450)]
[Column("USER_ID")]
public string UserId { get; set; }
// ユーザ名
[Column("USER_NM")]
[MaxLength(100)]
public string UserNm { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using sample2_1.Models.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace sample2_1.Models
{
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<AppDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
/* 作成するテーブル一覧 */
public DbSet<MUser> MUsers { get; set; }
}
}
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
@* ユーザ名の追加 *@
<div class="form-group">
<label asp-for="Input.UserNm"></label>
<input asp-for="Input.UserNm" class="form-control" />
<span asp-validation-for="Input.UserNm" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
/* ユーザ名の追加 */
using sample2_1.Models.Entity;
using sample2_1.Models;
namespace sample2_1.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
/* ユーザ名追加 */
[Required]
[Display(Name = "ユーザ名")]
public string UserNm { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
/* ユーザ名の登録 */
var mUser = new MUser { UserId = user.Id, UserNm = Input.UserNm };
var con = new AppDbContext();
con.Add(mUser);
var result2 = con.SaveChanges();
if (result2 == 0)
{
_logger.LogInformation("ユーザ名の登録も完了しました。");
}
else
{
_logger.LogError("ユーザ名の登録は失敗しました。");
foreach (var error in result.Errors)
{
_logger.LogError(error.Description);
}
}
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
namespace sample2_1.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
/* ログインされている場合、共通受け渡しデータにユーザ名を入れておく */
if (User.Identity.IsAuthenticated)
{
var user_id = "";
var user_nm = "";
// ユーザIDの特定
foreach (var i in this.User.Identities)
{
foreach (var n in i.Claims)
{
if (n.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")
{
user_id = n.Value;
}
if (user_id != "") break;
}
if (user_id != "") break;
}
// ユーザマスタから情報取得
using (var db = new AppDbContext())
{
// [出力]
foreach (var mUser in db.MUsers.Where(m => m.UserId == user_id))
{
user_nm = mUser.UserNm;
break;
}
}
ViewData["user_nm"] = user_nm;
}
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">お疲れさまです @ViewData["user_nm"] さん</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
##参考サイト
EntityFrameworkCoreを.NET Core コンソールアプリでCodeFirstに使う:
https://qiita.com/namoshika/items/7d1bf911bc03ed03e17d
【是非お試しください】※完全無料です
TanaToru -本棚管理サービス-
https://app.zero-one-system.co.jp/TanaToru/