I'm working on adding reset password functionality on our K12 site. I have created a simple controller and view that accepts the userid and reset token as parameters and displays a simple password/confirm password view to reset the users password. The URL is sent to the user via a email template. This is in a area called Coordinators. I'm using IIS 10 on local and dev.
This works locally when testing but when loaded to the dev server I get a 404 error. I have used RouteDebugger and can see the route on the dev site, but it will not give route information on a 404 page. The route works locally though.
I have added a second controller/view as a test incase there is a issue loading a backend resource. This controller just displays the text "Index". I get the same result. It works locally but fails on the dev server. Interesting note: If I log into the dev sever and access the page it works, but using the same URL from my local PC fails with the 404 error.
Local and the dev site point to the same Kentico database and the web.config files match in both environments.
I am missing something but I haven't been able to figure it out. Do I need to do something with Kentico or is this an IIS issue?
Below are examples of the code.
reset path:
... /Coordinators/resetpassword/Reset?userId=3779&token=AQAAANCMnd8BFd ...
Route class:
public class CoordinatorsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Coordinators";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Coordinator Search",
"Coordinators/search",
new { controller = "Search", action = "Index", searchText = UrlParameter.Optional, pageNumber = UrlParameter.Optional, indexes = "BCIndex;BCFileIndex" }
);
context.MapRoute(
"Coordinator_Login",
"Coordinators/login/{action}",
new { controller = "login", action = "Index" }
);
context.MapRoute(
"Coordinator_Logout",
"Coordinators/logout/{action}",
new { controller = "Logout", action = "Index" }
);
context.MapRoute(
"Coordinator_Register",
"Coordinators/register/{action}",
new { controller = "register", action = "Index" }
);
context.MapRoute(
"Coordinator_Reset_Password",
"Coordinators/resetpassword/{action}",
new { controller = "ResetPassword", action = "Index" }
);
context.MapRoute(
"Coordinator_Forgot_Password",
"Coordinators/forgotpassword/{action}",
new { controller = "ForgotPassword", action = "Index" }
);
context.MapRoute(
"Coordinator_UnlockAccount",
"Coordinators/unlockaccount/{action}",
new { controller = "UnlockAccount", action = "Index" }
);
context.MapRoute(
"Coordinator_Profile",
"Coordinators/profile/{action}",
new { controller = "profile", action = "Index" }
);
context.MapRoute(
"Coordinator_Directory",
"Coordinators/user-directory/{action}",
new { controller = "directory", action = "Index" }
);
context.MapRoute(
"Coordinator_Default",
"Coordinators/{controller}/{action}"
);
}
}
Controller class:
public class ResetPasswordController : BaseController
{
public IAccountManager _accountManager { get; set; }
public ILocalizationService _localizationService { get; }
public ResetPasswordController(IAccountManager accountManager,
IBusinessDependencies dependencies)
: base(dependencies)
{
_accountManager = accountManager ?? throw new ArgumentNullException(nameof(accountManager));
_localizationService = dependencies.LocalizationService ?? throw new ArgumentNullException(nameof(dependencies.LocalizationService));
}
[AllowAnonymous]
[HttpGet]
public ActionResult Index()
{
ResetPasswordViewModel model = new ResetPasswordViewModel();
return View(model);
}
[AllowAnonymous]
[HttpGet]
public async Task<ActionResult> Reset(int? userId, string token)
{
var model = new ResetPasswordViewModel();
IdentityManagerResult<ResetPasswordResultState, ResetPasswordViewModel> result = new IdentityManagerResult<ResetPasswordResultState, ResetPasswordViewModel>();
try
{
result = await _accountManager.VerifyResetPasswordTokenAsync(userId ?? 1, token);
if (result.Success)
{
model = result.Data;
}
else
{
model.Status = _localizationService.Localize("membership.passwreset.failed");
}
}
catch (InvalidOperationException)
{
model.Status = _localizationService.Localize("membership.passwreset.failed");
}
return View("Reset", model);
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Reset(ResetPasswordViewModel model)
{
IdentityManagerResult<ResetPasswordResultState> result = new IdentityManagerResult<ResetPasswordResultState>();
if (ModelState.IsValid)
{
result.Success = false;
result.ResultState = ResetPasswordResultState.PasswordNotReset;
model.Status = _localizationService.Localize("membership.passwordreset.failed");
try
{
result = await _accountManager.ResetPasswordAsync(model);
if (result.Success)
{
var emailMessage = new EmailMessage();
var macroResolver = MacroResolver.GetInstance();
emailMessage.Recipients = model.UserName;
EmailSender.SendEmail(SiteContext.CurrentSiteName, emailMessage, "Membership.PasswordReset", macroResolver, false);
model.Status = _localizationService.Localize("membership.passwordreset.success").Replace("%username%", model.UserName);
return Redirect("/coordinators/login");
}
else
{
model.Status = _localizationService.Localize("membership.passwordreset.failed");
}
}
catch (InvalidOperationException)
{
model.Status = _localizationService.Localize("membership.passwordreset.failed");
}
}
return View(model);
}
}
View class(Reset.cshtml):
@model #####.Areas.Coordinators.Models.ResetPasswordViewModel
@{ Layout = "~/Areas/Coordinators/Views/Shared/_Layout.cshtml"; }
<div class="container">
@using (Html.BeginForm())
{
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div><span class="required-field"></span> - denotes a required field</div>
@Html.AntiForgeryToken()
<input type="hidden" name="ResetToken" value="@Model.ResetToken" />
<input type="hidden" name="UserName" value="@Model.UserName" />
<div class="form-group">
@Html.LabelFor(m => m.Password) @if (ModelMetadata.FromStringExpression("UserName", ViewData).IsRequired)
{<span class='required-field'></span>}
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.PasswordVerify) @if (ModelMetadata.FromStringExpression("UserName", ViewData).IsRequired)
{<span class='required-field'></span>}
@Html.ValidationMessageFor(m => m.PasswordVerify, "", new { @class = "text-danger" })
@Html.PasswordFor(m => m.PasswordVerify, new { @class = "form-control" })
</div>
<div>
<button type="submit" class="btn btn-green">Submit</button>
</div>
<div class="form-group">
<span class="">@Html.DisplayFor(m => m.Status)</span>
</div>
}
</div>