MODEL
public class SearchTerm
{
[Required(ErrorMessage="please enter")]
public string SearchTrm { get; set; }
}
View
#using (#Html.BeginForm())
{
#Html.ValidationSummary();
#Html.AntiForgeryToken();
....
#Html.TextBoxFor(m=>m.SearchTrm)</span>
<input type="submit" value="Search"/>
#Html.ValidationMessageFor(m=>m.SearchTrm)
#using (Html.BeginForm("Search","Home"))
{
#Html.DropDownList("SelectedFieldId", new SelectList(Model.Fields, "FieldID", "NiceName", Model.SelectedFieldId));
}
}
controller
[HttpPost]
public ActionResult Search(SearchTerm Model)
{
// some code here....
}
When i click a empty search I want the validation message to take place but instead page is getting postback and i am having NullReferenceException
Mention the script name, #section scripts { ...} and check whether jqueryval has 2 files - ~/scripts/jquery.validate.min.js","~/scripts/jquery.validate.unobtrusive.min.js","~/scripts
Related
SUMMARY
Question: Why doesn't the custom validation error message show when using a ViewModel.
Answer: The custom validation should be applied to the ViewModel not the Class. See the end of #JaySilk84's answer for example code.
MVC3, project using
jquery-1.7.2.min.js
modernizr-2.5.3.js
jquery-ui-1.8.22.custom.min.js (generated by jQuery.com for the Accordion plugin)
jquery.validate.min.js and
jquery.validate.unobtrusive.min.js
I have validation working in my project for both dataannotations in the View and for ModelState.AddModelError in the Controller so I know I have all the validation code configured properly.
But with custom validation an error is generated in the code but the error message doesn't display.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{ if (DOB > DateTime.Now.AddYears(-18))
{ yield return new ValidationResult("Must be 18 or over."); } }
Drilling down in debug in the POST action the custom validation causes Model state to fail and the error message is placed in the proper value field but when the model is sent back to the view the error message doesn't display. In the controller I also have ModelState.AddModelError code and its message does display. How is that handled differently as to one would work and not the other? If not that what else would prevent the error message from displaying?
Update 1 :
I'm using a ViewModel to create the model in the view. I stripped out the ViewModel and the error message started displaying, as soon I added the ViewModel back in the message again stopped displaying. Has anyone successfully used a custom validation with a ViewModel? Was there anything you had to do extra to get it to work?
Update 2 :
I created a new MVC3 project with these two simple classes (Agency and Person).
public class Agency : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DOB { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18."); }
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
Here's the Controller Code
public ActionResult Create()
{
return View();
}
//
// POST: /Agency/Create
[HttpPost]
public ActionResult Create(Agency agency)
{
if (ModelState.IsValid)
{
db.Agencies.Add(agency);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(agency);
}
//[HttpPost]
//public ActionResult Create(AgencyVM agencyVM)
//{
// if (ModelState.IsValid)
// {
// var agency = agencyVM.Agency;
// db.Agencies.Add(agency);
// db.SaveChanges();
// return RedirectToAction("Index");
// }
// return View(agencyVM);
//}
The View
#model CustValTest.Models.Agency
#*#model CustValTest.Models.AgencyVM*#
#* When using VM (model => model.Name) becomes (model => model.Agency.Name) etc. *#
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Agency</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DOB)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The ViewModel
public class AgencyVM
{
public Agency Agency { get; set; }
public Person Person { get; set; }
}
When just Agency is presented in the View the validation error displays (DOB under 18). When the ViewModel is presented the error doesn't display. The custom validation always catches the error though and causes ModelState.IsValid to fail and the view to be re-presented. Can anyone replicate this? Any ideas on why and how to fix?
Update 3 :
As a temporary work around I have changed the Validation into a field level one (vs. a model level one) by adding a parameter to the ValidationResult:
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18.", new [] { "DOB" }); }
The problem with this is now the error message is showing up next to the field rather than at the top of the form (which is not good in say an accordion view since the user will be returned to the form with no visible error message). To fix this secondary problem I added this code to the Controller POST action.
ModelState.AddModelError(string.Empty, errMsgInvld);
return View(agencyVM);
}
string errMsgInvld = "There was an entry error, please review the entire form. Invalid entries will be noted in red.";
The question is still unanswered, why doesn't the model level error message show with a ViewModel (see my response to JaySilk84 for more on this)?
The issue is now that your models are nested, the error message is being placed into ModelState under Agency without the .DOB because you didn't specify it in the ValidationResult. The ValidationMessageFor() helper is looking for a key named Agency.DOB (see relevant code below from ValidationMessageFor() helper):
string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
FormContext clientValidation = htmlHelper.ViewContext.GetFormContextForClientValidation();
if (!htmlHelper.ViewData.ModelState.ContainsKey(fullHtmlFieldName) && clientValidation == null)
return (MvcHtmlString) null;
GetFullHtmlFieldName() is returning Agency.DOB, not Agency
I think if you add the DOB to the ValidationResult it will work:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18.", new List<string>() { "DOB" }); }
}
That second parameter to ValidationResult will tell it what key to use in ModelState (By default it will append the parent object which is Agency) so the ModelState will have a key named Agency.DOB which is what your ValidationMessageFor() is looking for.
Edit:
If you don't want field level validation then you don't need the Html.ValidationMessageFor(). You just need the ValidationSummary().
The view is treating AgencyVM as the model. If you want it to validate properly then put the validation at the AgencyVM level and have it validate the child objects. Alternatively you could put validation on the child objects but the parent object (AgencyVM) has to aggregate it to the view. Another thing you can do is keep it as it is and change ValidationSummary(true) to ValidationSummary(false). This will print everything in ModelState to the summary. I think removing the validation from Agency and putting it on AgencyVM might be the best approach:
public class AgencyVM : IValidatableObject
{
public Agency Agency { get; set; }
public Person Person { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Agency.DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18."); }
if (string.IsNullOrEmpty(Agency.Name)) { yield return new ValidationResult("Need a name"); }
}
}
In an MVC3 project, i use an Html.BeginForm to post some (model-)values. Along with those i want to send an extra parameter that is not part of the form (the model) but in the ViewBag. Now, when i use a Button (code in answer here: MVC3 razor Error in creating HtmlButtonExtension), all the form values are posted but the extra parameter remains null. When i use an ActionLink, the parameter is posted but the form values are not :) Any know how i can combine the two? Thanks!
#Html.Button("Generate!", new { id = ViewBag.ProjectID })
#Html.ActionLink("Generate!", "Post", new { id = #ViewBag.ProjectID })
My advice would be to declare a new Object in your App.Domain.Model something like this
namespace App.Domain.Model
{
public class CustomEntity
{
public Project projectEntity { get; set; }
public int variableUsed { get; set; }
}
}
In your view you can acces them easily by using CustomEntity.projectEntity and CustomEntity.variableUsed.
Hope it helps
You can do something like below.
View code
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post, new { #id = "frmId", #name = "frmId" }))
{
#*You have to define input as a type button not as a sumit. you also need to define hidden variable for the extra value.*#
<input type="hidden" name="hndExtraParameter" id="hndExtraParameter" />
<input value="Submit" type="button" id="btnSubmit" onclick="UpdateHiddenValue()" />
}
<script type="text/javascript">
function ValidateUser() {
$("#hndExtraParameter").val('Assignvaluehere');
$("#frmId").submit();
}
</script>
Controller Code
[HttpPost]
public ActionResult ActionName(Model model, string hndExtraParameter)
{
//Do your operation here.
}
I'm having problems getting an uploaded file (HTTPPostedFile) and an object posted to an action. I have a class called widget:
public class Widget
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FilePath { get; set; }
}
and in the Widget controller I have an 'Add' method
public ActionResult Add()
{
return View();
}
and an overloaded method to accept what the user posts back
[HttpPost]
public ActionResult Add(Widget widget, HttpPostedFile file)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
and in the View I have the following:
#model Project.Models.Widget
#{
using(Html.BeginForm())
{
Html.LabelFor(model => model.FirstName)<br />
Html.TextBoxFor(model => model.FirstName)<br />
Html.LabelFor(model => model.LastName)<br />
Html.TextBoxFor(model => model.LastName)<br />
<input type="file" id="file" /><br />
<input type="submit" value="Save" />
}
}
What I want to do is have the user fill out the form and select a file to upload. Once the file is uploaded, I want to save the file off using a unique name and then store the path of the file as widget.FilePath.
Each time I try, the widget object is populated, but the uploadedFile is null.
Any Help would be greatly appreciated.
There are a couple of issues with your code.
Make sure you have set the proper enctype="multipart/form-data" to your form, otherwise you won't be able to upload any files.
Make sure that your file input has a name attribute and that the value of this attribute matches the name of your action argument. Assigning an id has no effect for the server side binding.
For example:
#model Project.Models.Widget
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(model => model.FirstName)<br />
#Html.TextBoxFor(model => model.FirstName)<br />
#Html.LabelFor(model => model.LastName)<br />
#Html.TextBoxFor(model => model.LastName)<br />
<input type="file" id="file" name="file" /><br />
<input type="submit" value="Save" />
}
Also make sure that your controller action works with a HttpPostedFileBase instead of HttpPostedFile:
[HttpPost]
public ActionResult Add(Widget widget, HttpPostedFileBase file)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
Also you could merge the 2 parameters into a single view model:
public class Widget
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FilePath { get; set; }
public HttpPostedFileBase File { get; set; }
}
and then:
[HttpPost]
public ActionResult Add(Widget widget)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
Finally read the following blog post: http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx
I'm new to ASP.NET MVC. I'm currently using ASP.NET MVC 3. I'm trying to create a basic form with an image button that posts data back to the controller. My form looks like the following:
MyView.cshtml
#using (Html.BeginForm())
{
#Html.TextBox("queryTextBox", string.Empty, new { style = "width:150px;" })
<input type="image" alt="Search" src="/img/search.png" value="Search" name="ExecuteQuery" />
}
MyController.cs
public class MyController: Controller
{
public ActionResult Index()
{
ViewData["CurrentDate"] = DateTime.UtcNow;
return View();
}
[HttpPost]
public ActionResult ExecuteQuery()
{
if (ModelState.IsValid)
{
return View();
}
}
}
I have verified that I'm accessing my controller code. The way I've been able to do this is by successfully printing out the "CurrentDate" associated with the ViewData. However, when I click my image button, I was assuming that my break point in the ExecuteQuery method would fire. It does not. What am I doing wrong? How do I do a simple POST in MVC?
Your form is pointing to the wrong action.
Calling Html.BeginForm() creates a <form> that POSTs to the current action (Index).
You need to pass the action name to BeginForm.
Try this one
#using (Html.BeginForm("ExecuteQuery", "MyController", FormMethod.Post))
{
#Html.TextBox("queryTextBox", string.Empty, new { style = "width:150px;" })
<input type="submit" value="Search" />
}
In your controller
public class MyController: Controller
{
public ActionResult Index()
{
ViewData["CurrentDate"] = DateTime.UtcNow;
return View();
}
[HttpPost]
public ActionResult ExecuteQuery(string queryTextBox)
{
if (ModelState.IsValid)
{
// DO SOMETHING WITH queryTextBox
return View();
}
}
}
If the image submit button is very important for you, you can still change the button background image using CSS.
We have a field on our model which has a [Remote] attribute. When we store that field on a Hidden form element and then try to submit that form we have to click the submit button twice. Also interesting is that the 2nd time we click it no remote validation is occurring (so says Fiddler).
Thoughts?
Unable to repro. If the hidden field is decorated with the Remote attribute you won't be able to submit the form no matter how many times you click on the submit button if the remote function sends false.
For example:
Model:
public class MyViewModel
{
[HiddenInput(DisplayValue = false)]
[Remote("Check", "Home")]
public string Id { get; set; }
[Required]
public string Name { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
Id = "1"
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
public ActionResult Check(string Id)
{
return Json(Id == "2", JsonRequestBehavior.AllowGet);
}
}
View:
#model AppName.Models.MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
Because the remote function will always return false this form cannot be submitted. If the remote function returns true a single click would be enough to submit it assuming of course that the other validation passed.