This is probably a newbie question, as I'm quite new to ASP.NET MVC 5. When I tell Visual Studio to add a View based on my ViewModel class, it completely skips properties defined like public EnumName? PropertyName { get; set; } and does not create any #Html.EditorFor calls for it.
However, if I manually add the call #Html.EditorFor(model => model.PropertyName, new { htmlAttributes = new { #class = "form-control" } }) I get exactly what I expect -- a dropdown which is empty by default. Should scaffolding not do this by itself?
My understanding is that this is supposed to be supported in the current version of ASP.NET MVC. Am I wrong about that, or am I missing something? Help or advice is greatly appreciated.
Thanks in advance!
These are the ASP.NET products installed:
ASP.NET and Web Tools 12.4.51016.0
ASP.NET Web Frameworks and Tools 2012.2 4.1.21001.0
ASP.NET Web Frameworks and Tools 2013 5.2.21010.0
Edit for sample code:
Here is a small section of the view model. There are 170 different properties, almost all of them nullable-Enum type.
public partial class MedicalHistoryViewModel
{
public YesNo? Cancer { get; set; }
public MedicalHistoryDiagnosed? CancerDiagnosed { get; set; }
public YesNoUnsure? CancerIndustrialInOrigin { get; set; }
public YesNo? Diabetes { get; set; }
public MedicalHistoryDiagnosed? DiabetesDiagnosed { get; set; }
public YesNoUnsure? DiabetesIndustrialInOrigin { get; set; }
public YesNo? HeartDisease { get; set; }
//...
[Display(Name = #"Do you attribute the sleep disturbance to pain, anxiety and/or depression, or to other factors?")]
[DataType(DataType.MultilineText)]
public string SleepDisturbanceAttributedToComments { get; set; }
[Display(Name = #"Other (please specify)")]
[DataType(DataType.MultilineText)]
public string ParentsGrandparentsMedicalHistoryComments { get; set; }
}
Here is the complete output I get from Scaffolding. As you can see, it has completely ignored all enum properties.
#model QmeSurveyApp.ViewModels.MedicalHistoryViewModel
#{
ViewBag.Title = "EditMedicalHistory"; }
<h2>EditMedicalHistory</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>MedicalHistoryViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SleepDisturbanceAttributedToComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SleepDisturbanceAttributedToComments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SleepDisturbanceAttributedToComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SiblingsCousinsMedicalHistoryComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SiblingsCousinsMedicalHistoryComments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SiblingsCousinsMedicalHistoryComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ParentsGrandparentsMedicalHistoryComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ParentsGrandparentsMedicalHistoryComments, new { htmlAttributes
= new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ParentsGrandparentsMedicalHistoryComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div> }
<div>
#Html.ActionLink("Back to List", "Index") </div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval") }
But, if I add this block manually, I get exactly what I want: a drop-down which is empty by default, with my full pick list as the choices.
<div class="form-group">
#Html.LabelFor(model => model.Cancer, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Cancer, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Cancer, "", new { #class = "text-danger" })
</div>
</div>
You don't mention it, but I'm guessing you're not using Entity Framework.
I had a similar situation in an MVC project I was working in without EF. I had a POCO with a property that was an enum, and it was being completely skipped by the scaffolding engine. I even tried overriding the T4 templates with my own CodeTemplates and that's when I noticed the ModelMetadata.Properties collection didn't even contain my enum property.
I finally got it to work just by adding an empty Code First Entity Data model to the project. Doing that adds the Data context class textbox to the Add View scaffold item, and the resulting scaffolded view now includes my enum properties. This seems like a bug to me.
Related
This is my first time posting to Stack Overflow. So forgive me if I am not asking the question properly.
I have created a Razor page (in 4.72) wherein the Employer model has a List of Employee models embedded into the Employer model as well. I have Razor page with the Employer information on top and entries for multiple employees below. When submitting the page to create the employer, and employees, I am passing the Employer model into Action Result . But for some reason the List of Employees doesn't come with the Employer model.
The Employer Model and it's values comes over just fine., but not Employees.
My Razor page:
#model FraudReports.Models.FraudReportEmployer
#{
ViewBag.Title = "CreateEmployee";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.EmployerName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EmployerName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmployerName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FEIN, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FEIN, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FEIN, "", new { #class = "text-danger" })
</div>
</div>
<!-- More form Employer fields here -->
<!-- Start list of Employees here (Number of Employees could vary, using 5 entries for now. )-->
#{
for (var i = 1; i < 5; i++)
{
<hr />
<div class="form-group">
#Html.LabelFor(model => model.FraudReportEmployees[i].FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FraudReportEmployees[i].FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FraudReportEmployees[i].FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FraudReportEmployees[i].MI, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FraudReportEmployees[i].MI, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FraudReportEmployees[i].MI, "", new { #class = "text-danger" })
</div>
</div>
<!-- More form Employee fields here -->
}
<!-- End list of Employee fields here -->
}
<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>
My Action method:
public ActionResult CreateEmployee(FraudReportEmployer fraudReportEmployer)
{
List<FraudReportEmployee> FraudReportEmployees = fraudReportEmployer.FraudReportEmployees;
! There are values in Employer model but not the Employee List inside.
.
.
return View();
}
The Employer Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace FraudReports.Models
{
public class FraudReportEmployer
{
public string EmployerName { get; set; }
public string ContactName { get; set; }
public string ContactPhone { get; set; }
public string ContactEmail { get; set; }
....
public List<FraudReportEmployee> FraudReportEmployees { get; set; }
}
}
Employee MOdel:
namespace FraudReports.Models
{
public class FraudReportEmployee
{
public string EmployeeId { get; set; }
public string FirstName { get; set; }
public string MI { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
}
The indexing should be started from 0, like
for (var i = 0; i < 5; i++)
When the first element in the collection is missing the MVC can't bind the collection elements properly.
ANSWERED
See answers section below
I've been struggling for hours with this problem and I haven't found anything that relates, so apologies if this post is a duplicate.
I'll start by constructing a problem related to my own.
Let's say we create a ViewModel:
public class XmlViewModel(){
[Required]
public string Code { get; set; }
public string XML { get; set; }
}
and a constructor method which loads the View and as well as another that gets called when the submit is registered on the View.
public class Extractor{
public ActionResult Index()
{
XmlViewModel xmlVM = new XmlViewModel ()
{
XML = "Sample XML";
};
return View(xmlVM);
}
public ActionResult GetXml(XmlViewModel xmlVM){
xmlVM.XML = GetXMLByCode();
return View ("Index", xmlVM)
}
}
Then the view Index as below
#model Project.ViewModel.XmlViewModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("GetXml", "Extractor", Model))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Code, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Code, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Code, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(Model.XML))</pre>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Generate XML" class="btn btn-default"/>
</div>
</div>
</div>
}
So in this scenario I'm starting the page with:
When the user clicks Submit using a XML Code, I want the Code to be run against some collection of XML, then that returned XML replaces the "SampleXML"
Problem is when the form gets submitted again(Twice) (Now with the XML field holding a few hundred characters) it overloads the Query and returns this:
Because yep you guessed it, the XML fills up the Request with the XML from the previous Form result.
So my question is, is there any way to clear the ViewModel Property so it isn't passed in the Query, or some attribute to add that will tell the ViewModel to not pass the property through the Html.BeginForm()?
If possible I would like to stay away from passing the ViewModel properties individually as the actual problem's ViewModel is more complicated and it would be troublesome going down that route.
After my suggestions, if you are still getting header to large, I can help diagnose.
Add an xml file to your project. Add tons of xml to it. Right click to get properties. For build action, change to embedded resource.
Where I have WebApplication2.XMLFile1.xml, you should have your assembly name dot then your file name. You can right click on your project, and see properties to get assembly name.
Here is my code
namespace WebApplication2.Controllers
{
public class XmlViewModel
{
[Required]
public string Code { get; set; }
public string XML { get; set; }
}
public class HomeController : Controller
{
public static string GetXMLByCode()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "WebApplication2.XMLFile1.xml";
string result = String.Empty;
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
result = reader.ReadToEnd();
}
return result;
}
public ActionResult GetXml(XmlViewModel xmlVM)
{
xmlVM.XML = GetXMLByCode();
return View("Index9", xmlVM);
}
public ActionResult Index9()
{
XmlViewModel xmlVM = new XmlViewModel { XML = "Sample XML" };
return View(xmlVM);
}
here is my view
#model WebApplication2.Controllers.XmlViewModel
#{
ViewBag.Title = "Index9";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("GetXml", "Home", Model))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Code, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Code, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Code, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(Model.XML))</pre>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Generate XML" class="btn btn-default" />
</div>
</div>
</div>
}
ANSWERED
I eventually figured it out by doing the following in the view:
#{
ViewBag.Title = "Index";
ViewBag.XML= XmlViewModel.XML;
XmlViewModel.XML = "";
}
---
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(ViewBag.XML))</pre>
</div>
</div>
Now when the view is loaded, the Viewbag will hold the XML being returned and I can safely empty the model data before returning it to the Html.BeginForm()
Keeping for anyone in the same situation
I have a trivial web app with the following model:
public class SillyModel
{
public SillyModel()
{ Id = Guid.NewGuid(); Children = new List<SillyModel>(); }
[Key]
public virtual Guid Id { get; set; }
public virtual string Value { get; set; }
public virtual List<SillyModel> Children { get; set; }
}
}
I have an Edit View of:
#model WebApplication1.Models.SillyModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.Partial("EditPartial", Model)
<div class="form-horizontal">
<h4>SillyModel</h4>
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
With the Partial:
#model WebApplication1.Models.SillyModel
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Value, "", new { #class = "text-danger" })
</div>
#foreach (var item in Model.Children)
{
#Html.Partial("EditPartial", item)
}
</div>
The rendering is Fine! But for the life of me (well at least 3 days of struggle) I can not get it so that the returned model is properly bound! [No children are returned]
I am at my wits end.
You have to re-structure the way you are coding a little
In the Views/YourController, add a folder called EditorTemplates if not yet exists and add a view named SillyModel and copy the code from EditPartial to this new view
You change the foreach to for loop to decorate the controls with index
The code
~/Views/YourController/EditorTemplates/SillyModel.cshtml
#model WebApplication1.Models.SillyModel
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Value, "", new { #class = "text-danger" })
</div>
#for (var index=0; index<Model.Children.Count;index++)
{
#Html.EditorFor(model=>Model.Children[index])
}
</div>
~/Views/YourController/Edit
instead of #Html.Partial("EditPartial", Model), use #Html.EditorFor(m=>m)
Explanation
By adding the EditorTemplates/SillyModel , now you can call #Html.EditorFor(model=>Model.Children[index]) and your custom editor will be rendered
You need to use indexed controls in order Binding to the Model succeeded
Hope this will help you
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; }
}
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);
}
}