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. webpack 安装流程

    我最近想看看wabpack,然后就面临着安装的问题,说实话,我一点也不懂cmd,怎么还需要用cmd安装呢.其实看教程上说可以在npm上安装,但是我打开npm总是出现闪退,所以我就选择了cmd 安装的过 ...

  2. Timestamp解析0000-00-00 00:00:00报格式错误

    mysql中存储的是Timestamp类型的0000-00-00 00:00:00, 但是在java程序中使用 Timestamp.valueOf("0000-00-00 00:00:00& ...

  3. CSS学习笔记汇总

    CSS语法格式:一个css规则,由一个选择器和一个格式声明语句构成    例如:h1{color:red; font-size:14px;} CSS选择器: 1.基本选择器 1)* 号选择器:通配符, ...

  4. Android开发之启动模式的深入理解

    standard(标准模式): 当用ApplicationContext去启动standard模式时会报错,是因为非Activity类型的Context没有所谓的任务栈, 所以需要为待启动的Activ ...

  5. 关于连接数据库的T-SQL语句中的一种小技巧

    (编程生活中,我们经常会用到数据库.然后在通过T-SQL语句来对数据库进行操作的时候,遇到很多麻烦.话说昨天我就被困扰了一天.明明这个T-sql插数据的语句放在数据库运行的时候没有问题,到了java代 ...

  6. java学习笔记 --- 继承

    继承 (1)定义:把多个类中相同的成员给提取出来定义到一个独立的类中.然后让这多个类和该独立的类产生一个关系,    这多个类就具备了这些内容.这个关系叫继承.  (2)Java中如何表示继承呢?格式 ...

  7. iOS开发之Xib和storyboard对比

    相同点: (2)都用来描述软件界面 (2)都用Interface Builder工具来编辑 不同点: (1)Xib是轻量级的,用来描述局部的UI界面 (2)Storyboard是重量级的,用来描述整个 ...

  8. iOS开发之类扩展

    在以往写代码时,我们经常是把声明写在.h文件中,把实现写在.m文件中,但是在实际开发中,如果把声明写在.h文件中会暴露程序很多属性(成员变量.成员变量的get和set方法),为了安全考虑,引入了类扩展 ...

  9. shell脚本监控目录下文件被篡改时报警

    思路: 目录下文件被篡改的几种可能: 1.被修改 2.被删除 3.新增文件 md5命令详解 参数: -b 以二进制模式读入文件内容 -t 以文本模式读入文件内容 -c 根据已生成的md5值,对现存文件 ...

  10. html中引入调用另一个html的方法

    html中引入调用另一个html的方法,尝试了好几种,都列出来: 其中第一种是最好的,其他的方法,可以尝试看看,是不是适合你当前项目 一.div+$("#page1").load( ...