(五)用户管理及登录页面
目录
1.创建项目
项目中会多出如下红框内的用户身份管理的文件:
单独启动该项目,已经拥有了用户登录、注册、注销、管理等功能页面。
2.重写用户管理界面
但是,我们并不想使用原有的登录页面,我们想要自定义用户页面。
项目=》 右键 =》 添加 =》 新搭建基架的项目
添加过程中如果报错 “运行所选代码生成器时出错”, 则先清理该项目,尝试重新添加,如果仍然报错,重启vsstudio,再次添加基架即可。
然后就会看到项目中添加了下图中的文件:
user.cs 中添加姓名和生日:
public class user : identityuser { [personaldata] public string name { get; set; } [personaldata] public datetime birthday { get; set; } }
3.用户管理
leo.users.areas.identity.pages.account.manage.index.cshtml.cs :
public partial class indexmodel : pagemodel { private readonly usermanager<user> _usermanager; private readonly signinmanager<user> _signinmanager; private readonly iemailsender _emailsender; public indexmodel( usermanager<user> usermanager, signinmanager<user> signinmanager, iemailsender emailsender) { _usermanager = usermanager; _signinmanager = signinmanager; _emailsender = emailsender; } public string username { get; set; } public bool isemailconfirmed { get; set; } [tempdata] public string statusmessage { get; set; } [bindproperty] public inputmodel input { get; set; } public class inputmodel { #region 新增 [required] [datatype(datatype.text)] [display(name = "姓名")] public string name { get; set; } [required] [display(name = "生日")] [datatype(datatype.date)] public datetime birthday { get; set; } #endregion [required] [emailaddress] public string email { get; set; } [phone] [display(name = "电话")] public string phonenumber { get; set; } } public async task<iactionresult> ongetasync() { var user = await _usermanager.getuserasync(user); if (user == null) { return notfound($"unable to load user with id '{_usermanager.getuserid(user)}'."); } var username = await _usermanager.getusernameasync(user); var email = await _usermanager.getemailasync(user); var phonenumber = await _usermanager.getphonenumberasync(user); username = username; input = new inputmodel { #region 新增 name = user.name, birthday = user.birthday, #endregion email = email, phonenumber = phonenumber }; isemailconfirmed = await _usermanager.isemailconfirmedasync(user); return page(); } public async task<iactionresult> onpostasync() { if (!modelstate.isvalid) { return page(); } var user = await _usermanager.getuserasync(user); if (user == null) { return notfound($"unable to load user with id '{_usermanager.getuserid(user)}'."); } var email = await _usermanager.getemailasync(user); if (input.email != email) { var setemailresult = await _usermanager.setemailasync(user, input.email); if (!setemailresult.succeeded) { var userid = await _usermanager.getuseridasync(user); throw new invalidoperationexception($"unexpected error occurred setting email for user with id '{userid}'."); } } #region 新增 if (input.name != user.name) { user.name = input.name; } if (input.birthday != user.birthday) { user.birthday = input.birthday; } #endregion var phonenumber = await _usermanager.getphonenumberasync(user); if (input.phonenumber != phonenumber) { var setphoneresult = await _usermanager.setphonenumberasync(user, input.phonenumber); if (!setphoneresult.succeeded) { var userid = await _usermanager.getuseridasync(user); throw new invalidoperationexception($"unexpected error occurred setting phone number for user with id '{userid}'."); } } #region 新增 await _usermanager.updateasync(user); #endregion await _signinmanager.refreshsigninasync(user); statusmessage = "your profile has been updated"; return redirecttopage(); } public async task<iactionresult> onpostsendverificationemailasync() { if (!modelstate.isvalid) { return page(); } var user = await _usermanager.getuserasync(user); if (user == null) { return notfound($"unable to load user with id '{_usermanager.getuserid(user)}'."); } var userid = await _usermanager.getuseridasync(user); var email = await _usermanager.getemailasync(user); var code = await _usermanager.generateemailconfirmationtokenasync(user); var callbackurl = url.page( "/account/confirmemail", pagehandler: null, values: new { userid = userid, code = code }, protocol: request.scheme); await _emailsender.sendemailasync( email, "confirm your email", $"please confirm your account by <a href='{htmlencoder.default.encode(callbackurl)}'>clicking here</a>."); statusmessage = "verification email sent. please check your email."; return redirecttopage(); } }
展示页面 index.cshtml :
@page @model indexmodel @{ viewdata["title"] = "profile"; viewdata["activepage"] = managenavpages.index; } <h4>@viewdata["title"]</h4> <partial name="_statusmessage" for="statusmessage" /> <div class="row"> <div class="col-md-6"> <form id="profile-form" method="post"> <div asp-validation-summary="all" class="text-danger"></div> <div class="form-group"> <label asp-for="username"></label> <input asp-for="username" class="form-control" disabled /> </div> <div class="form-group"> <label asp-for="input.email"></label> @if (model.isemailconfirmed) { <div class="input-group"> <input asp-for="input.email" class="form-control" /> <span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span> </div> } else { <input asp-for="input.email" class="form-control" /> <button id="email-verification" type="submit" asp-page-handler="sendverificationemail" class="btn btn-link">send verification email</button> } <span asp-validation-for="input.email" class="text-danger"></span> </div> <div class="form-group"> @* 新增 start *@ <div class="form-group"> <label asp-for="input.name"></label> <input asp-for="input.name" class="form-control" /> </div> <div class="form-group"> <label asp-for="input.birthday"></label> <input asp-for="input.birthday" class="form-control" /> </div> @* end *@ <label asp-for="input.phonenumber"></label> <input asp-for="input.phonenumber" class="form-control" /> <span asp-validation-for="input.phonenumber" class="text-danger"></span> </div> <button id="update-profile-button" type="submit" class="btn btn-primary">save</button> </form> </div> </div> @section scripts { <partial name="_validationscriptspartial" /> }
4.用户注册
leo.users.areas.identity.pages.account.register.cshtml.cs :
[allowanonymous] public class registermodel : pagemodel { private readonly signinmanager<user> _signinmanager; private readonly usermanager<user> _usermanager; private readonly ilogger<registermodel> _logger; private readonly iemailsender _emailsender; public registermodel( usermanager<user> usermanager, signinmanager<user> 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 { #region 新增 [required] [datatype(datatype.text)] [display(name = "姓名")] public string name { get; set; } [required] [display(name = "生日")] [datatype(datatype.date)] public datetime birthday { get; set; } #endregion [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 = "密码")] public string password { get; set; } [datatype(datatype.password)] [display(name = "确认密码")] [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 user { #region 新增 name = input.name, birthday = input.birthday, #endregion 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(); } }
展示页面 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> @* 新增 start *@ <div class="form-group"> <label asp-for="input.name"></label> <input asp-for="input.name" class="form-control" /> <span asp-validation-for="input.name" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="input.birthday"></label> <input asp-for="input.birthday" class="form-control" /> <span asp-validation-for="input.birthday" class="text-danger"></span> </div> @* end *@ <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" /> }
5.用户登录
login.cshtml.cs :
[allowanonymous] public class loginmodel : pagemodel { private readonly signinmanager<user> _signinmanager; private readonly ilogger<loginmodel> _logger; public loginmodel(signinmanager<user> signinmanager, ilogger<loginmodel> logger) { _signinmanager = signinmanager; _logger = logger; } [bindproperty] public inputmodel input { get; set; } public ilist<authenticationscheme> externallogins { get; set; } public string returnurl { get; set; } [tempdata] public string errormessage { get; set; } public class inputmodel { [required] [emailaddress] public string email { get; set; } [required] [datatype(datatype.password)] public string password { get; set; } [display(name = "remember me?")] public bool rememberme { get; set; } } public async task ongetasync(string returnurl = null) { if (!string.isnullorempty(errormessage)) { modelstate.addmodelerror(string.empty, errormessage); } returnurl = returnurl ?? url.content("~/"); // clear the existing external cookie to ensure a clean login process await httpcontext.signoutasync(identityconstants.externalscheme); externallogins = (await _signinmanager.getexternalauthenticationschemesasync()).tolist(); returnurl = returnurl; } public async task<iactionresult> onpostasync(string returnurl = null) { returnurl = returnurl ?? url.content("~/"); 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: true); if (result.succeeded) { _logger.loginformation("user logged in."); return localredirect(returnurl); } if (result.requirestwofactor) { return redirecttopage("./loginwith2fa", new { returnurl = returnurl, rememberme = input.rememberme }); } if (result.islockedout) { _logger.logwarning("user account locked out."); return redirecttopage("./lockout"); } else { modelstate.addmodelerror(string.empty, "invalid login attempt."); return page(); } } // if we got this far, something failed, redisplay form return page(); } }
login.cshtml :
@page @model loginmodel @{ viewdata["title"] = "log in"; } <h1>@viewdata["title"]</h1> <div class="row"> <div class="col-md-4"> <section> <form id="account" method="post"> <h4>use a local account to log in.</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"> <div class="checkbox"> <label asp-for="input.rememberme"> <input asp-for="input.rememberme" /> @html.displaynamefor(m => m.input.rememberme) </label> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">log in</button> </div> <div class="form-group"> <p> <a id="forgot-password" asp-page="./forgotpassword">forgot your password?</a> </p> <p> <a asp-page="./register" asp-route-returnurl="@model.returnurl">register as a new user</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 { <partial name="_validationscriptspartial" /> }
6.用户退出
logout.cshtml.cs :
[allowanonymous] public class logoutmodel : pagemodel { private readonly signinmanager<user> _signinmanager; private readonly ilogger<logoutmodel> _logger; public logoutmodel(signinmanager<user> signinmanager, ilogger<logoutmodel> logger) { _signinmanager = signinmanager; _logger = logger; } public void onget() { } public async task<iactionresult> onpost(string returnurl = null) { await _signinmanager.signoutasync(); _logger.loginformation("user logged out."); if (returnurl != null) { return localredirect(returnurl); } else { return page(); } } }
logout.cshtml :
@page @model logoutmodel @{ viewdata["title"] = "log out"; } <header> <h1>@viewdata["title"]</h1> <p>您已成功退出</p> </header>
7.自动数据迁移
程序自动完成数据库以及表的构建
如图,会报错,是因为项目中有两个context 数据上下文:
删掉下面的包含 applicationdbcontext.cs 的 data文件夹
然后编译时startup.cs 会报错,找不到applicationdbcontext 类,此时我们直接将这一段注释掉即可,因为我们添加基架以后已经有了新的数据模块:
public class startup { public startup(iconfiguration configuration) { configuration = configuration; } public iconfiguration configuration { get; } // this method gets called by the runtime. use this method to add services to the container. public void configureservices(iservicecollection services) { services.configure<cookiepolicyoptions>(options => { // this lambda determines whether user consent for non-essential cookies is needed for a given request. options.checkconsentneeded = context => true; options.minimumsamesitepolicy = samesitemode.none; }); #region 身份认证相关,注释掉 //services.adddbcontext<applicationdbcontext>(options => // options.usesqlserver( // configuration.getconnectionstring("defaultconnection"))); //services.adddefaultidentity<identityuser>() // .adddefaultui(uiframework.bootstrap4) // .addentityframeworkstores<applicationdbcontext >(); #endregion services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2); } // this method gets called by the runtime. use this method to configure the http request pipeline. public void configure(iapplicationbuilder app, ihostingenvironment env) { if (env.isdevelopment()) { app.usedeveloperexceptionpage(); app.usedatabaseerrorpage(); } else { app.useexceptionhandler("/home/error"); // the default hsts value is 30 days. you may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.usehsts(); } app.usehttpsredirection(); app.usestaticfiles(); app.usecookiepolicy(); #region 身份认证相关,保留 app.useauthentication(); #endregion app.usemvc(routes => { routes.maproute( name: "default", template: "{controller=home}/{action=index}/{id?}"); }); } }
再次添加迁移,不再报错:
add-migration customuserdata
更新到数据库:
update-database
完成。
查看数据库:
为了防止以后出现问题,我们删掉原来的defaultconnection ,将 userscontextconnection 改为 defaultconnection, 搜索“userscontextconnection” ,用到该字符串的地方都替换为 “defaultconnection” 。
8.启动应用
选择用 kestrel 服务器启动,方便监控:
启动以后,会抛出异常( invalidoperationexception: no service for type 'microsoft.aspnetcore.identity.usermanager`1[microsoft.aspnetcore.identity.identityuser]' has been registered.):
意思是说 usermanager<identityuser> 类型的服务没有被注册,事实上,我们将identityuser实现为user ,是对user模型进行管理,搜索 identityuser,替换为 user
记得添加命名空间,不然引入的user并不正确:
@inject signinmanager<leo.users.areas.identity.data.user> signinmanager @inject usermanager<leo.users.areas.identity.data.user> usermanager
启动成功,注册账号:
完成:
接下来,我们检查一下后台的用户数据:
完成!