On my website I use MVC Authentication, if a user wants to reset his password, he clicks on 'Forgot Password' then he gets by email a link with a unique code to reset his password.
My issue is if for some reason the reset fails (for example- password and password confirmation don't match etc) then it returns to the View without the unique code that was generated in the link that the user got by email and the reset password won't work afterwards (code query string param)
How can I keep the query string unique code when the reset fails and return it to the View?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
}
ViewBag.Error = "Password reset failed";
return View();
}
And this is the form:
<form id="reset-form" method="post" action="Account/ResetPassword">
#Html.AntiForgeryToken()
<div class="header">
<h2>Reset Password</h2>
#if (!String.IsNullOrEmpty(ViewBag.Error))
{
<div class="invalid">
#ViewBag.Error
</div>
}
</div>
<div class="inputField">
<input type="email" id="email" name="email" placeholder="USERNAME" />
</div>
<div class="inputField">
<input type="password" id="password" name="password" placeholder="PASSWORD" />
</div>
<div class="inputField">
<input type="password" id="confirm-password" name="confirmPassword" placeholder="CONFIRM PASSWORD" />
<input type="submit"/>
</div>
</form>
I need it to return to the View like this:
~/account/resetpassword?code=xyz
The URL will remain the same as what you post when the view is returned unless you do a redirect (not recommended).
However, the issue isn't that you need the URL to change after the action method runs. You need to change the URL that is posted to the action method to include the query string so it is there when the action method returns the view. This can be done by simply changing the URL of the form.
Note that you should never hard code the URL in MVC views. Instead, you should always use controller, action, and other route values to resolve the URL using the UrlHelper (and related extension methods such as ActionLink). The routing infrastructure should be the only place where URLs exist so they can be changed in one place.
In this case the solution is to use Html.BeginForm to build up the form tag based on routing.
#using (Html.BeginForm("ResetPassword", "Account", new { code = "xyz" }, FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="header">
<h2>Reset Password</h2>
#if (!String.IsNullOrEmpty(ViewBag.Error))
{
<div class="invalid">
#ViewBag.Error
</div>
}
</div>
<div class="inputField">
<input type="email" id="email" name="email" placeholder="USERNAME" />
</div>
<div class="inputField">
<input type="password" id="password" name="password" placeholder="PASSWORD" />
</div>
<div class="inputField">
<input type="password" id="confirm-password" name="confirmPassword" placeholder="CONFIRM PASSWORD" />
<input type="submit"/>
</div>
}
Of course, you shouldn't hard code "xyz" here either, it should be passed to the view from the HttpGet ResetPassword method either in a Model or in ViewBag.
Note that if the url parameter of the route definition (typically in RouteConfig.cs) does not contain a parameter for code, it will automatically be added to the URL as a query string parameter.
I'm using Thymeleaf and had a similar issue. Here's how I solved it using the thymeleaf functionality:
HTML:
<form class="forms-sample" action="/login"
!!! th:action="#{/reset_password(token=${token})}" !!!
th:object="${passwordDto}" method="post">
**INSERT CODE HERE**
</form>
MVC Controller:
if(bindingResult.hasErrors()) {
model.addAttribute("token", tokenStr); !!!!!!!!
return "create_password";
}
try {
PasswordResetToken token = passwordResetTokenService.getByToken(tokenStr);
User user = token.getUser();
user = userMapper.setNewPassword(user, passwordDto);
userService.update(user);
passwordResetTokenService.delete(token);
return "home";
}catch (PasswordNotMatchException e) {
bindingResult.rejectValue("confirmNewPassword", "password_error",
"Confirm password did not match.");
model.addAttribute("token", tokenStr); !!!!!!!!
return "create_password";
}
catch (EntityNotFoundException e) {
return "create_password";
}
What I do is I put the token in the Model and if the post method encounters an error I make sure to add it again, before returning the view for creating a password.
The important thing to note is, as NightOwl mentioned, that the url stays the same, only the Model changes. Thats why we need to readd the token as a model attribute.
Related
I have the following model:
public class Card
{
[DataType(DataType.Date)]
[BindProperty]
public DateTime Day { get; set; }
[BindProperty]
public string Field { get; set; }
}
The following Controller:
// GET: Card
public async Task<IActionResult> Index(DateTime? day)
{
return View(model);
}
public async Task<IActionResult> Refresh(DateTime? Day, string Field)
{
return RedirectToAction("Index", Day);
}
The following View:
#model Card
<h1>Cards</h1>
<div class="text-center">
<label asp-for="Day" class="control-label"></label>
<input asp-for="Day" class="form-control" />
</div>
<div class="text-center">
<label asp-for="Field" class="control-label"></label>
<select asp-for="Field" class="form-control" asp-items="ViewBag.Fields"></select>
</div>
<form asp-action="Refresh">
#Html.HiddenFor(x => x.Day)
#Html.HiddenFor(y => y.Field)
<input type="submit" value="Refresh" class="btn btn-default" />
</form>
No matter what I change, I always get the initial Day value back and null as the Field, like the Model has never been changed…
So how can I post back the modified model to my controller?
your form is submitting the values from the hidden fields which are rendered on the page when the view first loads and then they are never modified (which is why you are seeing the default initialization for Day and for Field). Your editable fields are outside of the form and are what you're editing but they never get submitted to the server. I think the main takeaway here for you is that forms only know about inputs that exist inside of them (unless you write some javascript magic to handle this but there is no reason to do so in this case)
You need to remove the hidden fields and put your editable fields inside the form as follows:
#model Card
<h1>Cards</h1>
<form method="post" asp-action="Refresh">
<div class="text-center">
<label asp-for="Day" class="control-label"></label>
<input asp-for="Day" class="form-control" />
</div>
<div class="text-center">
<label asp-for="Field" class="control-label"></label>
<select asp-for="Field" class="form-control" asp-items="ViewBag.Fields"></select>
</div>
<input type="submit" value="Refresh" class="btn btn-default" />
</form>
you can also change your controller action to:
[HttpPost]
public async Task<IActionResult> Refresh(Card card)
{
return RedirectToAction("Index", card.Day);
}
I am new to MVC.NET and I am stopped at some point while passing data from controller to view. I have two action, one is for GET and another is for POST. When I am setting ViewBag values in my POST method action, it redirects me to View but passes the values using GET in the URL hence ViewBag values are not accessible in view.
Here is snapshot of the same:
View:
<div>
<p>#ViewData["FileName"]</p>
<p>#ViewData["myName"]</p>
<p>#ViewBag.myAdd</p>
<p>#ViewBag.someData</p>
</div>
<div>
<form id="myForm" action="~/Test/Index">
<input type="text" name="myName"/>
<input type="text" name="myAdd" />
<input type="file" name="myFile"/>
<input type="submit"/>
</form>
</div>
CONTROLLER
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(HttpPostedFileBase file, FormCollection data)
{
ViewBag.FileName = Convert.ToString(file.FileName);
ViewBag.myName = Convert.ToString(data["myName"]);
ViewBag.myAdd = Convert.ToString(data["myAdd"]);
ViewBag.someData = "someData";
return View();
}
On submit of form, it redirects me to http://localhost:65077/Test/Index?myName=mYname&myAdd=MyAdddress&myFile=432f7018-d505-4b0b-8cba-505d62b5472d.png
it would be great if someone can help and explain the same to me.
thanks in advance.
Per default form-data is appended to the URL when send back to the server(GET-method). You have to change this by useing the method attribute:
<form id="myForm" action="~/Test/Index" method="post">
<input type="text" name="myName"/>
<input type="text" name="myAdd" />
<input type="file" name="myFile"/>
<input type="submit"/>
</form>
I'm developing an application on asp.net.I use the standard modelbinder
I have code
#model Sciencecom.Models.Billboards1
#{
ViewBag.Title = "CreateBilboard";
SelectList owners = new SelectList(new SciencecomEntities().Owners.Select(m=>m.Name).ToList());
}
#using (Html.BeginForm("Bilboard", "Data", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
#Html.ValidationMessage("Error")
#*владелец*#
<input type="text" name="Locality" value="345"/>
<input type="text" name="Locality" value="3435" />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Добавить" class="btn btn-default" />
</div>
</div>
</div>
}
I send form on controller.but i have issue .I have reference-null
public ActionResult Bilboard( IEnumerable<Sciencecom.Models.Billboards1> Billboard, IEnumerable<Sciencecom.Models.Surface> test)
{
return View();
}
what ideas?
Inside your form you have only 2 input fields with the same name (Locality) which is quite confusing. So on your server all you can get is a variable with the same name as your input field because that's the only information that is posted back when the form is submitted:
public ActionResult Bilboard(Locality locality)
{
...
}
In the code you have shown in your question your Bilboard action seem to be taking some Billboard and test collection arguments but they are not present as input fields inside your form so you cannot possibly expect them to be populated.
In my form I see that the data is being sent as Querystring instead of form data (that I expect it to).
My page:
#using (Html.BeginForm("AAForm", "Test", FormMethod.Post, new { id = "myAForm" }))
{
<label for="a">A</label>
<input id="aid" name="aname" style="width: 300px" required validationMessage="Select"/>
<button type="submit" class="btn btn-primary" value="aa" > Go </button>
}
ControllerSignature
public ActionResult GetValues(FormCollection formCollection){
//Some code }
And I am making an AJAX call to GetValues().
What could I be doing wrong? Pls let me know if I should be posting more information.
Another question: What is "_:" in the query string? And it has some random number value.
Thanks.
add an [HttpPost] attribute on your ActionResult.
I have a form that has a hidden field wich stores a object. This object is a RoutesValues (I want to store a reference because when I process the form I want to redirect to a route). The action that processes the form is:
public ActionResult Añadir(string userName, string codigoArticulo, string resultAction, string resultController, object resultRouteValues, int cantidad)
{
processForm(codigoArticulo, cantidad);
if (!ModelState.IsValid)
TempData["Error"] = #ErrorStrings.CantidadMayorQue0;
if (!string.IsNullOrWhiteSpace(resultAction) && !string.IsNullOrWhiteSpace(resultController))
return RedirectToAction(resultAction, resultController, resultRouteValues);
return RedirectToAction("Index", "Busqueda", new {Area = ""});
}
and my form is:
#using (Html.BeginForm("Añadir", "Carrito", FormMethod.Get, new { #class = "afegidorCarrito" }))
{
<fieldset>
<input type="hidden" name="codigoArticulo" value="#Model.CodiArticle" />
<input type="hidden" name="resultController" value="#Model.Controller" />
<input type="hidden" name="resultAction" value="#Model.Action" />
<input type="hidden" name="resultRouteValues" value="#Model.RouteValues" />
<input type="text" name="cantidad" value="1" class="anadirCantidad" />
<input type="submit" />
</fieldset>
}
the problem I have is that resultRouteValues gets passed as a string instead of an object. Is there any way to fix this?
Thanks.
No, there is no easy way if RouteValues is a complex object. You will have to serialize the object into some text representation into this hidden field and then deserialize it back in your controller action. You may take a look at MvcContrib's Html.Serialize helper.