AspNetCore - MVC实战系列目录

. 爱留图网站诞生

. git源码:https://github.com/shenniubuxing3/LovePicture.Web

. AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型

. AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码

AspNetCore-MVC实战系列(三)之个人中心

AspNetCore-MVC实战系列(四)之账户设置

开篇唠嗑

本篇内容写在5.1假期前夕,主要是让大家能在节假日休息充点的时候能有好的干货例子,到目前为止netcore方面的实战例子分享即将进入正轨,谢谢各位朋友多多支持;最近工作安排的新项目即将开始,项目前期就我一人搭建,让我犹豫的是对于公司这个内部系统并且是初建的项目用什么开发方式好呢,最初考虑的是mvc5但是又想了下如果这样还不如直接使用NetCore1.1的MVC呢,因为现在这版本基本也算稳定了可以试试水,可是又有顾虑是mvc项目在上线的时候会影响到其他人的使用(前期不考虑nginx分发),然后目光又转向aspx网站的方式,不得不说这种方式在发布上的确有优势,尤其是在没有分布式的前提下;好吧目前还在考虑中,希望能得到各位朋友的建议。。。

注册模块

首先,这里讲解的内容对应的实体和表结构是基于上一篇文章创建的项目这里就不多说了;对于一个注册功能来说,通常需要的属性是:账号,密码,确认密码,验证码(可省略),甚至有些快捷的注册方式就是通过手机号来注册,当然咋们没有短信通道的功能不能发短信,所以采用前者,先来看下Action中设计代码:

Register的get路由Action

  // GET: Member/Create
public IActionResult Register()
{
return View();
}

Register的post提交注册信息的Action

 [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register([Bind("UserName,UserPwd,ComfirmPwd")] MoRegisterUser loginUser)
{
if (ModelState.IsValid)
{
#region 验证
if (_context.ToUserInfo.Any(b => b.UserName.ToUpper() == loginUser.UserName.Trim().ToUpper()))
{
this.MsgBox("已经存在相同的账号!");
return View(loginUser);
}
#endregion #region 入库 ToUserInfo userInfo = new ToUserInfo(); userInfo.UserName = loginUser.UserName.Trim();
userInfo.UserPwd = PublicClass._Md5(loginUser.UserPwd.Trim());
userInfo.NickName = userInfo.UserName;
userInfo.Status = (int)EnumHelper.EmUserStatus.启用;
userInfo.CreateTime = DateTime.Now;
userInfo.LevelNum = (int)EmLevelNum.注册; userInfo.Ips = this.GetUserIp();
userInfo.HeadPhoto = "/images/ailiutu_user.png";
userInfo.Sex = false; _context.Add(userInfo);
var result = await _context.SaveChangesAsync();
if (result > )
{
var moUserInfo = new MoUserInfo
{
Id = userInfo.Id,
UserName = userInfo.UserName,
NickName = userInfo.NickName,
Addr = userInfo.Addr,
Birthday = userInfo.Birthday, Blog = userInfo.Blog,
CreateTime = userInfo.CreateTime,
Email = userInfo.Email,
HeadPhoto = userInfo.HeadPhoto,
Introduce = userInfo.Introduce, Ips = userInfo.Ips,
LevelNum = userInfo.LevelNum,
Sex = userInfo.Sex,
Tel = userInfo.Tel,
Status = userInfo.Status, LoginTime = DateTime.Now
};
HttpContext.Session.Set<MoUserInfo>(HttpContext.Session.SessionKey(), moUserInfo); if (!string.IsNullOrWhiteSpace(moUserInfo.Ips))
{
_context.ToUserLog.Add(new ToUserLog
{
CodeId = (int)EmLogCode.登录,
CreateTime = DateTime.Now,
Des = $"IP:{moUserInfo.Ips},登录时间:{moUserInfo.LoginTime.ToString("yyyy-MM-dd HH:mm")}",
UserId = userInfo.Id
});
} _context.ToUserLog.Add(new ToUserLog
{
CodeId = (int)EmLogCode.积分,
CreateTime = DateTime.Now,
Des = $"【注册】 +{(int)EmLevelNum.注册}",
UserId = userInfo.Id
});
await _context.SaveChangesAsync(); return RedirectToAction(nameof(HomeController.Index), "home");
}
#endregion this.MsgBox("注册失败,请稍后重试。");
return View(loginUser);
}
return View(loginUser);
}

通过Post的Action能够看出注册处理的Action主要操作步骤有以下几点:

. ModelState.IsValid验证提交的信息是否满足model规则设置

. Linq的Any()方法验证是否存在相同账号

. _context.Add()入库注册信息

. HttpContext.Session.Set的扩展方法设置登陆的session

. 记录登陆记录和登陆增加的积分信息

对于一个简单的注册基本就是这样的流程,我们来看看提交注册时的模型实体:

  /// <summary>
/// 注册实体
/// </summary>
public class MoRegisterUser
{
[Required(AllowEmptyStrings = false, ErrorMessage = "账号长度范围6-30字符!")]
[Display(Prompt = "邮箱/手机号/6-30字符")]
[RegularExpression(@"[^\s]{6,30}", ErrorMessage = "账号长度范围6-30字符。")]
public string UserName { get; set; } [Required(AllowEmptyStrings = false, ErrorMessage = "密码长度范围6-20字符!")]
[DataType(DataType.Password)]
[Display(Prompt = "密码长度范围6-20字符!")]
[RegularExpression(@"[^\s]{6,20}", ErrorMessage = "密码长度范围6-20字符。")]
public string UserPwd { get; set; } [Compare("UserPwd", ErrorMessage = "密码与确认密码不相同!")]
[DataType(DataType.Password)]
[Display(Prompt = "必须与密码相同")]
public string ComfirmPwd { get; set; }
}

这里自定义的注册模型,设置了DataAnnotations,以此来快速设置验证,不用再每个都用js写了,mvc框架帮你做了这些;下面看看View的代码:

 @model LovePicture.Model.MoClass.MoRegisterUser

 @{
ViewData["Title"] = "注册";
} <h3><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> 注册</h3>
<form name="form_submit" asp-action="Register">
<div class="form-horizontal">
<h4> 爱留图:欢迎您成为我们的一份子,让我们一起留存珍惜的图片吧。</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserName" class="col-md-2 control-label">账号</label>
<div class="col-md-10">
<input asp-for="UserName" required="required" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="UserPwd" class="col-md-2 control-label">密码</label>
<div class="col-md-10">
<input asp-for="UserPwd" required="required" class="form-control" />
<span asp-validation-for="UserPwd" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ComfirmPwd" class="col-md-2 control-label">确认密码</label>
<div class="col-md-10">
<input asp-for="ComfirmPwd" class="form-control" />
<span asp-validation-for="ComfirmPwd" class="text-danger"></span>
</div>
</div> <div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="注 册" name="btnSubmit" class="btn btn-default" />
<span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
</div>
</div>
</div>
</form>
<br />
<div>
<a href="/member/login">有账号去登录</a> | <a href="/member/ForgetPassword">忘记密码?</a>
</div>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

对于mvc模型注解的方式在前端需要引入这两个js文件

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View内容注意点在于我试图中的Button按钮是不是submit形式,这样做的理由是,当您注册的Action业务过多时,用户点击注册按钮提交数据,这个时候如果无法快速响应信息给用户,那么用户可能多次点击,因此就有个需求是需要吧注册提交按钮置灰或者影藏点,这里我为了方便把全站点的提交按钮都弄成统一name的按钮了,最后用js来提交submit(注册效果):

  bindSubmitBtn: function () {
$("input[name='btnSubmit']").on("click", function () { var _btn = $(this);
_btn.addClass("hide");
var _msg = $("#msgbox");
_msg.html("提交中,请稍后..."); var _form = $("form[name='form_submit']");
if (_form.valid()) {
_form.submit();
} else {
_btn.removeClass("hide");
_msg.html("");
}
});
}

登录模块

从代码上来说登录和注册相差不大,功能上登录模块主要用来验证登陆用户是否存在,分配唯一sessionid,如果有跳转地址还需要在登陆成功后执行跳回原访问地址;

Login的Get方式Action

  // GET: Member
public IActionResult Login(string returnUrl = null)
{
//获取session
var userInfo = HttpContext.Session.Get<MoUserInfo>(HttpContext.Session.SessionKey());
if (userInfo != null)
{
if (string.IsNullOrWhiteSpace(returnUrl)) { return RedirectToAction(nameof(HomeController.Index), "Home"); }
else { Redirect(returnUrl); }
}
this.MsgBox(returnUrl, "returnUrl");
return View();
}

Login的Post登录方式Action

 [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login([Bind("UserName,UserPwd,ReturnUrl")] MoLoginUser loginUser)
{
if (ModelState.IsValid)
{
#region 验证
var md5Pwd = PublicClass._Md5(loginUser.UserPwd.Trim());
var userInfo = await _context.ToUserInfo.SingleOrDefaultAsync(b =>
b.UserName.Equals(loginUser.UserName, StringComparison.CurrentCultureIgnoreCase) &&
b.UserPwd.Equals(md5Pwd));
if (userInfo == null)
{
this.MsgBox("账号或密码错误!");
return View(loginUser);
}
else if (userInfo.Status == (int)EnumHelper.EmUserStatus.禁用)
{
this.MsgBox("该账号已被禁用,或许你可以尝试重新注册一个账号!");
return View(loginUser);
}
#endregion #region 更新登录信息
userInfo.Ips = this.GetUserIp();
userInfo.LoginTime = DateTime.Now;
userInfo.LevelNum += (int)EmLevelNum.登录; //记录session
var moUserInfo = new MoUserInfo
{
Id = userInfo.Id,
UserName = userInfo.UserName,
NickName = userInfo.NickName,
Addr = userInfo.Addr,
Birthday = userInfo.Birthday, Blog = userInfo.Blog,
CreateTime = userInfo.CreateTime,
Email = userInfo.Email,
HeadPhoto = userInfo.HeadPhoto,
Introduce = userInfo.Introduce, Ips = userInfo.Ips,
LevelNum = userInfo.LevelNum,
Sex = userInfo.Sex,
Tel = userInfo.Tel,
Status = userInfo.Status, LoginTime = Convert.ToDateTime(userInfo.LoginTime)
};
HttpContext.Session.Set<MoUserInfo>(HttpContext.Session.SessionKey(), moUserInfo); if (!string.IsNullOrWhiteSpace(moUserInfo.Ips))
{
_context.ToUserLog.Add(new ToUserLog
{
CodeId = (int)EmLogCode.登录,
CreateTime = DateTime.Now,
Des = $"IP:{moUserInfo.Ips},登录时间:{moUserInfo.LoginTime.ToString("yyyy-MM-dd HH:mm")}",
UserId = userInfo.Id
});
} _context.ToUserLog.Add(new ToUserLog
{
CodeId = (int)EmLogCode.积分,
CreateTime = DateTime.Now,
Des = $"【登录】 +{(int)EmLevelNum.登录}",
UserId = userInfo.Id
}); await _context.SaveChangesAsync(); if (string.IsNullOrWhiteSpace(loginUser.ReturnUrl)) { return RedirectToAction(nameof(HomeController.Index), "Home"); }
else { return Redirect(loginUser.ReturnUrl); }
#endregion
}
return View(loginUser);
}

这里模仿微软官方实例给出的样子,把登陆和注册实体模型分开了,因为登陆模型不需要什么重复密码,并且还有其他的属性如:回调地址,验证码等:

 /// <summary>
/// 登录实体
/// </summary>
public class MoLoginUser
{
[Required(AllowEmptyStrings = false, ErrorMessage = "账号长度范围6-30字符!")]
[Display(Prompt = "邮箱/手机号/6-30字符")]
[RegularExpression(@"[^\s]{6,30}", ErrorMessage = "账号长度范围6-30字符。")]
public string UserName { get; set; } [Required(AllowEmptyStrings = false, ErrorMessage = "密码长度范围6-20字符!")]
[DataType(DataType.Password)]
[Display(Prompt = "密码长度范围6-20字符!")]
[RegularExpression(@"[^\s]{6,20}", ErrorMessage = "密码长度范围6-20字符。")]
public string UserPwd { get; set; } /// <summary>
/// 回跳地址
/// </summary>
public string ReturnUrl { get; set; }
}

同理对于登录的view设计也和注册差不多,只是不同网站对于安全设置可能会增加一些验证码,或其他的验证方式而已,如下登录View代码:

 @model LovePicture.Model.MoClass.MoLoginUser

 @{
ViewData["Title"] = "登录";
} <h3><span class="glyphicon glyphicon-send" aria-hidden="true"></span> 登录</h3>
<form name="form_submit" asp-action="Login">
<div class="form-horizontal">
<h4>爱留图:即刻登录,让我们一起留存珍惜的图片吧。</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserName" class="col-md-2 control-label">账号</label>
<div class="col-md-10">
<input asp-for="UserName" required="required" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="UserPwd" class="col-md-2 control-label">密码</label>
<div class="col-md-10">
<input asp-for="UserPwd" required="required" class="form-control" />
<span asp-validation-for="UserPwd" class="text-danger"></span>
</div>
</div> <div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="登 录" name="btnSubmit" class="btn btn-default" /> <a href="/member/register" title="没账号来这里注册吧">没账号这里注册</a>
<span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
</div>
</div>
</div>
<input type="hidden" name="ReturnUrl" value="@ViewData["returnUrl"]" />
</form>
<br />
<div>
<a href="/member/register">没账号这里注册</a> | <a href="/member/ForgetPassword">忘记密码?</a>
</div>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

邮箱找回密码

来到这里才真正进入今天的主题,对于一些有安全设置的网站来说通常有类似于通过绑定邮箱找回密码的功能,这里爱留图目前也做了这样的设置:

如果是绑定邮箱的用户,可以通过找回密码-通过绑定邮箱找回密码的功能来获取重新设置密码的权限;首先我们需要一个忘记密码的界面,好让用户填写自己之前绑定好的邮箱,并且提交发送找回邮箱的邮件操作,因此有了如下Ation代码:

ForgetPassword忘记密码get的Action

   public IActionResult ForgetPassword()
{
return View();
}

ForgetPassword忘记密码post提交发送邮件的Action

 /// <summary>
/// 提交忘记密码内容
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgetPassword(string email)
{
if (string.IsNullOrWhiteSpace(email)) { this.MsgBox("邮箱必填!"); return View(); } email = email.Trim().ToLower();
if (email.Length >= || email.Length <= )
{
this.MsgBox("邮箱长度不符!"); return View();
}
else if (!email.Contains("@"))
{
this.MsgBox("邮箱格式不正确!"); return View();
}
var user = await _context.ToUserInfo.SingleOrDefaultAsync(b => b.Email.ToLower() == email);
if (user == null) { this.MsgBox("不存在该绑定邮箱的账号!"); return View(); }
else if (user.Status == (int)EnumHelper.EmUserStatus.禁用)
{
this.MsgBox("该绑定邮箱的账号已被禁用,可以通过发送邮件至:841202396@qq.com联系客服!"); return View();
} var timeOut = ;
var now = DateTime.Now.AddMinutes(timeOut);
var expires = now.ToString("yyyy-MM-dd HH:mm");
var token = PublicClass._Md5($"{expires}-{email}-{Request.Host.Host}");
var appUrl = $"http://{Request.Host.Host}:{Request.Host.Port}";
var comfirmUrl = $"{appUrl}/member/confirmpassword?expire={expires}&token={token}&email={email}&t=0.{now.ToString("ssfff")}"; //读取模板
var tpl = await PublicClass._GetHtmlTpl(EnumHelper.EmEmailTpl.MsgBox, _selfSetting.EmailTplPath);
if (string.IsNullOrWhiteSpace(tpl)) { this.MsgBox("发送绑定邮件失败,请稍后重试。"); return View(); } tpl = tpl.Replace("{name}", "尊敬的用户").
Replace("{content}", $"您正在使用<a href='{appUrl}'>爱留图网</a>邮箱重置密码功能,请点击以下链接确认绑定邮箱<a href='{comfirmUrl}'>{comfirmUrl}</a>;注意该地址有效时间{timeOut}分钟。");
//发送
var isOk = PublicClass._SendEmail(
new Dictionary<string, string> {
{ "尊敬的用户",email}
},
"爱留图 - 重置密码",
tpl); this.MsgBox(isOk ? "已给您邮箱发送了重置密码邮件,请收件后点击重置密码链接地址。" : "发送绑定邮件失败,请稍后重试!"); return View();
}

这里值得关注的是咋们构造了一个修改密码的确认链接comfirmUrl,这里我简单设置的有链接超时时间,token加密的信息,这些信息主要用来在后面用户点击此链接的时候跳转到重置密码的路由,当然我这里是简单设置验证制作出来的重置密码的url,大的门户网站可不会像这样如此简单构造链接和token的,这里经供参考吧;这里涉及到需要发送邮件,我采用的是netcore第三方组件MailKit(就目前为止个人感觉很好用,能兼容很多邮件的方式),这里我用的是qq邮箱服务器:

 /// <summary>
/// 发送邮件
/// </summary>
/// <param name="dicToEmail"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="name"></param>
/// <param name="fromEmail"></param>
/// <returns></returns>
public static bool _SendEmail(
Dictionary<string, string> dicToEmail,
string title, string content,
string name = "爱留图网", string fromEmail = "841202396@qq.com")
{
var isOk = false;
try
{
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content)) { return isOk; } //设置基本信息
var message = new MimeMessage();
message.From.Add(new MailboxAddress(name, fromEmail));
foreach (var item in dicToEmail.Keys)
{
message.To.Add(new MailboxAddress(item, dicToEmail[item]));
}
message.Subject = title;
message.Body = new TextPart("html")
{
Text = content
}; //链接发送
using (var client = new SmtpClient())
{
// For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
client.ServerCertificateValidationCallback = (s, c, h, e) => true; client.Connect("smtp.qq.com", , false); //这里是qq邮箱 // Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2"); // Note: only needed if the SMTP server requires authentication
client.Authenticate("您邮箱", "你的邮箱密码"); client.Send(message);
client.Disconnect(true);
}
isOk = true;
}
catch (Exception ex)
{ }
return isOk;
}

通过参数 message.Body = new TextPart("html") 来设置允许邮箱发送html,为了我修改邮件模板方便我这里自定义了邮件模板SettingEmail.html,主要是一些样式的设置和内容部分的设置:

 <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>爱留图 - 绑定邮箱模板</title>
</head>
<body>
<h3>{name} 您好:</h3>
<div style='padding-top:20px;width:100%'>{content}</div>
<div style='padding-top:30px;width:100%'>温馨提示:如果您不是<a href='http://www.lovexins.com:9527'>爱留图</a>用户,请勿点击。</div>
<div style='padding-top:50px;width:100%'>
<a href='http://www.lovexins.com:9527'>
<img style="width:50px;height:50px" src="http://www.lovexins.com:9527/images/ailiutu_user.png" />
<br />
爱留图网
</a>
</div>
<div style='padding-top:5px;width:100%;color:#ccc;border-top:1px solid #ccc'>此为系统邮件请勿回复</div>
</body>
</html>

有了html模板,咋们还需要把这个文件读取出来,加入到发送邮件内容中,读取html模板:

 public static async Task<string> _GetHtmlTpl(EnumHelper.EmEmailTpl tpl, string folderPath = @"D:\F\学习\vs2017\netcore\LovePicture.Web\wwwroot\tpl")
{
var content = string.Empty;
if (string.IsNullOrWhiteSpace(folderPath)) { return content; } var path = $"{folderPath}/{tpl}.html";
try
{
using (var stream = File.OpenRead(path))
{
using (var reader = new StreamReader(stream))
{
content = await reader.ReadToEndAsync();
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return content;
}

好的到这里,通过用户输入的绑定邮箱,然后发送邮件的功能就完成了,来看看效果吧:

怎么样是不是感觉瞬间逼格提升了很多呢哈哈,电子邮件的推广和用途很多公司都太小看了,我们公司也是哎,什么时候您公司能够重视并且能够像jd那样常常个你发推送邮件,那么估计你离成功不远了嘿嘿;

接受重置密码通知

来继续咋们的讲解,当用户点击重置密码的链接后,我们需要一个接受的地址,这里我的Action取名为ConfirmPassword,接受的参数和我构造重置密码确认链接时候差不多:

 /// <summary>
/// 接受重置密码通知
/// </summary>
/// <returns></returns>
public IActionResult ConfirmPassword(string expire, string token, string email, string t)
{
if (string.IsNullOrWhiteSpace(expire) || string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(email) || !email.Contains("@") || string.IsNullOrWhiteSpace(t))
{
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "无效的请求。" });
}
else if (t.Length != )
{
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "无效的请求。" });
} email = email.Trim().ToLower();
if (!DateTime.TryParse(expire, out var expires)) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "无效的请求!" }); }
else if (expires.AddMinutes() < DateTime.Now)
{
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "请求已过期,重新操作!" });
} var compareToken = PublicClass._Md5($"{expire}-{email}-{Request.Host.Host}");
if (!token.Equals(compareToken)) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "验证失败,无效的请求!" }); } var user = _context.ToUserInfo.SingleOrDefault(b => b.Email.ToLower() == email);
if (user == null) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "不存在该绑定邮箱的账号!" }); }
else if (user.Status == (int)EnumHelper.EmUserStatus.禁用)
{
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "该绑定邮箱的账号已被禁用,可以通过发送邮件至:841202396@qq.com联系客服!" });
} var key = $"checkConfirmPwd{email}";
if (!_cache.TryGetValue<MoUserInfo>(key, out var result))
{
_cache.Set<MoUserInfo>(key, new MoUserInfo { Id = user.Id, Email = email }, TimeSpan.FromMinutes());
} return View(new MoRegisterUser { UserName = email });
}

通过各种安全性的验证(如:链接过期,不存在此用户,token验证失败等),最终会跳转到填写重置密码界面:

重置密码的界面和提交后的处理方式,基本和登陆,注册差不多,所以这里我直接贴出提交重置密码的Action代码:

 /// <summary>
/// 提交重置的密码
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ConfirmPassword([Bind("UserName", "UserPwd", "ComfirmPwd")]MoRegisterUser registUser)
{
if (ModelState.IsValid)
{
if (string.IsNullOrWhiteSpace(registUser.UserPwd))
{
this.MsgBox("密码不能为空!");
return View(registUser);
}
else if (string.IsNullOrWhiteSpace(registUser.ComfirmPwd))
{
this.MsgBox("确认密码不能为空!");
return View(registUser);
}
else if (registUser.UserPwd != registUser.ComfirmPwd)
{
this.MsgBox("密码和确认密码不相同!");
return View(registUser);
} var key = $"checkConfirmPwd{registUser.UserName}";
if (!_cache.TryGetValue<MoUserInfo>(key, out var checkUser))
{
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "请求已过期,重新操作!" });
} var user = _context.ToUserInfo.Where(b => b.Id == checkUser.Id && b.Email == checkUser.Email).SingleOrDefault();
if (user == null)
{
_cache.Remove(key);
return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "重置的密码失败,请稍后重试!" });
} user.UserPwd = PublicClass._Md5(registUser.UserPwd.Trim());
var result = await _context.SaveChangesAsync();
if (result > )
{
_cache.Remove(key);
this.MsgBox("重置密码成功!");
}
else { this.MsgBox("重置密码失败!"); }
}
return View(registUser);
}

下面是确认密码界面的View设计

 @model LovePicture.Model.MoClass.MoRegisterUser

 @{
ViewData["Title"] = "重置密码";
} <h3><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> 重置密码</h3>
<form name="form_submit" asp-action="ConfirmPassword">
<div class="form-horizontal">
<h4> 爱留图:欢迎您成为我们的一份子,让我们一起留存珍惜的图片吧。</h4>
<hr />
<input type="hidden" name="UserName" value="@Model.UserName"/>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserPwd" class="col-md-2 control-label">密码</label>
<div class="col-md-10">
<input asp-for="UserPwd" required="required" class="form-control" />
<span asp-validation-for="UserPwd" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ComfirmPwd" class="col-md-2 control-label">确认密码</label>
<div class="col-md-10">
<input asp-for="ComfirmPwd" class="form-control" />
<span asp-validation-for="ComfirmPwd" class="text-danger"></span>
</div>
</div> <div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="提 交" name="btnSubmit" class="btn btn-default" />
<span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
</div>
</div>
</div>
</form>
<br />
<div>
<a href="/member/login">有账号去登录</a> | <a href="/member/register">没账号这里注册</a>
</div>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

好的,此此盘文章很长,如果您有耐心读完相信您能有好的收获,一天一进步,别人的经验只要您学习到了同样也是自己的了,就算您没有做过类似的功能或者系统,当您读完后您也有大概的思路了呢;如果帮助,请不吝点个“推荐”,谢谢。

AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码的更多相关文章

  1. AspNetCore - MVC实战系列(一)

    本章开篇先简单介绍下最近两周自己利用业余时间做的一个图片收集网站,当然这个是靠用户自己上传来收集不是去抓某些个网站的图片,那样没意义,这里我取名为“爱留图”:该网站的简单介绍大家可以参考下上篇的内容爱 ...

  2. WCF开发实战系列二:使用IIS发布WCF服务

    WCF开发实战系列二:使用IIS发布WCF服务 (原创:灰灰虫的家http://hi.baidu.com/grayworm) 上一篇中,我们创建了一个简单的WCF服务,在测试的时候,我们使用VS200 ...

  3. [转]ASP.NET MVC学习系列(二)-WebAPI请求 传参

    [转]ASP.NET MVC学习系列(二)-WebAPI请求 传参 本文转自:http://www.cnblogs.com/babycool/p/3922738.html ASP.NET MVC学习系 ...

  4. [CXF REST标准实战系列] 二、Spring4.0 整合 CXF3.0,实现测试接口(转)

    转自:[CXF REST标准实战系列] 二.Spring4.0 整合 CXF3.0,实现测试接口 文章Points: 1.介绍RESTful架构风格 2.Spring配置CXF 3.三层初设计,实现W ...

  5. spring mvc下实现通过邮箱找回密码功能

    1功能分析 通过spring mvc框架实现通过邮箱找回密码. 2 实现分析 主要是借助某个邮箱的pop3/smtp服务实现的邮件代发功能. 3 源码分析 3.1首先在用户表对应的javabean中加 ...

  6. ASP.NET MVC学习系列(二)-WebAPI请求

    继续接着上文 ASP.NET MVC学习系列(一)-WebAPI初探 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用Jquery 来发起异步请求实现 ...

  7. ASP.NET MVC学习系列(二)-WebAPI请求(转)

    转自:http://www.cnblogs.com/babycool/p/3922738.html 继续接着上文 ASP.NET MVC学习系列(一)-WebAPI初探 来看看对于一般前台页面发起的g ...

  8. ASP.NET MVC学习系列(二)-WebAPI请求 转载https://www.cnblogs.com/babycool/p/3922738.html

    继续接着上文 ASP.NET MVC学习系列(一)-WebAPI初探 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用Jquery 来发起异步请求实现 ...

  9. SQL Server 性能优化实战系列(二)

    SQL Server datetime数据类型设计.优化误区 一.场景 在SQL Server 2005中,有一个表TestDatetime,其中Dates这个字段的数据类型是datetime,如果你 ...

随机推荐

  1. 循环单词 java

    链接:https://www.nowcoder.com/questionTerminal/9d5fbe7750a34d0b91c73943f93b2d7d来源:牛客网如果一个单词通过循环右移获得的单词 ...

  2. SPM HW1 A project

    项目分析 --民航航班异常轨迹可视分析 最近完成的一个项目是一个可视化大作业--民航航班异常轨迹可视分析.要求利用已给的8G飞机的飞行记录数据,将飞机的飞行轨迹在浏览器中进行飞行轨迹高维可视化以及对异 ...

  3. Hadoop单机和伪分布式安装

    本教程为单机版+伪分布式的Hadoop,安装过程写的有些简单,只作为笔记方便自己研究Hadoop用. 环境 操作系统 Centos 6.5_64bit   本机名称 hadoop001   本机IP ...

  4. Mac本地编辑服务器代码

    windows上映射网络驱动器可以直接在本地编辑远程服务器的代码,那么在mac上怎么配置呢? 1.打开finder——前往——连接服务器——输入:smb://********(你的机器名) 2.输入服 ...

  5. cobbler自动安装系统

    一.简介 Cobbler是一个快速网络安装linux的服务,而且在经过调整也可以支持网络安装windows.该工具使用python开发,小巧轻便(才15k行python代码),使用简单的命令即可完成P ...

  6. linux vi hjkl由来

    很远原因来自历史 I was reading about vim the other day and found out why it used hjkl keys as arrow keys. Wh ...

  7. For循环及例题

    For循环    (1)循环操作某一个功能(执行某段代码)    (2)四要素                  循环初始值                  循环条件                 ...

  8. cephfs创建及挂载

    Ceph 文件系统( Ceph FS )是个 POSIX 兼容的文件系统,它使用 Ceph 存储集群来存储数据.Ceph 文件系统要求 Ceph 存储集群内至少有一个 Ceph 元数据服务器. 1.添 ...

  9. 这辈子只能碰到一次! 记一次SSL无故被撤消!

    SSL证书刚更新一切都那么正常, 突然有一天网站不能访问了, Chrome浏览器提示有风险, 没有继续访问链接,没有,没有, 重要的事情说三遍, 于是乎赶紧加班查原因, 发展浏览器报的错误是证书撤消( ...

  10. VC++6.0中不兼容问题

    记得上次用VC++6.0已经是很长一段时间之前的事情了.这次由于需要学习计算机图形学,要开始学这写一些算法之类的,我又开始了VC++之旅. 重新安装一个vc++,我用的是Visual C++ 6.0( ...