前言:

本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。

本系列文章主要参考资料:

微软文档:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows

《Pro ASP.NET MVC 5》、《锋利的 jQuery》

此系列皆使用 VS2017+C# 作为开发环境。如果有什么问题或者意见欢迎在留言区进行留言。

项目 github 地址:https://github.com/NanaseRuri/LibraryDemo

  本章内容:Identity 修改密码和找回密码、c# SMTP 的使用、配置文件的使用

一、添加密码修改功能

  首先创建对应的视图模型:

  其中 16 行的 [Compare] 特性构造函数参数为需进行对比的属性,此处用于确认修改后的密码。  

     public class ModifyModel
{
[UIHint("password")]
[Display(Name = "原密码")]
[Required]
public string OriginalPassword { get; set; } [Required]
[Display(Name = "新密码")]
[UIHint("password")]
public string ModifiedPassword { get; set; } [Required]
[Display(Name = "确认密码")]
[UIHint("password")]
[Compare("ModifiedPassword", ErrorMessage = "两次密码不匹配")]
public string ConfirmedPassword { get; set; }
}

  利用 Identity 框架中 UserManager 对象的 ChangePasswordAsync 方法用来修改密码,该方法返回一个 IdentityResult 对象,可通过其 Succeeded 属性查看操作是否成功。在此修改成功后调用 _signInManager.SignOutAsync() 方法来清除当前 Cookie。

  定义用于修改密码的动作方法和视图:

         public IActionResult ModifyPassword()
{
ModifyModel model=new ModifyModel();
return View(model);
} [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ModifyPassword(ModifyModel model)
{
if (ModelState.IsValid)
{
string username = HttpContext.User.Identity.Name;
var student = _userManager.Users.FirstOrDefault(s => s.UserName == username);
var result =
await _userManager.ChangePasswordAsync(student, model.OriginalPassword, model.ModifiedPassword);
if (result.Succeeded)
{
await _signInManager.SignOutAsync();
return View("ModifySuccess");
}
ModelState.AddModelError("","原密码输入错误");
}
return View(model);
}

  ModifyPassword 视图,添加用以表示是否显示密码的复选框,并使用 jQuery 和 JS 添加相应的事件。将<script></script>标签统一放在 @section Scripts 以方便地使用布局:

     @model ModifyModel

     @{
ViewData["Title"] = "ModifyPassword";
} @section Scripts{
<script>
$(document).ready(function() {
var $btn = $("#showPas");
var btn = $btn.get();
$btn.click(function() {
if (btn.checked) {
$(".pass").attr("type", "");
} else {
$(".pass").attr("type", "password");
}
});
})
</script>
} <h2>修改密码</h2> <div class="text-danger" asp-validation-summary="All"></div>
<form asp-action="ModifyPassword" method="post">
<div class="form-group">
<label asp-for="OriginalPassword"></label>
<input asp-for="OriginalPassword" class="pass"/>
</div>
<div class="form-group">
<label asp-for="ModifiedPassword"></label>
<input asp-for="ModifiedPassword" id="modifiedPassword" class="pass"/>
</div>
<div class="form-group">
<label asp-for="ConfirmedPassword"></label>
<input asp-for="ConfirmedPassword" id="confirmedPassword" onkeydown="" class="pass"/>
</div>
<div class="form-group">
<label>显示密码 </label><input style="margin-left: 10px" type="checkbox" id="showPas"/>
</div>
<input type="submit"/>
<input type="reset"/>
</form>

  随便建的 ModifySuccess 视图:

     @{
ViewData["Title"] = "修改成功";
} <h2>修改成功</h2> <h4><a asp-action="Login">请重新登录</a></h4>

  然后修改 AccountInfo 视图以添加对应的修改密码的按钮:

     @model Dictionary<string, object>
@{
ViewData["Title"] = "AccountInfo";
}
<h2>账户信息</h2>
<ul>
@foreach (var info in Model)
{
<li>@info.Key: @Model[info.Key]</li>
}
</ul>
<br />
<a class="btn btn-danger" asp-action="Logout">登出</a>
<a class="btn btn-primary" asp-action="ModifyPassword">修改密码</a>

Cookie 被清除:

 二、重置密码

  在 Identity 框架中, UserManager 提供了 GeneratePasswordResetTokenAsync 以及 ResetPasswordAsync 方法用以重置密码。

  现实生活中,一般通过邮件发送重置连接来重置密码,为日后更方便地配置,在此创建 Mail.json

 {
"Mail": {
"MailFromAddress": "",
"UseSsl": "false",
"Username": "",
"Password": "",
"ServerPort": "",
"ServerName": "smtp.163.com",
"UseDefaultCredentials": "true"
}
}

   各大邮箱运营商拥有自己的 SMTP 服务器,需要对应邮箱的请自行百度。这里仅展示 163 邮箱,这里请自行输入自己的 163 账号和密码。

  然后创建一个类用来配置发送邮件的相关信息:

     public class EmailSender
{
IConfiguration emailConfig = new ConfigurationBuilder().AddJsonFile("Mail.json").Build().GetSection("Mail");
public SmtpClient SmtpClient=new SmtpClient(); public EmailSender()
{
SmtpClient.EnableSsl = Boolean.Parse(emailConfig["UseSsl"]);
SmtpClient.UseDefaultCredentials = bool.Parse(emailConfig["UseDefaultCredentials"]);
SmtpClient.Credentials = new NetworkCredential(emailConfig["Username"], emailConfig["Password"]);
SmtpClient.Port = Int32.Parse(emailConfig["ServerPort"]);
SmtpClient.Host = emailConfig["ServerName"];
SmtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
}
}

  该类定义了一个读取配置的字段,以及一个用来发送邮件的 SmtpClient 属性。

  此处第三行将会从 bin 文件夹中读取 Mail.json 文件中的 Mail 节点,为使 ConfigurationBuilder 能够读取到 bin 文件夹的文件,需要将 Mail.json 设置为复制到输出目录中:

  然后该类将在构造函数对 SmtpClient 进行相应的配置。注意需要在为 SmtpClient 的 Credentials 属性赋值前为 UseDefaultCredentials 赋值,否则 Credentials 将被赋值为空值而出 Bug。

  为使整个网页应用在整个生命期内使用的是同一个 SmtpClient 实例,在 ConfigureServices 中进行配置:  

     services.AddSingleton<EmailSender>();

  创建用于确定找回途径的模型:

     public enum RetrieveType
{
UserName,
Email
} public class RetrieveModel
{
[Required]
public RetrieveType RetrieveWay { get;set; }
[Required]
public string Account { get; set; }
}

  定义一个 PasswordRetrieverController 专门用以处理找回密码的逻辑,Retrieve 方法创建接收用户信息输入的视图:

     public class PasswordRetrieverController : Controller
{
private UserManager<Student> _userManager;
public EmailSender _emailSender; public PasswordRetrieverController(UserManager<Student> studentManager, EmailSender emailSender)
{
_userManager = studentManager;
_emailSender = emailSender;
} public IActionResult Retrieve()
{
RetrieveModel model = new RetrieveModel();
return View(model);
}

  Retrieve 视图:

     @model RetrieveModel

     <h2>找回密码</h2>
<hr/> <label class="text-danger">@ViewBag.Error</label> <form asp-action="RetrievePassword" asp-controller="PasswordRetriever" method="post">
<div class="form-group">
<input asp-for="Account" class="form-control" placeholder="请输入你的邮箱 / 账号 / 手机号"/>
</div>
<br/>
<div class="form-group">
<label>找回方式</label>
<select asp-for="RetrieveWay">
<option disabled value="">找回方式: </option>
<LoginType login-type="@Enum.GetNames(typeof(RetrieveType))"></LoginType>
</select>
</div>
<br/>
<input class="btn btn-primary" type="submit" value="确认"/>
<input class="btn btn-primary" type="reset"/>
</form>

  定义用来进行具体逻辑验证的 RetrievePassword 方法,该方法验证用户是否存在,生成用以重置密码的 token 并发送邮件:

         [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RetrievePassword(RetrieveModel model)
{
bool sendResult=false;
if (ModelState.IsValid)
{
Student student = new Student();
switch (model.RetrieveWay)
{
case RetrieveType.UserName:
student = await _userManager.FindByNameAsync(model.Account);
if (student != null)
{
string code = await _userManager.GeneratePasswordResetTokenAsync(student);
sendResult = await SendEmail(student.Id, code, student.Email);
}
break;
case RetrieveType.Email:
student = await _userManager.FindByEmailAsync(model.Account);
if (student != null)
{
string code = await _userManager.GeneratePasswordResetTokenAsync(student);
sendResult = await SendEmail(student.Id, code, student.Email);
}
break;
}
if (student == null)
{
ViewBag.Error("用户不存在,请重新输入");
return View("Retrieve",model);
}
}
ViewBag.Message = "已发送邮件至您的邮箱,请注意查收";
ViewBag.Failed = "信息发送失败";
return View(sendResult);
}

  在 PasswordRetrieverController 中定义用以发送邮件的方法,以 bool 为返回值以判断邮件是否发送成功,此处 MailMessage 处的 from 参数请自行配置:

         async Task<bool> SendEmail(string userId, string code, string mailAddress)
{
Student student = await _userManager.FindByIdAsync(userId);
if (student!=null)
{
string url = Url.Action("ResetPassword","PasswordRetriever",new{userId=userId,code=code}, Url.ActionContext.HttpContext.Request.Scheme);
StringBuilder sb = new StringBuilder();
sb.AppendLine($" 请点击<a href=\"{url}\">此处</a>重置您的密码");
MailMessage message = new MailMessage(from: "xxxx@163.com", to: mailAddress, subject: "重置密码", body: sb.ToString());
message.BodyEncoding=Encoding.UTF8;
message.IsBodyHtml = true;
try
{
_emailSender.SmtpClient.Send(message);
}
catch (Exception e)
{
return false;
} return true;
}
return false;
}

  为 Url.Action 方法指定 protocol 参数以生成完整 url ,否则只会生成相对 url,由于此处为发送邮件,所以需要指定 url 为绝对 Url。

  为使用该 token,创建专门用于重置密码的模型,其中 Code 用来接收 GeneratePasswordResetTokenAsync 生成的 token,UserId 用来传递待重置用户的 Id:

    public class ResetPasswordModel
{
public string Code { get; set; } public string UserId { get; set; } [Required]
[Display(Name="密码")]
[DataType(DataType.Password)]
public string Password { get; set; } [Required]
[Display(Name = "确认密码")]
[DataType(DataType.Password)]
[Compare("Password",ErrorMessage = "两次密码不匹配")]
public string ConfirmPassword { get; set; }
}

  定义用来重置密码的方法 ResetPassword:

         public IActionResult ResetPassword(string userId,string code)
{
ResetPasswordModel model=new ResetPasswordModel()
{
UserId = userId,
Code = code
};
return View(model);
}

  ResetPassword 视图,此视图将 token 和userId 设置为隐藏字段以在请求中传递:

     @model ResetPasswordModel
@{
ViewData["Title"] = "ResetPassword";
} <h2>重置密码</h2> <form asp-action="ResetPassword" method="post" asp-antiforgery="true">
<div class="form-group">
@Html.HiddenFor(m=>m.Code)
@Html.HiddenFor(m=>m.UserId)
<label asp-for="Password"></label>
<input asp-for="Password"/>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword"/>
</div>
<input type="submit"/>
<input type="reset"/>
</form>

  定义用以具体逻辑验证的 ResetPassword 方法,UserManager<T> 对象的 ResetPasswordAsync 方法接收一个 T类型对象、一个 token 字符串以及密码,返回 IdentityResult 对象:

        [ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
{
if (ModelState.IsValid)
{
var user = _userManager.FindByIdAsync(model.UserId);
if (user!=null)
{
var result = await _userManager.ResetPasswordAsync(user.Result, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(ResetSuccess));
}
}
}
return View(model);
}

  随便定义的用以表示更改成功的 ResetSuccess 方法和视图:

           public IActionResult ResetSuccess()
{
return View();
}
     @{
ViewData["Title"] = "ResetSuccess";
} <h2>重置成功</h2> <h3>点击<a asp-action="Login" asp-controller="StudentAccount" target="_blank">此处</a>进行登录</h3>

  最后向之前建立的 _LoginParitalView 视图中添加找回密码的按钮:

     @model LoginModel

     <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl"/>
<div class="form-group">
<label asp-for="Account"></label>
<input asp-for="Account" class="form-control" placeholder="请输入你的账号(学号) / 邮箱 / 手机号"/>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" placeholder="请输入你的密码"/>
</div>
<div class="form-group">
<label>登录方式</label>
<select asp-for="LoginType">
<option disabled value="">登录方式</option>
<LoginType login-type="@Enum.GetNames(typeof(LoginType))"></LoginType>
</select>
</div>
<input type="submit" class="btn btn-primary"/>
<input type="reset" class="btn btn-primary"/>
<a class="btn btn-success" asp-action="Retrieve" asp-controller="PasswordRetriever">找回密码</a>

ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(三)密码修改以及密码重置的更多相关文章

  1. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  2. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(七) 学生信息增删

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  3. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(六)学生借阅/预约/查询书籍事务

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  4. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(五)外借/阅览图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  5. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(四)图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  6. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(一) 基本模型以及数据库的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  7. 002.Create a web API with ASP.NET Core MVC and Visual Studio for Windows -- 【在windows上用vs与asp.net core mvc 创建一个 web api 程序】

    Create a web API with ASP.NET Core MVC and Visual Studio for Windows 在windows上用vs与asp.net core mvc 创 ...

  8. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

  9. Pro ASP.NET Core MVC 第6版 第二章(前半章)

    目录 第二章 第一个MVC 应用程序 学习一个软件开发框架的最好方法是跳进他的内部并使用它.在本章,你将用ASP.NET Core MVC创建一个简单的数据登录应用.我将它一步一步地展示,以便你能看清 ...

随机推荐

  1. linux C 中的volatile使用

    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了.精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存 ...

  2. SharePoint中取得ACL和组中用户数量

     SharePoint中取得ACL和组中用户数量 1. 取得ACL的数量: select COUNT(ra.PrincipalId) as [Count],p.ScopeUrl from [WSS_C ...

  3. IntelliJ 中类似于Eclipse ctrl+q的是Ctrl+Shift+Backspace

    IntelliJ 中类似于Eclipse ctrl+q的是Ctrl+Shift+Backspace 回到刚刚编辑的地方: ctrl+alt+Left 是回到刚刚浏览的地方,不一定是编辑的地方,可能已经 ...

  4. Highways POJ 2485【Prim】

    Description The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia has no public h ...

  5. [RxJS] Implement RxJS `mergeMap` through inner Observables to Subscribe and Pass Values Through

    Understanding sources and subscribers makes it much easier to understand what's going on with mergeM ...

  6. SQL 撤销索引、撤销表以及撤销数据库

    SQL 撤销索引.撤销表以及撤销数据库 通过使用 DROP 语句,可以轻松地删除索引.表和数据库. DROP INDEX 语句 DROP INDEX 语句用于删除表中的索引. 用于 MS Access ...

  7. 微信小程序之 Tabbar(底部选项卡)

    1.项目目录 2.在app.json里填写:tab个数范围2-5个 app.json { "pages": [ "pages/index/index", &qu ...

  8. hdoj 1533 Going Home 【最小费用最大流】【KM入门题】

    Going Home Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tota ...

  9. Android 自己定义View须要重写ondraw()等方法

    Android  自己定义View须要重写ondraw()等方法.这篇博客给大家说说自己定义View的写法,须要我们继承View,然后重写一些 方法,方法多多,看你须要什么方法 首先写一个自己定义的V ...

  10. 【Mongodb教程 第七课 】MongoDB 查询文档

    find() 方法 要从MongoDB 查询集合数据,需要使用MongoDB 的 find() 方法. 语法 基本的find()方法语法如下 >db.COLLECTION_NAME.find() ...