Weird Razor field caching while posting back same page - asp.net-mvc

The situation would seem straight-forward, but I am getting a very strange bug. Perhaps someone can explain why? I have provided full code below of the relevant parts.
The basic idea is:
Enter a company number
Use the Companies House Gateway API to retrieve results
Display the company name & address on the same page, to allow review
The problem is, that entering a different company number results in the previous results always being displayed (just the variable number of address lines change).
I have break-pointed in the controller and in the view and the correct company details are being returned, but they are not displayed. I have never seen a simple page like this go wrong.
I replaced the Gateway service with hard-wired companies to elimiate that service and it still goes wrong.
Account controller (Step1 action)
[AllowAnonymous]
public ActionResult Step1()
{
RegisterCompanyViewModel vm = new RegisterCompanyViewModel();
return View(vm);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Step1(RegisterCompanyViewModel model)
{
if (ModelState.IsValid)
{
if (model.CompanyNumber.EndsWith("1"))
{
model.Address = new List<string>() { "Line 1", "line 2", "Line 3"};
model.CompanyName = "Company name 1";
}else if (model.CompanyNumber.EndsWith("2"))
{
model.Address = new List<string>() { "Line 4", "line 5", "Line 6", "Line 7" };
model.CompanyName = "Company name 2";
}
else
{
ModelState.AddModelError("CompanyNumber", "Company number not found");
}
}
return View(model);
}
RegisterCompanyViewModel class
public class RegisterCompanyViewModel
{
[Display(Name="Company Number")]
public string CompanyNumber { get; set; }
[Display(Name = "Company Name")]
public string CompanyName { get; set; }
[Display(Name = "Address")]
public List<string> Address { get; set; }
public RegisterCompanyViewModel()
{
this.Address = new List<string>();
}
}
Step1.cshtml
#model MvcApplication.Models.RegisterCompanyViewModel
#{
ViewBag.Title = "Step1";
}
<h2>Enter your Company Number</h2>
#using (#Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CompanyNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.CompanyNumber)
#Html.ValidationMessageFor(x => Model.CompanyNumber)
</div>
#Html.ValidationMessageFor(model => model.CompanyNumber, "", new { #class = "text-danger" })
</div>
#if (!string.IsNullOrEmpty(Model.CompanyName))
{
<div class="form-group">
#Html.LabelFor(model => model.CompanyName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.CompanyName)
#Html.ValidationMessageFor(x => Model.CompanyName)
</div>
</div>
}
#if (Model.Address.Any())
{
for (int i = 0; i < Model.Address.Count(); i++)
{
<div class="form-group">
#Html.LabelFor(model => model.Address, "Address lines " + i.ToString(), htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.Address[i])
#Html.ValidationMessageFor(x => Model.Address[i])
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save Details" class="btn btn-default" />
</div>
</div>
}
else
{
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Lookup" class="btn btn-default" />
</div>
</div>
}
</div>
}
Notes:
I am using the latest MVC "everything" as of yesterday.
I even added a timer display to the main layout, so I could confirm the page was fully refreshing (it is).
Screen shots after entering "1" then "2" as the company numbers:
Note the new "Line 7" is visible, but all other fields have their previous values:
Any ideas on why this form page is showing the previous field values when re-submitting?
Solution:
My revised code, based on the very useful explanation by #Chris Pratt:
[AllowAnonymous]
public ActionResult Step1([Bind(Prefix="id")]string companyNumber)
{
RegisterCompanyViewModel vm = new RegisterCompanyViewModel();
vm.CompanyNumber = companyNumber;
if (!string.IsNullOrEmpty(companyNumber))
{
Gateway gateway = new Gateway(senderId, password);
var company = gateway.GetCompanyDetails(companyNumber);
if (company != null)
{
vm.CompanyName = company.CompanyName;
vm.Address = company.AddressLines;
}
else
{
ModelState.AddModelError("CompanyNumber", "Company number not found");
}
}
return View(vm);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Step1(RegisterCompanyViewModel model, string action)
{
if (ModelState.IsValid)
{
if (action == "Search for company")
{
return RedirectToAction("Step1", new { id = model.CompanyNumber });
}
else if (action == "Save Details")
{
// Save changes now
}
}
return View(model);
}
The page now looks like this:

This is a common confusion. The posted data goes into the ModelState object, and anything in ModelState overrides anything on your model. This is by design. For example consider the scenario where you're editing an existing entity, but in the edit, the user had an error. If Razor used the model's data, then when the view was returned for the user to fix whatever problem there was, all their edits would be gone, replaced with the original model data. By have ModelState take precedence, it ensures that the posted data is preserved when returning the form again.
What you need to do is follow the proper PRG pattern (Post-Redirect-Get). If the initial submission is good, you redirect back to the same action to display it again, instead of just returning the view. That way, the ModelState is clean.

Related

ASP.NET MVC - Null Object in ViewModel on POST

Upon POST of an ActionController I am receiving the great ole' object reference not set to an instance of an object error.
Basically I need the ID of the userRequest to be saved WITH the requestResponse. (Foreign Key here)
Here is the code.
ViewModel:
public class RequestResponseViewModel
{
public Models.Request userRequest { get; set; }
public Models.RequestResponse requestResponse { get; set; }
}
View: In debug here there is value in model.userRequest.ID
#model UserRequests.ViewModels.RequestResponseViewModel
#{
ViewBag.Title = "Create";
}
<h2>Admin Response to Request</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.requestResponse.Response,
htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.requestResponse.Response, new {
#class = "form-control", #rows = 5 })
#Html.ValidationMessageFor(model =>
model.requestResponse.Response, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userRequest.ID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-2">
#Html.DisplayFor(model => model.userRequest.ID)
#Html.ValidationMessageFor(model => model.userRequest.ID, "", new { #class = "text-danger" })
</div>
#Html.LabelFor(model => model.requestResponse.Author, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-3">
#Html.EditorFor(model => model.requestResponse.Author, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.requestResponse.Author, "", new { #class = "text-danger" })
</div>
#Html.LabelFor(model => model.requestResponse.CreateDate, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-3">
<h5>#DateTime.Now</h5>
#Html.ValidationMessageFor(model => model.requestResponse.CreateDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-1">
<button type="reset" class="btn btn-default">Cancel</button>
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
<hr />
<h3 class="text-success">Original Request</h3>
<div class="row">
<div class="col-md-10">
<h4>#Html.DisplayFor(model => model.userRequest.Title)</h4>
</div>
</div>
<div class="row">
<div class="col-md-10">
<h4>#Html.DisplayFor(model => model.userRequest.Description)</h4>
</div>
</div>
}
<div>
#Html.ActionLink("Back to Browse", "Browse","Change")
</div>
Get ActionResult:
public ActionResult Create(int id)
{
UserRequestContextDataContext db = new UserRequestContextDataContext();
var request = (from m in db.Requests
where m.ID == id
select new Models.Request()
{
ID = m.ID,
Title = m.Title,
Description = m.Description,
BusinessUnit = m.BusinessUnit,
Author = m.Author,
ModuleName = m.MenuItem,
RequestStatus = 2,
SubmitDate = m.SubmitDate,
Type = m.Type,
UrgencyNum = m.UrgencyLevel
}).FirstOrDefault();
var reqResponse = new Models.RequestResponse();
var viewModel = new RequestResponseViewModel
{
userRequest = request,
requestResponse = reqResponse
};
return View(viewModel);
}
The "viewModel" here has everything I need. It's lost somewhere between the ActionResults..
And Finally the Post ActionResult:
[HttpPost]
public ActionResult Create(RequestResponseViewModel _requestResponseViewModel)
{
try
{
if (ModelState.IsValid)
{
using (UserRequestContextDataContext db = new UserRequestContextDataContext())
{
RequestResponse reqRes = new RequestResponse();
reqRes.Response = _requestResponseViewModel.requestResponse.Response.ToString();
reqRes.RequestID = _requestResponseViewModel.userRequest.ID;
reqRes.Author = _requestResponseViewModel.requestResponse.Author.ToString();
reqRes.CreateDate = DateTime.Now;
db.RequestResponses.InsertOnSubmit(reqRes);
db.SubmitChanges();
}
}
return RedirectToAction("Browse","Change");
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Create"));
}
}
Using debug mode the userRequest object is NULL in the view model parameter of the POST method but requestResponse is FINE and populated as should.
Searching on this, it seemed most had issues with the naming convention in the view model but I've made sure there are no discrepancies there.
If there is a more clear way to do this workflow please mention.
#Html.DisplayFor does not create an HTML input element, but a simple string literal (for most types, some exceptions are listed in the docs: https://msdn.microsoft.com/en-us/library/ee407420(v=vs.118).aspx#Anchor_1).
So when you press submit, your browser will not send the ID back to the server because it sends only form data (e.g. data from input, textare, select fields). Using your browsers developer tools (F12) you can examine what is actually send to the server.
You can add a hidden input field using #Html.HiddenFor(model => model.userRequest.ID) or use a custom display template for the ID to automatically add a hidden input field. You could further use UIHint attributes to automatically select a display template. Both approaches are thoroughly documented (e.g. http://www.codeguru.com/csharp/.net/net_asp/mvc/using-display-templates-and-editor-templates-in-asp.net-mvc.htm).
Another reason the object could be NULL in the POST is due to forgetting to add the setters { get; set; } in your view model:
public Orders orders; --> missing { get; set; }
public class OrderViewModel
{
public Orders orders { get; set; }
public List<VendorJobTitleView> Jobs { get; set; }
public List<ManagerView> Managers { get; set; }
}

Passing data from view to method Create in controller [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
I have the following problem with passing data to metod create in controller.
First I display view for method create (GET)
public ActionResult Create(string id)
{
RolesUser RolesUser = new RolesUser(id,repository.GetUserById(id).Roles, repository.GetRoles().ToList());
return View(RolesUser);
}
I use the following ViewModel
public class RolesUser
{
public RolesUser()
{
}
public RolesUser(string id,ICollection<IdentityUserRole> userRoles,List<IdentityRole> Roles)
{
userId = id;
this.Roles = GetNewRolesForUser(userRoles.ToList(), Roles.ToDictionary(x => x.Id, x => x.Name)).ConvertAll(
role =>
{
return new SelectListItem()
{
Text = role.ToString(),
Value = role.ToString(),
Selected = false
};
});
}
public IEnumerable<SelectListItem> Roles { get; set; }
public string userId { get; set; }
private List<string> GetNewRolesForUser(List<IdentityUserRole> UserRoles,Dictionary<string,string> Roles)
{
List<string> AvaiableRoles = new List<string>();
List<string> IdUserRoles = new List<string>();
UserRoles.ForEach(item => IdUserRoles.Add(item.RoleId));
foreach(KeyValuePair<string,string> Role in Roles)
{
if (!IdUserRoles.Contains(Role.Key))
{
AvaiableRoles.Add(Role.Value);
}
}
return AvaiableRoles;
}
}
It displays me essential information on my view in Create.cshtml, when I execute Submit it shows me following error
Object reference not set to an instance of an object.
The metod create (POST) looks like this
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles ="Admin")]
public ActionResult Create([Bind(Include ="userId")] RolesUser user)
{
if (ModelState.IsValid)
{
try
{
repository.AssignToRole(user.userId, "Klient");
repository.SaveChanges();
}
catch
{
ViewBag.exception = true;
return View();
}
}
ViewBag.exception = false;
return RedirectToAction("Index");
}
Code for Create.cshtml
#model Repository.ViewModels.RolesUser
#{
ViewBag.Title = "Create";
}
<h2>Dodaj poziom dostępu</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>#Model.userId</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.userId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.userId, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.Label("Poziomy dostępu", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.userId, Model.Roles)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Dodaj" class="btn btn-default" />
</div>
</div>
</div>
}
It looks like viewmodel is not passing to method, so I have null exception. But I don't understand why, if GET metod render properly view.
The NullReferenceException is likely hit inside the repository.AssignToRole() method because user.userId is null. You should add a hidden input for the userId somewhere inside the form so the userId value is posted to the parameter object on the Create action. If you add it after the #Html.AntiForgeryToken() the start of the form would look like
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.userId)
...

how to get approximate values in textarea when I click on an item in the dropdown list items in mvc

how to get approximate data automatically in textarea when I click on an item in the dropdown list items in mvc in the edit action method
model
public partial class Service
{
public int Id { get; set; }
public string ItemName { get; set; }
public string Description { get; set; }
}
view
<div class="form-group">
#Html.LabelFor(model => model.ItemName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10"><p>
#Html.DropDownListFor(model => model.ItemName, new SelectList(new[] { "Core Banking", "ATM", "RTGS/NEFT", "IFSC Code", "Money Transfer", "Locker Facility", "Mobile Banking (MAMBA)" }), "Select")
#Html.ValidationMessageFor(model => model.ItemName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.Description, 8, 100, null)
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Update" class="btn btn-default" />
</div>
</div>
</div>
}
controller
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Service service = await db.Services.FindAsync(id);
if (service == null)
{
return HttpNotFound();
}
return View(service);
}
// POST: Services/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,ItemName,Description")] Service service)
{
if (ModelState.IsValid)
{
db.Entry(service).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(service);
}
This is my controller, model, and view page
here in the view page when I select an Item from the dropdown list, the corresponding data for the selected item should appear in the textarea just below the drop down list. But this cannot be done for me, Is this possible to do like that? Should I need to change my edit action method? How can I solve my problem ??

Two Ajax forms, one view model. Validation only works for one

I've got two forms on my page. My viewmodel looks like this:
public class HomeViewModel
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
public ContactForm ContactForm { get; set; }
}
ContactForm:
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
First form action:
public ActionResult FreeConsultSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//do stuff
}
return PartialView("_SubmitResult", false);
}
Second Action:
public ActionResult ContactSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//dostuff
}
return PartialView("_SubmitContactResult", false);
}
First Ajax Form
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Second Ajax Form:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.ContactForm.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.ContactForm.Name)
#Html.TextBoxFor(m => m.ContactForm.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.ContactForm.EmailAddress)
#Html.TextAreaFor(m => m.ContactForm.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.ContactForm.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
I've got everything wired up fine, and client side validation works as it should. This may be overkill, but I also wanted to set up server side validation.
The issue is, when submitting the first form (just the email string), I check if the ModelState is valid, and it is. If you drill down into the ModelState, you see that only 1 property is being looked at.
If you submit the second form though (the ContactForm), the ModelState.IsValid returns false, and if you drill down, you see that it's looking at 4 properties (the 3 ContactForm properties, plus the string email). Since the email string is required, it fails.
I'm confused as to why it works for one, but not the other. I could just remove the server side validation, but I'd at least like to know why this is the case. I could also remove the error from the ModelState, but that doesn't seem elegant at all.
If you simply are trying to have two separate forms within one view, you're probably better off splitting the forms into separate "sub views" and child actions, and then using #Html.Action() to render them in place.
Here's an example:
Models
I'd remove the ContactForm model from HomeViewModel, and rename HomeViewModel to ConsultingForm (to match your naming convention for the contact model):
public class ConsultingForm
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
}
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
Controller
Add "child" actions, like those below:
public class HomeController : Controller
{
public ActionResult Home()
{
return View();
}
[ChildActionOnly, HttpGet]
public ActionResult ConsultingRequest()
{
var model = new ConsultingForm();
return View(model);
}
[ChildActionOnly, HttpGet]
public ActionResult ContactRequest()
{
var model = new ContactForm();
return View(model);
}
}
The ChildActionOnlyAttribute marks the action as a child action. From the MSDN:
A child action method renders inline HTML markup for part of a view
instead of rendering a whole view.
Views
Your first subview will be the same as you already have:
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Your second subview simply needs to remove the extra "step" in the property bindings:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.Name)
#Html.TextBoxFor(m => m.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.EmailAddress)
#Html.TextAreaFor(m => m.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
The "wrapper", or parent view, will look something like:
<div class="whatever">
#Html.Action("ConsultingRequest", "Home")
#Html.Action("ContactRequest", "Home")
</div>
If you inspect the rendered HTML, you'll see that each form is properly bound to only its model's properties, so when you post each form, only those properties are model-bound and validated.

ViewBag message is changed even when a form has errors

I have a simple contact form that is using a model for it's fields, everything seems to work but the ViewBag message gets changed regardless if there are validation errors or not, user validation prevents this but I also need the HttpPost action to set the message based on if the the form was filled correctly.
I tried using if(ModelState.IsValid) but it doesn't seem to work. I realize I can probably manually check each variable in the home to see if it's empty, but that won't really tell me if it's valid or the post was returned with errors, is there a build in method for this?
ContactFormModel.cs
namespace TestApplication.Models
{
public class ContactFormModel
{
[Required]
[Display(Name = "Name")]
public string name { get; set; }
[Required]
[Display(Name = "Phone")]
public string phone { get; set; }
[Required]
[Display(Name = "Message")]
public string message { get; set; }
}
}
HomeController.cs
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
ViewBag.Message = "Thank you. Your message has been sent.";
return View();
}
Contact.cshtml
#model TestApplication.Models.ContactFormModel
#{
ViewBag.Title = "Contact";
}
<h2>#ViewBag.Title.</h2>
<h3>#ViewBag.Message</h3>
<p>Use this area to provide additional information.</p>
#*#if (!IsPost)
{
#Html.EditorForModel(Model)
}*#
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.name, "", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.phone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.phone, "", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.phone, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.message, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.message, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.message, "", new { #class = "text-danger" })
</div>
</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>
}
The pattern for doing what you want is to check ModelState.IsValid. If it's valid continue processing, if not return the view with the existing model contents to give the user a chance to correct their error(s).
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
if (ModelState.IsValid)
{
ViewBag.Message = "Thank you. Your message has been sent.";
return View();
}
else
{
return View(contactForm);
}
}
Having said that, you should consider using a PRG (post-redirect-get) pattern. By returning the same view in the HttpPost version of the method you open yourself up to repeated posting of the data. If the user hits Refresh in their browser it will repost the data they just posted (after popping up a dialog that most non-technical users will never understand). You must have a HttpGet version that delivers the view in the first place, you should redirect to that on success. You'll have to switch to using TempData instead of ViewBag because the ViewBag won't survive the redirect.
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
if (ModelState.IsValid)
{
TempData.Message = "Thank you. Your message has been sent.";
// Assumes there is a Get version of the Contact action method
return RedirectToAction("Contact");
}
else
{
return View(contactForm);
}
}

Resources