Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9691

【C#入門】初学者がASP.NETでWebアプリを作る:第6回

$
0
0

今回やること

・前回足りてなかったこと
・ログイン

前回足りていなかったこと

DBをよく見てみると、なんとパスワードが登録されていませんでした。
image.png

Register.cshtml.csでハッシュ化したパスワードを設定するようにしました。

Register.cshtml.cs
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,UserName=Input.UserName,IsLocked=false,LoginFailCount=0,CreateDate=DateTime.Now,UpdateDate=DateTime.Now};user.Password=newPasswordHasher<SMSUser>().HashPassword(user,Input.Password);// 追加部分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();}

再度アカウント登録してみると、ハッシュ化されたパスワードが保存されました。
image.png

ログインする

まずログイン画面が英語なのと、メールアドレスを入力するようになっているので、修正していきます。
コードから直すのとcshtmlから直すのとではどちらが楽なんでしょう?
image.png

まずはLogincshtml.cs

Login.cshtml.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel.DataAnnotations;usingSystem.Linq;usingSystem.Text.Encodings.Web;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Identity;usingMicrosoft.AspNetCore.Identity.UI.Services;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.RazorPages;usingMicrosoft.Extensions.Logging;usingSalaryManagementSystem.Models;namespaceSalaryManagementSystem.Areas.Identity.Pages.Account{[AllowAnonymous]publicclassLoginModel:PageModel{privatereadonlyUserManager<SMSUser>_userManager;privatereadonlySignInManager<SMSUser>_signInManager;privatereadonlyILogger<LoginModel>_logger;publicLoginModel(SignInManager<SMSUser>signInManager,ILogger<LoginModel>logger,UserManager<SMSUser>userManager){_userManager=userManager;_signInManager=signInManager;_logger=logger;}[BindProperty]publicInputModelInput{get;set;}publicIList<AuthenticationScheme>ExternalLogins{get;set;}publicstringReturnUrl{get;set;}[TempData]publicstringErrorMessage{get;set;}publicclassInputModel{//[Required]//[EmailAddress]//public string Email { get; set; }[Required][Display(Name="ユーザID")]publicstringUserId{get;set;}[Required][Display(Name="パスワード")][DataType(DataType.Password)]publicstringPassword{get;set;}[Display(Name="記憶する")]publicboolRememberMe{get;set;}}publicasyncTaskOnGetAsync(stringreturnUrl=null){if(!string.IsNullOrEmpty(ErrorMessage)){ModelState.AddModelError(string.Empty,ErrorMessage);}returnUrl??=Url.Content("~/");// Clear the existing external cookie to ensure a clean login processawaitHttpContext.SignOutAsync(IdentityConstants.ExternalScheme);ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();ReturnUrl=returnUrl;}publicasyncTask<IActionResult>OnPostAsync(stringreturnUrl=null){returnUrl??=Url.Content("~/");ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();if(ModelState.IsValid){// This doesn't count login failures towards account lockout// To enable password failures to trigger account lockout, set lockoutOnFailure: true//var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);varresult=await_signInManager.PasswordSignInAsync(Input.UserId,Input.Password,Input.RememberMe,lockoutOnFailure:false);if(result.Succeeded){_logger.LogInformation("ログインしました。");returnLocalRedirect(returnUrl);}if(result.RequiresTwoFactor){returnRedirectToPage("./LoginWith2fa",new{ReturnUrl=returnUrl,RememberMe=Input.RememberMe});}if(result.IsLockedOut){_logger.LogWarning("アカウントロックしました。");returnRedirectToPage("./Lockout");}else{ModelState.AddModelError(string.Empty,"ログインできませんでした。");returnPage();}}// If we got this far, something failed, redisplay formreturnPage();}}}

次にLogin.cshtml
Resend email confirmationのリンクは、メール認証を使わないので消しました。

Login.cshtml
@page
@model LoginModel

@{
    ViewData["Title"] = "ログイン";
}

<h1>@ViewData["Title"]</h1><divclass="row"><divclass="col-md-4"><section><formid="account"method="post"><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.Password"></label><inputasp-for="Input.Password"class="form-control"/><spanasp-validation-for="Input.Password"class="text-danger"></span></div><divclass="form-group"><divclass="checkbox"><labelasp-for="Input.RememberMe"><inputasp-for="Input.RememberMe"/>
                            @Html.DisplayNameFor(m => m.Input.RememberMe)
                        </label></div></div><divclass="form-group"><buttontype="submit"class="btn btn-primary">ログイン</button></div><divclass="form-group"><p><aid="forgot-password"asp-page="./ForgotPassword">パスワードを忘れた場合はこちら</a></p><p><aasp-page="./Register"asp-route-returnUrl="@Model.ReturnUrl">新規アカウント登録</a></p><!--<p>
                        <a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
                    </p>--></div></form></section></div><!--<div class="col-md-6 col-md-offset-2">
        <section>
            <h4>Use another service to log in.</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>
                }
            }
        </section>
    </div>--></div>

@section Scripts {
    <partialname="_ValidationScriptsPartial"/>
}

ログインしてみます。
image.png

ログインできませんでした。
image.png

どうやらUserIdではなくUserNameで探しているみたいです。
あんまりやりたくなかったですが(難しそうだから)、SignInManagerをオーバーライドすることにします。

心が折れた

色々調べたり試したりしたのですが、結局実装できませんでした…。
そもそもASP.NET CoreのIdentityを使っている(カスタマイズしている)記事が全然見つからず、
Coreじゃないものばかり引っかかって、Coreだと違うインターフェースになってるとかで初学者には厳しすぎました。
なので、認証部分はデフォルトのIdentityを使うことにします。

デフォルトのIdentityに戻す

一旦。自分が作ったもの(クラスやらなにやら)は全部消します。
変更したものはもうちょっと後で戻します。(この後、このタイミングで消してよかったと気づく)
1つだけ、DbContextだけはIdentityDbContextを継承した形にします。
以前やったIDのスキャフォールディングをするためです。

ApplicationDbContext.cs
usingMicrosoft.AspNetCore.Identity;usingMicrosoft.AspNetCore.Identity.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore;usingSalaryManagementSystem.Models;usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceSalaryManagementSystem.Data{publicclassApplicationDbContext:IdentityDbContext<IdentityUser>{publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}publicDbSet<SalaryManagementSystem.Models.Salary>Salary{get;set;}publicDbSet<IdentityUser>SMSUsers{get;set;}}}

この状態でIDをスキャフォールディング。
今回もやっぱり全部チェックを入れておきます。
image.png

置き換えるか聞かれるのではいを選択
image.png

ビルドエラー解消しなきゃいけないので解消しましょう。
解消するのめんどくさいので、Identityフォルダごと消した方が早いですね。
あとStartup.csにIdentityを設定している個所があるので、それも消します。
Views/Sharedにある_LoginPartical.cshtmlも消します。
image.png

スキャフォールディング出来たら、Startup.csは下記の通り修正しておきます。
なぜかここは書かれなかった。

Startup.cs
publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext<ApplicationDbContext>(options=>options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));services.AddDatabaseDeveloperPageExceptionFilter();// ここから追加services.AddDefaultIdentity<IdentityUser>().AddDefaultUI().AddRoles<IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();// ここまで追加services.AddControllersWithViews();services.AddRazorPages();}

これでいったん起動。あれ、起動するけどDBマイグレーションいらんのか…?
image.png

登録。
image.png

今言わないで。(自分が悪い)
というわけでDBマイグレーションは必要ですね。
image.png

元のユーザーテーブルになりました。
image.png

ユーザIDとパスワードで認証してユーザ名を表示する方法を考える

今回まででで何が困ったかというと…
・メール認証がいらない(配信サービス使うのは面倒だし個人情報を持ちたくない)
・表示名がメールアドレスになっている(EmailをUserNameにも登録しているから。ここは直せる)
・ユーザIDでログインできない(UserNameでしか検索できない)

おそらくデフォルトだとログイン時のユーザIDと表示名を別々に保持する手立てがなさそう(=独自のユーザクラスを実装する必要がある)なので、今回は、
・メール認証なし
・ユーザID(表示も同じ)とパスワードでログイン
で実装します。

メール認証がいらないので、以前の記事の通り、アカウント登録のメール関連は削除して、
代わりにユーザID(UserNameに登録する)を保持するように作り変えます。
ポイントはInput.UserNameをIdentityUserのUserNameにセットするところ…

Register.cshtml.cs
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;namespaceSalaryManagementSystem.Areas.Identity.Pages.Account{[AllowAnonymous]publicclassRegisterModel:PageModel{privatereadonlySignInManager<IdentityUser>_signInManager;privatereadonlyUserManager<IdentityUser>_userManager;privatereadonlyILogger<RegisterModel>_logger;privatereadonlyIEmailSender_emailSender;publicRegisterModel(UserManager<IdentityUser>userManager,SignInManager<IdentityUser>signInManager,ILogger<RegisterModel>logger,IEmailSenderemailSender){_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="ユーザ名")]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){varuser=newIdentityUser{UserName=Input.UserName,Email=""};user.PasswordHash=newPasswordHasher<IdentityUser>().HashPassword(user,Input.Password);varresult=await_userManager.CreateAsync(user,Input.Password);if(result.Succeeded){_logger.LogInformation("アカウント登録が完了しました。");//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();}}}
Register.cshtml
@page
@model RegisterModel
@{
    ViewData["Title"] = "アカウント登録";
}

<h1>@ViewData["Title"]</h1><divclass="row"><divclass="col-md-4"><formasp-route-returnUrl="@Model.ReturnUrl"method="post"><h4>新しいアカウントを作成します。</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.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">登録</button></form></div><!--<div class="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>
                }
            }
        </section>
    </div>--></div>

@section Scripts {
    <partialname="_ValidationScriptsPartial"/>
}

ログインも直してしまいます。
ポイントはUserNameを使うところ。

Login.cshtml.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel.DataAnnotations;usingSystem.Linq;usingSystem.Text.Encodings.Web;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Identity;usingMicrosoft.AspNetCore.Identity.UI.Services;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.RazorPages;usingMicrosoft.Extensions.Logging;namespaceSalaryManagementSystem.Areas.Identity.Pages.Account{[AllowAnonymous]publicclassLoginModel:PageModel{privatereadonlyUserManager<IdentityUser>_userManager;privatereadonlySignInManager<IdentityUser>_signInManager;privatereadonlyILogger<LoginModel>_logger;publicLoginModel(SignInManager<IdentityUser>signInManager,ILogger<LoginModel>logger,UserManager<IdentityUser>userManager){_userManager=userManager;_signInManager=signInManager;_logger=logger;}[BindProperty]publicInputModelInput{get;set;}publicIList<AuthenticationScheme>ExternalLogins{get;set;}publicstringReturnUrl{get;set;}[TempData]publicstringErrorMessage{get;set;}publicclassInputModel{//[Required]//[EmailAddress]//public string Email { get; set; }[Required][Display(Name="ユーザ名")]publicstringUserName{get;set;}[Required][DataType(DataType.Password)][Display(Name="パスワード")]publicstringPassword{get;set;}[Display(Name="記憶する")]publicboolRememberMe{get;set;}}publicasyncTaskOnGetAsync(stringreturnUrl=null){if(!string.IsNullOrEmpty(ErrorMessage)){ModelState.AddModelError(string.Empty,ErrorMessage);}returnUrl??=Url.Content("~/");// Clear the existing external cookie to ensure a clean login processawaitHttpContext.SignOutAsync(IdentityConstants.ExternalScheme);ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();ReturnUrl=returnUrl;}publicasyncTask<IActionResult>OnPostAsync(stringreturnUrl=null){returnUrl??=Url.Content("~/");ExternalLogins=(await_signInManager.GetExternalAuthenticationSchemesAsync()).ToList();if(ModelState.IsValid){// This doesn't count login failures towards account lockout// To enable password failures to trigger account lockout, set lockoutOnFailure: truevarresult=await_signInManager.PasswordSignInAsync(Input.UserName,Input.Password,Input.RememberMe,lockoutOnFailure:false);if(result.Succeeded){_logger.LogInformation("ログインしました。");returnLocalRedirect(returnUrl);}if(result.RequiresTwoFactor){returnRedirectToPage("./LoginWith2fa",new{ReturnUrl=returnUrl,RememberMe=Input.RememberMe});}if(result.IsLockedOut){_logger.LogWarning("ロックされました。");returnRedirectToPage("./Lockout");}else{ModelState.AddModelError(string.Empty,"ログインに失敗しました。");returnPage();}}// If we got this far, something failed, redisplay formreturnPage();}}}
Login.cshtml
@page
@model LoginModel

@{
    ViewData["Title"] = "ログイン";
}

<h1>@ViewData["Title"]</h1><divclass="row"><divclass="col-md-4"><section><formid="account"method="post"><!--<h4>Use a local account to log in.</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.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"><divclass="checkbox"><labelasp-for="Input.RememberMe"><inputasp-for="Input.RememberMe"/>
                            @Html.DisplayNameFor(m => m.Input.RememberMe)
                        </label></div></div><divclass="form-group"><buttontype="submit"class="btn btn-primary">ログイン</button></div><divclass="form-group"><p><aid="forgot-password"asp-page="./ForgotPassword">パスワードを忘れた</a></p><p><aasp-page="./Register"asp-route-returnUrl="@Model.ReturnUrl">新規アカウント登録</a></p><!--<p>
                        <a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
                    </p>--></div></form></section></div><!--<div class="col-md-6 col-md-offset-2">
        <section>
            <h4>Use another service to log in.</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>
                }
            }
        </section>
    </div>--></div>

@section Scripts {
    <partialname="_ValidationScriptsPartial"/>
}

アカウント登録、ログイン

左に寄ってるけど後で治すから…
image.png

登録完了。ログインした状態になっている。
image.png

一度ログアウトし、ログイン
image.png

できました。アカウント登録時に使用する文字列とログイン時に使用する文字列と表示文字列が一致。疲れた。。。
image.png

次回予告

・アカウント登録後、ログイン状態にせずログインさせる
・アカウントと給与データを紐づける


Viewing all articles
Browse latest Browse all 9691

Trending Articles