今回やること
アカウント認証の実装の続きです。
前回足りてない修正
前回の状態で一度起動すると、エラーになってしまい起動できませんでした。
そこで下記2つの修正を行いました。
①Startup.csのConfigureServicesにAddRazorPagesを追加
// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext<ApplicationDbContext>(options=>options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));services.AddDatabaseDeveloperPageExceptionFilter();//services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)// .AddEntityFrameworkStores<ApplicationDbContext>();services.AddIdentity<SMSUser,IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();services.AddControllersWithViews();services.AddRazorPages(); // 追加部分}
②_LoginPartial.cshtmlを修正
@using Microsoft.AspNetCore.Identity
@inject SignInManager<SMSUser> SignInManager ←ここの型引数を独自ユーザクラスにする
@inject UserManager<SMSUser> UserManager ←ここの型引数を独自ユーザクラスにする
<ulclass="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Manage/Index"title="Manage">ようこそ @User.Identity.Name!</a></li><liclass="nav-item"><formclass="form-inline"asp-area="Identity"asp-page="/Account/Logout"asp-route-returnUrl="@Url.Action("Index","Home",new{area = ""})"><buttontype="submit"class="nav-link btn btn-link text-dark">ログアウト</button></form></li>
}
else
{
<liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Register">アカウント登録</a></li><liclass="nav-item"><aclass="nav-link text-dark"asp-area="Identity"asp-page="/Account/Login">ログイン</a></li>
}
</ul>
起動するとこのような画面になります。アカウント登録してみます。
URLは変わったんですが何も画面遷移しませんでした。
対応するビューとかコントローラーがないからですね。
デフォルトだとASP.NET Core Identityのライブラリにそのあたりが含まれているようで、
独自クラスを使ったとたんそれらが使えなくなったってことですね…
で、ライブラリに含まれているコントローラやらビューやらはスキャフォールディングで作れる機能があります。
プロジェクトを右クリックして、追加→新規スキャフォールディングアイテムでIDを選び、追加ボタンを押します。
自分に合った設定をします。オーバーライドはよくわからないので全部にしておきます。
(おそらくオーバーライドしたいもの(編集したい画面とか機能)をチェックすると、実体ができるのだと思いました。)
はいエラー。どうやらDbContextはIdentityDbContextを継承しないといけないようです。
なので前回の修正を戻します。
usingMicrosoft.AspNetCore.Identity.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore;usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceSalaryManagementSystem.Data{publicclassApplicationDbContext:IdentityDbContext//public class ApplicationDbContext : DbContext{publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}publicDbSet<SalaryManagementSystem.Models.Salary>Salary{get;set;}publicDbSet<SalaryManagementSystem.Models.SMSUser>SMSUsers{get;set;}}}
謎のテキストファイルが表示されました。うまくいったようです?
ユーザクラスが選べなかった(デフォルト:IdentityUserから変更できなかった)のは、DbContextの書き方がおかしかったようです。
usingMicrosoft.AspNetCore.Identity.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore;usingSalaryManagementSystem.Models;usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceSalaryManagementSystem.Data{publicclassApplicationDbContext:IdentityDbContext<SMSUser>//←ここに型引数が必要//public class ApplicationDbContext : DbContext{publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}publicDbSet<SalaryManagementSystem.Models.Salary>Salary{get;set;}publicDbSet<SalaryManagementSystem.Models.SMSUser>SMSUsers{get;set;}}}
もう一度スキャフォールディング…ユーザクラスは選べない。
追加を押すと、既にありますと言われますがOk。
さらにStartup.csを修正します。
// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext<ApplicationDbContext>(options=>options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));services.AddDatabaseDeveloperPageExceptionFilter();//services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)// .AddEntityFrameworkStores<ApplicationDbContext>();services.AddIdentity<SMSUser,IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders().AddDefaultUI();// ←追加services.AddControllersWithViews();services.AddRazorPages();}
登録画面には遷移するようになりました。ただ、入力項目等があるべき形になっていないので、修正する必要があります。
登録画面の修正
登録画面のロジックを修正していきます。
まずはコードビハインド。
(そもそもASP.NETはコードビハインドって言わない?WPFだけの話なのかな…)
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel.DataAnnotations;usingSystem.Linq;usingSystem.Text;usingSystem.Text.Encodings.Web;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Identity;usingMicrosoft.AspNetCore.Identity.UI.Services;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.RazorPages;usingMicrosoft.AspNetCore.WebUtilities;usingMicrosoft.Extensions.Logging;usingSalaryManagementSystem.Models;namespaceSalaryManagementSystem.Areas.Identity.Pages.Account{[AllowAnonymous]publicclassRegisterModel:PageModel{privatereadonlySignInManager<SMSUser>_signInManager;privatereadonlyUserManager<SMSUser>_userManager;privatereadonlyILogger<RegisterModel>_logger;//private readonly IEmailSender _emailSender;publicRegisterModel(UserManager<SMSUser>userManager,SignInManager<SMSUser>signInManager,ILogger<RegisterModel>logger//IEmailSender emailSender)){_userManager=userManager;_signInManager=signInManager;_logger=logger;//_emailSender = emailSender;}[BindProperty]publicInputModelInput{get;set;}publicstringReturnUrl{get;set;}publicIList<AuthenticationScheme>ExternalLogins{get;set;}publicclassInputModel{//[Required]//[EmailAddress]//[Display(Name = "Email")]//public string Email { get; set; }[Required][StringLength(100,ErrorMessage="{0} は {2} ~ {1} 文字で入力してください。",MinimumLength=6)][Display(Name="ユーザID")]publicstringUserId{get;set;}[Required][StringLength(100,ErrorMessage="{0} は {2} ~ {1} 文字で入力してください。",MinimumLength=6)][Display(Name="ユーザ名")]publicstringUserName{get;set;}[Required][StringLength(100,ErrorMessage="{0} は {2} ~ {1} 文字で入力してください。",MinimumLength=6)][DataType(DataType.Password)][Display(Name="パスワード")]publicstringPassword{get;set;}[DataType(DataType.Password)][Display(Name="パスワード再入力")][Compare("Password",ErrorMessage="パスワードが一致しません。")]publicstringConfirmPassword{get;set;}}publicasyncTaskOnGetAsync(stringreturnUrl=null){ReturnUrl=returnUrl;ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();}publicasyncTask<IActionResult>OnPostAsync(stringreturnUrl=null){returnUrl??=Url.Content("~/");ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();if(ModelState.IsValid){//var user = new SMSUser { UserName = Input.Email, Email = Input.Email };varuser=newSMSUser{SMSUserId=Input.UserId,SMSUserName=Input.UserName,IsLocked=false,LoginFailCount=0,CreateDate=DateTime.Now,UpdateDate=DateTime.Now};varresult=await_userManager.CreateAsync(user,Input.Password);if(result.Succeeded){_logger.LogInformation("User created a new account with password.");//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);//code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));//var callbackUrl = Url.Page(// "/Account/ConfirmEmail",// pageHandler: null,// values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },// 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>.");//if (_userManager.Options.SignIn.RequireConfirmedAccount)//{// return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });//}//else//{await_signInManager.SignInAsync(user,isPersistent:false);returnLocalRedirect(returnUrl);//}}foreach(varerrorinresult.Errors){ModelState.AddModelError(string.Empty,error.Description);}}// If we got this far, something failed, redisplay formreturnPage();}}}
次にビュー。
@page
@model RegisterModel
@{
ViewData["Title"] = "アカウント登録";
}
<h1>@ViewData["Title"]</h1><divclass="row"><divclass="col-md-4"><formasp-route-returnUrl="@Model.ReturnUrl"method="post"><!--<h4>Create a new account.</h4>--><hr/><divasp-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>--><divclass="form-group"><labelasp-for="Input.UserId"></label><inputasp-for="Input.UserId"class="form-control"/><spanasp-validation-for="Input.UserId"class="text-danger"></span></div><divclass="form-group"><labelasp-for="Input.UserName"></label><inputasp-for="Input.UserName"class="form-control"/><spanasp-validation-for="Input.UserName"class="text-danger"></span></div><divclass="form-group"><labelasp-for="Input.Password"></label><inputasp-for="Input.Password"class="form-control"/><spanasp-validation-for="Input.Password"class="text-danger"></span></div><divclass="form-group"><labelasp-for="Input.ConfirmPassword"></label><inputasp-for="Input.ConfirmPassword"class="form-control"/><spanasp-validation-for="Input.ConfirmPassword"class="text-danger"></span></div><buttontype="submit"class="btn btn-primary">Register</button></form></div><divclass="col-md-6 col-md-offset-2"><section><!--<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}--><h4>各項目は必須項目です。</h4><p>・ユーザIDはログイン時に使用します。6~10文字で入力してください。</p><p>・ユーザ名は画面に表示されます。1~100文字で入力してください。</p><p>・パスワードは6~100文字で入力してください。</p></section></div></div>
@section Scripts {
<partialname="_ValidationScriptsPartial"/>
}
ほかにちょっとだけ直すところがあります。
_ManageNav.cshtml内で自作ユーザクラスを使っていますが、usingがないのでエラーになってしまいます。
なので、usingを追加しておきます。
@using SalaryManagementSystem.Models; ←追加
@inject SignInManager<SMSUser> SignInManager
Register.cshtml.csをちょっと修正。不本意ながら使わないUserNameを設定。
//var user = new SMSUser { UserName = Input.Email, Email = Input.Email };varuser=newSMSUser{SMSUserId=Input.UserId,SMSUserName=Input.UserName,UserName=Input.UserName,IsLocked=false,LoginFailCount=0,CreateDate=DateTime.Now,UpdateDate=DateTime.Now};
どうやら日本語はだめらしい。こちらを参考に日本語対応する。
//services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)// .AddEntityFrameworkStores<ApplicationDbContext>();services.AddIdentity<SMSUser,IdentityRole>(options=>{options.User.AllowedUserNameCharacters=null;}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders().AddDefaultUI();
とりあえず指示通りAdd-MigrationとUpdate-Databaseする。
Add-Migrationで警告が出るがまあなんでもいいので無視。
PM> Add-Migration Init_20210202
Build started...
Build succeeded.
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
To undo this action, use Remove-Migration.
PM> Update-Database
Build started...
Build succeeded.
Done.
DBを見ると、SMSUserテーブルではなく、AspNetUsersに登録されていました。ナンデ!?
ちなみに独自項目はAspNetUsersテーブルにできていました。UserName1ってなに…
次回予告
次回はログインします。