MVC4 - form doesn't show new model values - c#

I have a controller with 2 actions: 1 for displaying a form and 2nd for processing the submitted form. Smth like this:
public ActionResult Create()
{
TestModel model = new TestModel() { Value1 = "aaa" };
return View(model);
}
[HttpPost]
public ActionResult Create(TestModel model)
{
model.Value2 = "bbb";
return View(model);
}
As you can see I pre-populate Value1 with "aaa" so it appears on the form - this part works fine. However, when I submit the form I would like to fill other properties of the model object and re-display the form using the same view.
The problem is that on submit the form still displays the original model object and not the updated one i.e. only Value1 is populated with "aaa" and Value2 is empty.
The view I use is the Auto-generated one by scaffolding option:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>TestModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Value1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value1)
#Html.ValidationMessageFor(model => model.Value1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Value2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value2)
#Html.ValidationMessageFor(model => model.Value2)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}

You have to call ModelState.Clear() so the result will be the expected. MVC does assumptions when POSTed data is sent back to the client. But beware, clearing it manually might cause undesired results ...
You can read more about here: Asp.net MVC ModelState.Clear and http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx

Related

Validating MVC model extracted from session

I’m making an MVC online web shopping app, so after the shopping cart page I’ve got ProcessStepOne action in which the user should fill his data.
[Authentication]
public ActionResult ProcessStepOne()
{
ProcessOrderViewModel model = GetOrderData();
return View("ProcessOrderStepOne", model);
}
private ProcessOrderViewModel GetOrderData()
{
ProcessOrderViewModel model = (ProcessOrderViewModel)Session["Process"];
if(model==null)
{
model = new ProcessOrderViewModel();
}
return model;
}
My ProcessOrderViewModel model is:
public class ProcessOrderViewModel
{
public ProcessOrderViewModel()
{
this.PrivateIndividualData = new PrivateIndividualModel();
this.OrderDiscoutPrice = new OrderDiscountPriceModel();
}
public PrivateIndividualModel PrivateIndividualData { get; set; }
public OrderDiscountPriceModel OrderDiscoutPrice { get; set; }
}
And my View ProcessOrderStepOne is:
#model ProcessOrderViewModel
<form id="formOrderData" method="post" action="#Url.Action("ProcessStepTwo")">
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label> First Name </label>
#Html.TextBoxFor(x => x.PrivateIndividualData.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.FirstName)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label> Last Name </label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Family, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Family)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Email, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Email)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="tel">#StringResources.GetResourceString("UserManagement", "Register_PhoneLabel")</label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Telephone, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Telephone)
</div>
</div>
</div>
</form>
So, my second step is just checking the values the user has entered and then show him for verification. If everything is OK I save the data in the session so the user can return to the previous ProcessStepOne action or proceed to making order with the MakeOrder action.
public ActionResult ProcessStepTwo(ProcessOrderViewModel model)
{
if (ModelState.IsValid)
{
Session["Process"] = model;
return View("ProcessOrderStepTwo", model);
}
return View("ProcessOrderStepOne", model);
}
And the view is:
#model ProcessOrderViewModel
<section class="form-section">
<p>
<a href='#Url.Action("ProcessStepOne")'>CHANGE</a>
<span class="text-semibold">Name:</span> #Model.PrivateIndividualData.FirstName <br>
<span class="text-semibold">Last Name:</span> #Model.PrivateIndividualData.Family <br>
<span class="text-semibold">E-mail:</span> #Model.PrivateIndividualData.Email<br>
<span class="text-semibold">Телефон:</span>#Model.PrivateIndividualData.Telephone <br>
</p>
</section>
<a href='#Url.Action("MakeOrder")'>PROCEED TO MAKE ORDER</a>
And, here is my last action which just gets the data from the session:
public ActionResult MakeOrder()
{
var data = (ProcessOrderViewModel)System.Web.HttpContext.Current.Session["Process"];
// make order with this data
return View("Thank You");
}
So my questions are: in the MakeOrder action I just take the model from the session without any verification. How can I validate it again and also is it right storing the data in session so I can return to make modifications or proceed to make an order. Is this the right thing to do because I can't think of another way.
In the MakeOrder action I just take the model from the session without any verification. How can I validate it again?
You can use TryValidateModel in your controller to re-validate your model after retrieving it from Session. However, from my testing it appears that this method only validates one level deep in the model. With this in mind, you code would be something like the following:
public ActionResult MakeOrder()
{
var data = (ProcessOrderViewModel)System.Web.HttpContext.Current.Session["Process"];
bool individualDataValid = TryValidateModel(data.PrivateIndividualData);
bool discountDataValid = TryValidateModel(data.OrderDiscoutPrice);
if (individualDataValid && discountDataValid)
{
// make order with this data
return View("Thank You");
}
//Error condition
return View("Error");
}
Is it right storing the data in session so I can return to make modifications or proceed to make an order. Is this the right thing to do because I can't think of another way.
Personally, I avoid using Session if I can. However, I think it is perfectly justifiable for a "Shopping Basket" requirement such as yours. What I would say is that from a maintenance and testing perspective, it may be better if you create a wrapper object for Session access. See Session variables in ASP.NET MVC.
This has the following benefits:
All your Session access is in one place. Not scattered liberally across controllers.
No "Magic Strings" used across your solution for session access.
As an added bonus, because you have abstracted session access out into an object, your controllers can be more "testable" (provided it is not a static wrapper).

Display Error message in Validationsummary with Ajax

Goal:
If you retrieve a error based on the input, it should be displayed in ValidationSummary in relation to ajax without the webpage being refreshed.
Problem:
I have tried to make it but it doesn't work so well.
What part am I missing?
Thank you!
Info:
*I have found some webpages but they do not fit exactly to my purpose.
*I'm using ASP.net mvc
#model WebApplication1.Controllers.PersonnelModel
#{
ViewBag.Title = "Ajax";
}
<h2>Ajax</h2>
<h2>AjaxViewModel</h2>
#using (Html.BeginForm("HtmlViewModel", "Home", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Html Form Action" />
</p>
}
<br/>
<br />
<h2>AjaxViewModel</h2>
#using (Ajax.BeginForm("AjaxViewModel", "Home", new AjaxOptions { UpdateTargetId = "result" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div id="result"></div>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Ajax Form Action" />
</p>
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-ajax-unobtrusive#3.2.4/jquery.unobtrusive-ajax.min.js"></script>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult HtmlViewModel(PersonnelModel Pmodel)
{
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
[HttpPost]
public ActionResult AjaxViewModel(PersonnelModel Pmodel)
{
/*
ModelState.AddModelError("", "login is fail");
return View("Index", Pmodel);
*/
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
}
public class PersonnelModel
{
[Required(ErrorMessage = "UserName Required.")]
public string UserName { get; set; }
[Required(ErrorMessage = "Email id Required.")]
public string MailAdress { get; set; }
}
}
EDIT - 03/11/2017 : There is an easy way to do this
Create a partial view for the form, Let's call it Form.cshtml and move the markup needed for the form to that. For your ajax form, set the data-ajax-mode attribute value to replace and data-ajax-update value to the id of the same form.
If you are using Ajax.BeginForm helper method, this is how you will do
#model PersonnelModel
#using (Ajax.BeginForm("Create", "Home",
new AjaxOptions { UpdateTargetId = "myform",
InsertionMode = InsertionMode.Replace },new { id="myform"}))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(f => f.UserName)
#Html.ValidationMessageFor(model => model.UserName)
#Html.TextBoxFor(f => f.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
<input type="Submit" id="submit" value="Submit" class="btn btn-default" />
}
Now in your main view, simple call this partial view
#model PersonnelModel
<h2>My form below</h2>
#Html.Partial("Form",Model)
Now in the action method, when model state validation fails, return the partial view.
public ActionResult Create(PersonnelModel model)
{
if (ModelState.IsValid)
{
// to do : Save
}
if (Request.IsAjaxRequest())
{
return PartialView("Form",model);
}
return View(model);
}
Now when you submit the form and model state validation fails, the action method code will return the partial view result with the validation error messages (generated by the validation helpers) and the jquery.unobtrusive-ajax.js library code will replace (because we specified that with data-ajax-mode="replace") the content of the result of the jquery selector #data-ajax-update (the form tag and it's inner contents) with the response coming back from the server.
This should do it. Less client side code, compared to my old approach (below)
The Html.ValidationSummary method will be executed when the razor view gets executed. If you are doing a normal form post (non ajax), your action method usually returns to the same view when Model validation fails (assuming you write code like that) and the razor view code gets executed and the ValidationSummary method will read the validation errors from the model state dictionary and render the error messages.
When you use Ajax.BeginForm helper method the helper will generate some extra data attributes on the form and as long as you have included the jquery.unobtrusive-ajax.min.js script file, the form submit will be hijacked and it will do an ajax form submit instead.
When you do the ajax form submit, if you want to render model validation messages, you need to explicitly read the model validation errors and return that as a JSON response which your client side code can read and display in the UI.
[HttpPost]
public ActionResult Index(PersonnelModel model)
{
if (ModelState.IsValid)
{
return Json(new {status = "success", message= "Thanks for the details"});
}
else
{
var errors = new List<string>();
foreach (var modelStateVal in ViewData.ModelState.Values)
{
errors.AddRange(modelStateVal.Errors.Select(error => error.ErrorMessage));
}
return Json(new {status = "validationerror", errors = errors});
}
}
Now in your view, make sure you have a success handler for your ajax begin form to handle the response json
#using (Ajax.BeginForm("Index", "Home", new AjaxOptions { OnSuccess = "OnSuccess", }))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(model => model.MailAdress)
<!--Your other form input elements-->
<input type="submit" value="Html Form Action" />
}
Note that i used #Html.ValidationSummary method with 2 overloads and passing an empty string as the first param. This will always render a ul element within the div which has class validation-summary-valid.
Now create your OnSuccess function and check the response and see whether response has a status property and the value of it is validationerror. If yes, loop through the errors collection and add a new li element with the error.
function OnSuccess(response) {
var $summaryUl = $(".validation-summary-valid").find("ul");
$summaryUl.empty();
if (response.status === 'validationerror') {
$.each(response.errors,
function(a, b) {
$summaryUl.append($("<li>").text(b));
});
} else {
alert(response.message);
}
}

Html.BeginForm Not work properly

I'm trying to use some features of MVC. In practice I made this view:
#using (Html.BeginForm("ResetPasswordToken", "Account", FormMethod.Post, new { rt = #Request.QueryString["rt"] })))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "Reset Password non riuscito")
<div class="container above-footer login-form">
<div class="col-md-6" align="center" style=" margin-left:25%; margin-top:100px; margin-bottom:100px;">
<div class="editor-label">
#Html.LabelFor(m => m.ResetToken)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.ResetToken, new { #Value = #Request.QueryString["rt"] })
#Html.ValidationMessageFor(m => m.ResetToken)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.NewPassword)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.NewPassword)
#Html.ValidationMessageFor(m => m.NewPassword)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.ConfirmPassword)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.ConfirmPassword)
#Html.ValidationMessageFor(m => m.ConfirmPassword)
</div>
<p>
<input type="submit" value="Change Password" />
</p>
</div>
</div>
The view is used to reset a password and i want to call the method ResetPasswordToken in AccountController when i click on "Change Password".
The AccountController is so structured, but it doesn't go to the method when i click on button:
[HttpPost]
public ActionResult ResetPasswordToken()
{
return View();
}
[HttpPost]
public ActionResult ResetPasswordToken(RecoverPasswordModel model)
{
if (ModelState.IsValid)
{
if (WebSecurity.ResetPassword(model.ResetToken, model.NewPassword))
{
return RedirectToAction("PasswordResetSuccess");
}
else
{
ModelState.AddModelError("", "The password reset token is invalid.");
}
}
return View(model);
}
Please someone can help me?
Thank you very much and good holidays
Roberto
You have two action with same name and same httppost attibute. Mark first action (without parameter) as httpget
Ok. It missing [AllowAnonymus]. Great and thank you. Now one question. What is the best method to pass the value of the tokens in the URL to the controller without use # Html.TextBoxFor (m => m.ResetToken, new { #Value = #Request.QueryString["rt"] }). I want to hidden this value to user.

ASP.NET MVC Controller actions with the same name

I've got a controller in MVC5 for an apply form. There is a GET to return the view and a POST to take the form data from the application and process it:-
public ActionResult Apply(string jobReference)
{
Apply form = new Apply();
Session[Settings.JOBREFERENCE] = jobReference;
return View(form);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Apply(Apply form)
{
if (ModelState.IsValid)
{
}
}
When I press the to submit the form, it calls the GET controller instead of the the POST controller, despite it having [HttpPost] on the latter.
If I actively renamed the HTTPPost controller and navigate to it, I see a Server Error, with a message saying it cannot be found. Submitting the form causes the page to crash.
Form Code
#model Salt.Models.Apply
<div class='applyForRoleExt popupBox'>
<img src="/img/closePopup.png" alt="close" />
<div class="innerContainer">
#using (Html.BeginForm("Apply", "Form", FormMethod.Post, new { enctype = "multipart/form-data", #id = "ApplyForm" }))
{
#Html.AntiForgeryToken()
<h6>Apply for this role</h6>
#*<div class="haveAccount">
<span>Have an account? Apply quickly by logging in now</span>
Login to my account
</div>*#
<div class="leftCol">
<label>Name<span>*</span></label>
<div class="inputBox">
#Html.TextBoxFor(m => m.Name)
</div>
<label>Email Address<span>*</span></label>
<div class="inputBox">
#Html.TextBoxFor(m => m.EmailAddress)
</div>
<label>Telephone Number<span>*</span></label>
<div class="inputBox">
#Html.TextBoxFor(m => m.TelephoneNumber)
</div>
<label>Portfolio Link<span>*</span></label>
<div class="inputBox">
#Html.TextBoxFor(m => m.PortfolioLink)
</div>
</div>
<div class="rightCol">
<label>CV Upload</label>
<div class="inputBox">
#Html.TextBoxFor(m => m.CV, new { #type = "file" })
</div>
<label>Covering Note</label>
<div class="inputArea">
#Html.TextAreaFor(m => m.CoveringNote)
</div>
</div>
<div class="actions">
<p class="terms">By submitting this form you consent to Salt Recruitment processing your personal data in accordance with our privacy policy and agree to future contact material.</p>
<!--<button class="submit">Apply Now</button>-->
<input type="submit" name="submit" value="Apply Now" />
</div>
}
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse("#ApplyForm");
});
</script>
Thanks,
Mike.
It's been fixed. It took a second pair of eyes but a rule in the web.config was removing the trailing slash from the form post URL and it was defaulting to loading the GET. No idea why that would cause the problem but it did.

MVC Retrieve Multi Entry from View in Controller

this is my controller:
public ActionResult Create() {
Number newNumber = new Number();
return View(newNumber);
}
and View :
#model PhoneBook.Models.Number
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="../../Scripts/jQuery.AddNewNumber.js" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Contact.Id)
<fieldset>
<legend>Number</legend>
<div class="TargetElements">
<div class="editor-label">
#Html.LabelFor(model => model.PhoneNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumber)
#Html.ValidationMessageFor(model => model.PhoneNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.NumberKind)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.NumberKind.Title, NumberKinds)
</div>
</div>
<p>
<input class="AddNew" value="Add New" type="button" /></p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
By press AddNew button (use jQuery AddNewNumber.js) new input+DropDown Added to form in client side. But there is a problem in retrieve values, When I have one entry element(include input+DropDown) I can retrieve values like the following in post of my controller:
[HttpPost]
public ActionResult Create(Number NewNumber, FormCollection collection) {
db.Numbers.Add(NewNumber);
db.SaveChanges();
return RedirectToAction("Index")
}
But when there is multi-entry how can I retrieve values and add them to DataBase?
Use by the name of element like this:
string[] PhoneNumbers = collection.GetValues("PhoneNumber");
You want all of your input elements to have the same name. Then in your POST action method, the parameter would be List. Here is an example:
Model Binding to a List of objects

Categories

Resources