Introduction
This is the 3rd part of this series So before continuing with this article, I would recommend you to read the previous article of this series where I have shown you how to verify registration link and create a login page with Remember Me option if you have not read the article till now.
In this series, till now we have learned to create registration page with email verification and creating a login page with Remember Me option. Here in this article, we will learn to create forgot password page in our asp.net MVC application step by step. Here we will do followings steps...
- First, we will create a page where we will ask user to provide their email id
- Second, we will verify user account with the provided email id and we will send reset password link to users email id.
- Third, we will have a function for verifying the reset password link and if the link is valid then we will provide a page to the user for reset their password.
- Finally, we will update the new password in the database.
Let's start implementing Forgot Password functionality in asp.net MVC application.
After downloading the application we will open the application in Visual Studio 2015.
Let's start implementing forgot password functionality in asp.net MVC application.
Step-1: Add a new MVC action in UserController.
[HttpGet] public ActionResult ForgotPassword() { return View(); }
Step-2: Add view for the ForgotPassword action.
@{ ViewBag.Title = "Forgot Password"; } <h2>Forgot Password</h2> @using (Html.BeginForm()) { <div class="form-horizontal"> <hr /> <div class="text-success"> @ViewBag.Message </div> <div class="form-group"> <label class="control-label col-md-2">Email ID</label> @Html.TextBox("EmailID", "",new { @class="form-control"}) </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Submit" class="btn btn-success" /> </div> </div> </div> }
Update SendVerificationLinkEmail function
[NonAction] public void SendVerificationLinkEmail(string emailID, string activationCode, string emailFor = "VerifyAccount") { var verifyUrl = "/User/"+emailFor+"/" + activationCode; var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, verifyUrl); var fromEmail = new MailAddress("dotnetawesome@gmail.com", "Dotnet Awesome"); var toEmail = new MailAddress(emailID); var fromEmailPassword = "******"; // Replace with actual password string subject = ""; string body = ""; if (emailFor == "VerifyAccount") { subject = "Your account is successfully created!"; body = "<br/><br/>We are excited to tell you that your Dotnet Awesome account is" + " successfully created. Please click on the below link to verify your account" + " <br/><br/><a href='" + link + "'>" + link + "</a> "; } else if (emailFor == "ResetPassword") { subject = "Reset Password"; body = "Hi,<br/>br/>We got request for reset your account password. Please click on the below link to reset your password" + "<br/><br/><a href="+link+">Reset Password link</a>"; } var smtp = new SmtpClient { Host = "smtp.gmail.com", Port = 587, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromEmail.Address, fromEmailPassword) }; using (var message = new MailMessage(fromEmail, toEmail) { Subject = subject, Body = body, IsBodyHtml = true }) smtp.Send(message); }
You can see the above function, I have added an additional parameter "emailFor" with default value "VerifyAccount" for identifying what email is the email for. According to the emailFor parameter value, email content will be different.
Step-3: Add another action in our UserController for verifying the email id.
[HttpPost] public ActionResult ForgotPassword(string EmailID) { //Verify Email ID //Generate Reset password link //Send Email string message = ""; bool status = false; using (MyDatabaseEntities dc = new MyDatabaseEntities()) { var account = dc.Users.Where(a => a.EmailID == EmailID).FirstOrDefault(); if (account != null) { //Send email for reset password string resetCode = Guid.NewGuid().ToString(); SendVerificationLinkEmail(account.EmailID, resetCode, "ResetPassword"); account.ResetPasswordCode = resetCode; //This line I have added here to avoid confirm password not match issue , as we had added a confirm password property //in our model class in part 1 dc.Configuration.ValidateOnSaveEnabled = false; dc.SaveChanges(); message = "Reset password link has been sent to your email id."; } else { message = "Account not found"; } } ViewBag.Message = message; return View(); }
You can see here in line 11, we have generated a unique identification no (GUID) for sending with the Reset Password email. This Unique identification no we will store in our user table so we can use this unique no to find the user account associated with it. Ok.
Step-4: Update User table from the database.
Step-5: Update Entity Model.
Open MyModel.edmx by double-clicking from Solution Explorer > Models. Right-click anywhere in the empty area > click on Update Model from Database from the context menu > Save.
Step-6: Add a ViewModel.
Go to Solution Explorer > Models folder > Right-click on the Models folder > Add > Class... > Enter class name "ResetPasswordModel.cs" > Click on Add button.
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; namespace RegistrationAndLogin.Models { public class ResetPasswordModel { [Required(ErrorMessage = "New password required", AllowEmptyStrings = false)] [DataType(DataType.Password)] public string NewPassword { get; set; } [DataType(DataType.Password)] [Compare("NewPassword", ErrorMessage ="New password and confirm password does not match")] public string ConfirmPassword { get; set; } [Required] public string ResetCode { get; set; } } }
Step-7: Add a new MVC Action for verifying ResetPassword link.
public ActionResult ResetPassword(string id) { //Verify the reset password link //Find account associated with this link //redirect to reset password page if (string.IsNullOrWhiteSpace(id)) { return HttpNotFound(); } using (MyDatabaseEntities dc = new MyDatabaseEntities()) { var user = dc.Users.Where(a => a.ResetPasswordCode == id).FirstOrDefault(); if (user != null) { ResetPasswordModel model = new ResetPasswordModel(); model.ResetCode = id; return View(model); } else { return HttpNotFound(); } } }
Here we have verified the link from the unique identification no what we sent to the Reset Password email link. The unique identification no we will get here in the id parameter.
If we will find a valid account associated with this unique no then we will return a view with the ViewModel after setting the ResetCode. So the user can view the page from where they can reset their password.
Step-8: Add view for the ResetPasswod action of UserController.
ResetPassword.cshtml@model RegistrationAndLogin.Models.ResetPasswordModel @{ ViewBag.Title = "Reset Password"; } <h2>Reset Password</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Reset Password</h4> <hr /> <div class="text-danger"> @ViewBag.Message </div> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.NewPassword, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.NewPassword, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.NewPassword, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.HiddenFor(a=>a.ResetCode) </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts{ <script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> }
Step-9: Add an another MVC action here in the UserController for Reset the password.
[HttpPost] [ValidateAntiForgeryToken] public ActionResult ResetPassword(ResetPasswordModel model) { var message = ""; if (ModelState.IsValid) { using (MyDatabaseEntities dc = new MyDatabaseEntities()) { var user = dc.Users.Where(a => a.ResetPasswordCode == model.ResetCode).FirstOrDefault(); if (user != null) { user.Password = Crypto.Hash(model.NewPassword); user.ResetPasswordCode = ""; dc.Configuration.ValidateOnSaveEnabled = false; dc.SaveChanges(); message = "New password updated successfully"; } } } else { message = "Something invalid"; } ViewBag.Message = message; return View(model); }See in line 11, we make the ResetPasswordCode empty for invalidate the reset password link. So the reset password link cannot be used again for resetting password once it is used.