MVC Client Side Validation Summary not showing when Model is Invalid - c#

I'm using the MVC Validation nuget package called MVC Foolproof Validation.
I'm using it on my model to set a required to true if another model property was empty. The validation part works, as the ModelState is correctly set to invalid when the Id field and Location field are left empty. Inspecting the errors on the ModelState array I can see its working.
My problem is that the client side validation summary does not display. Here is how I've set things up. Can anyone spot my problem?
[DisplayName("Image Id")]
public string Id{ get; set; }
[DisplayName("Location Id")]
[RequiredIfEmpty("Id", ErrorMessage = "You must..etc"]
public string LocationId{ get; set; }
In my view I'm setting up the validation summary and inputs as follows
<div class="form-horizontal">
<hr/>
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
<div class="form-group">
#Html.LabelFor(model => model.SearchCriteria.Id, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.SearchCriteria.Id, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.SearchCriteria.Id, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SearchCriteria.LocationId, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.SearchCriteria.LocationId, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.SearchCriteria.LocationId,"", new {#class = "text-danger"})
</div>
</div>
In my controller action I'm checking the Model state. Do I need to call ModelState.AddModelError(..). I've tried that but perhaps there is a way I need to call it.
[HttpPost]
public ActionResult Search(SearchCriteria searchCriteria)
{
var searchViewModel = new SearchViewModel
{
SearchCriteria = searchCriteria
};
if (ModelState.IsValid)
{
...
}
//ModelState.AddModelError("LocationId", "test");
return View(searchViewModel);
}

Change the boolean parameter (excludePropertyErrors) in the ValidationSummary helper line to false:
#Html.ValidationSummary(false, "", new {#class = "text-danger"})
See https://msdn.microsoft.com/de-de/library/ee839464(v=vs.118).aspx

In my case I solved it changing from 'ModelOnly' to 'All':
From:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
To:
<div asp-validation-summary="All" class="text-danger"></div>

My problem was that my call to my action was being done through ajax. I really should have specified, people who helped here would have diagnosed the problem immediately. When the modelstate was invalid i was returning a new View(searchViewModel) but I had to update that to return the errors in json.
[HttpPost]
public ActionResult Search(SearchCriteria searchCriteria)
{
if (ModelState.IsValid)
{
var searchResults = _searchProvider.GetData(searchCriteria);
return Json(new
{
searchResults
}, JsonRequestBehavior.AllowGet);
}
string errorMessages = string.Join(" <br /> ", this.ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage));
return this.Json(new { HasError = true, ErrorMessages = errorMessages });
}

Related

ASP.NET MVC view return empty object

I have the following code, but it return me an empty FormationDTO object, did I do anything wrong?
I don't understand why it can't properly bind FormationFormViewModel's FormationDTO to the action parameter FormationDTO, it worked in others controllers.
FormationsController
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(FormationDTO formation)
{
if (!ModelState.IsValid){
return View("FormationForm", new FormationFormViewModel { FormationDTO = formation, Categories = GetCategories() });
}
else{
// DO THE STUFF
}
}
FormationForm.cshtml
#model BSS_IT_Education.Models.FormationFormViewModel
#{
ViewBag.Title = "Formation";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("Save", "Formations"))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.FormationDTO.Id)
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.FormationDTO.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-4">
#Html.EditorFor(model => model.FormationDTO.Name, new { htmlAttributes = new { #class = "form-control", #placeholder = "Entrez le nom de la formation..." } })
#Html.ValidationMessageFor(model => model.FormationDTO.Name, "", new { #class = "text-danger" })
</div>
</div>
// BUNCH OF OTHERS FORM-GROUPS
<div class="form-group">
<div class="col-md-offset-2 col-md-8">
<button type="submit" class="btn btn-success">#((Model.FormationDTO.Id == 0) ? "Sauvegarder " : "Modifier")</button>
</div>
</div>
</div>
}
If I am understanding the code correctly. It looks like you should be passing FormationFormViewModel to the controller action. Not FormationDTO.
Take a look at the generated HTML on the page. I'm guessing the name attributes on your input elements will look something like formationDTO.name, because your ViewModel is a FormationFormViewModel. But the ModelBinder on the backend is going to look for just a property name, because you are trying to build a FormationDTO.
You may need to manually create those input elements, or use a child action to get the correct ViewModel to a view that lets you use the razor #Html helpers to build the correct elements.
Or, the easier option is to make your controller action accept a FormationFormViewModel, then the ModelBinder should correctly build out the properties of the FormationDTO you want.

Client-side validation of US currency

I have an ASP.Net MVC 5 web application and I need to accept user input of US currency. Some valid inputs might be:
100
$100.21
$ 1,234
$1,234.56
Invalid inputs might be:
10,12
1o0.21
My (simplified) model looks like:
public class Claim {
[DisplayName("$ Amount)]
[DataType(DataType.Currency)]
[Required]
[Range(0.0, 200000.0)]
public decimal? DollarAmount { get; set; }
}
My cshtml markup looks like:
#Html.LabelFor(model => model.DollarAmount, htmlAttributes: new { #class = "control-label col-md-3" })
<div class="col-md-9">
#Html.EditorFor(model => model.DollarAmount, new { htmlAttributes = new { #class = "form-control margin-bottom" } })
#Html.ValidationMessageFor(model => model.DollarAmount, "", new { #class = "text-danger" })
</div>
I used this advice this advice to build a binder that converts user input to a decimal, but client-side validation won't let the user enter a dollar-sign or commas. What do I need to do to allow the user to enter valid currency values, but warns her if she enters an invalid value? I'd prefer to do as much validation on the client-side as possible.
You Might want to look at https://github.com/globalizejs/globalize#currency-module. Helps allot with this kind of stuff. As for your Question to be able to use the Dollar Symbol you would not be able to store this Value as a decimal format in the database, only as a string.
There are a few things you can do, Use bootstrap to place a Dollar symbol in front of your TextBox using input-group-addon. Not sure if it will work properly as i see you have set Margin-bottom on your Textbox, telling me you might not be using bootstrap form tags above.
You may want to look into AutoNumeric jQuery plugin, It's well-maintained and they've basically "thought of everything" I could want for currency.
// View
#Html.LabelFor(model => model.DollarAmount, htmlAttributes: new { #class = "control-label col-md-3" })
<div class="col-md-9 input-group">
<span class="input-group-addon">$</span>
#Html.EditorFor(model => model.DollarAmount, new { htmlAttributes = new { #class = "form-control margin-bottom" } })
#Html.ValidationMessageFor(model => model.DollarAmount, "", new { #class = "text-danger" })
</div>
// Class
public class Claim {
[DisplayName("$ Amount")]
[DataType(DataType.Currency)]
// {0:C} Will show as currency {0:N} to show Numbers
[DisplayFormat(DataFormatString = "{0:C}", ApplyFormatInEditMode = true))]
[Required]
[Range(0.0, 200000.0)]
public decimal? DollarAmount { get; set; }
}
Another option is to have a hidden field with javascript that will duplicate the field from a string to decimal and that can be the one you submit like below.
// MODEL
public class Claim {
[DisplayName("$ Amount")]
[DataType(DataType.Currency)]
[Required]
[Range(0.0, 200000.0)]
public decimal? DollarAmount { get; set; }
}
// View
#Html.HiddenFor(model => model.DollarAmount, new { #id = "DollarAmount" })
#Html.LabelFor(model => model.DollarAmount, htmlAttributes: new { #class = "control-label col-md-3" })
<div class="col-md-9 input-group">
<span class="input-group-addon">$</span>
<input id="DollarSave" type="text" name="DollarSave" pattern="^\$?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$" title="You must enter in proper currency">
#Html.ValidationMessageFor(model => model.DollarAmount, "", new { #class = "text-danger" })
</div>
<script type="text/javascript">
jQuery(document).ready(function($){
$('#DollarSave').change(function(){
var sourceField = $("#DollarSave").val(); //source field key
$("#DollarAmount").val(sourceField); //destination field key
$("#DollarAmount").change(); //destination field key
});
});
</script>
Pool pro's answer was a great help in solving my problem but I couldn't get his input tag pattern to display a message. It worked in JSFiddles, but not in my Asp.Net view template. So, I did the pattern validation and message update in javascript. I also used a different regex. For completeness, I'm posting my solution here:
#Html.HiddenFor(model => model.DollarAmount, new { #id = "DollarAmount" })
#Html.LabelFor(model => model.DollarAmount, htmlAttributes: new { #class = "control-label col-md-3" })
<div class="col-md-9">
<input id="DollarSave" type="text" name="DollarSave" class="form-control text-box single-line">
<p id="BadDollarSave" class="text-danger"></p>
</div>
<script type="text/javascript">
jQuery(document).ready(function($){
$('#DollarSave').on('blur', function () {
validateDollarSave();
});
function validateMoney(inputId) {
var errorMsg = '';
var currency = $('#DollarSave').val();
var good = currency.match(/^(\$|\$ )?[0-9]{1,3}(?:(,[0-9]{3})*|([0-9]{3})*)(?:(\.|\.[0-9]{2}))?$/);
if (!good) {
errorMsg = "$ Amount must be US currency";
} else {
var num = currency.replace(/[, $]/g, "");
$('#DollarAmount').val(num);
}
document.getElementById('BadDollarSave').innerHTML = errorMsg;
};
});
</script>

Adding Html required to TextAreaFor not working c#

I'm trying to add a required to my TextAreaFor, but it won't give the error message when i post it. I'm trying to do it on the followinng line:
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
And this is my full code:
#using (Html.BeginForm("_Create", "Comments", FormMethod.Post))
{
#Html.HiddenFor(m => m.ThreadId)
<div class="form-group">
<div class="col-md-10">
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
#Html.ValidationMessageFor(model => model.Content, "", new { #class = "text-danger"})
</div>
</div>
<div class="form-group">
<div>
<input type="submit" value="Post" class="btn btn-default" />
</div>
</div>
}
If anyone wanst to do it with html attribute,
#Html.TextAreaFor(model => model.Content, new { required = "required", htmlAttributes = new { #class = "form-control"} })
You don't need required as a html attribute. It should be a data annotation on the model.
[Required]
public string Content { get; set; }
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
Should be:
#Html.TextAreaFor(model => model.Content, new { #class = "form-control", required = "required" })
Or if you want to explicitly name the parameter your anonymous object is for:
#Html.TextAreaFor(model => model.Content, htmlAttributes: new { #class = "form-control", required = "" } })
But, if you do not use data-annotation, it could be even easier this way:
<textarea id="Content" name="Content" required class="form-control">#Model.Content</textarea>
(id attribute may be optional, depending on your usages.)
Side note: I tend to minimize uses of html helpers methods. For me, MVC is also about letting you control very precisely the browser client code, which is imo better done by writing it yourself. WebForm is, on this subject, about hiding most of browser client code handling.
Using extensively html helpers, built-in validation logic, and so on, may cause you to lose the precise control of how your page should work.

C# Databinding does not contain property with name

I am creating a ListBoxFor with a MultiSelectList, but I get the following error: DataBinding: *.Models.Facilities does not contain a property with the name FacilitiesList.
Here's my view:
#model *.Models.Reservation
<div class="form-group">
#Html.LabelFor(model => model.FacilitiesList, "Facilities", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.FacilitiesList, ViewBag.FacilitiesList as MultiSelectList, htmlAttributes: new { #class = "form-control" })
</div>
</div>
The viewbag FacilitiesList is populated as follows:
public ActionResult Create()
{
ViewBag.FacilitiesList = new SelectList(_facilityrepository.GetAll(), "FacilitiesList", "FacilityType");
return View();
}
Solved it. Simple solution:
I changed this:
ViewBag.FacilitiesList = new SelectList(_facilityrepository.GetAll(), "FacilitiesList", "FacilityType");
to this:
ViewBag.FacilitiesList = new SelectList(_facilityrepository.GetAll(), "FacilityId", "FacilityType");

Viewmodel values not being returned to controller

I found some similar posts to mine, but I couldn't find an answer that suits my needs for this.
Problem is as follows:
I have a viewmodel like this:
public class PrefViewModel
{
public SelectList countries { get; set; }
public SelectList Provincies { get; set; }
public ApplicationUser user { get; set; }
public Preference MyPref{ get; set; }
public int mycountry { get; set; }
public int myprovince { get; set; }
}
my cshtml looks like this:
#using (Html.BeginForm("Index","Preferences", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.user.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
<span class="textvak">
#Html.TextBoxFor(model => model.user.UserName, new { disabled = "disabled", #readonly = "readonly" })
</span>
#Html.ValidationMessageFor(model => model.user.UserName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
<span class="textvak">
#Html.TextBoxFor(model => model.user.Email, new { disabled = "disabled", #readonly = "readonly" })
</span>
#Html.ValidationMessageFor(model => model.user.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Unhashed, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.user.Unhashed, new { htmlAttributes = new { #class = "form-control", type = "password" } })
#Html.ValidationMessageFor(model => model.user.Unhashed, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Provincie.Land, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
#Html.DropDownListFor(model => model.mycountry, Model.countries, new { Name = "ddlLand", id = "ddlLanden", #class = "textvak" })
#Html.ValidationMessageFor(model => model.user.Provincie.Land, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Provincie, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
#Html.DropDownListFor(model => model.myprovince, Model.Provincies, new { #class = "textvak" })
#Html.ValidationMessageFor(model => model.user.Provincie, "", new { #class = "text-danger" })
</div>
</div>
<br />
<table>
<tr>
<td>
<input type="submit" value=#Resources.Wijzig class="btn btn-default" />
</td>
</tr>
</table>
}
and in my controller I try to get the posted PrefViewModel back as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(PrefViewModel TestMymodel)
{
if (ModelState.IsValid)
{
int myCountry = TestMymodel.mycountry;
int myprovince = TestMymodel.myprovince;
}
return View();
}
My problem is that the PrefViewModel TestMymodel never is filled with the values I thought i'm posting back. Even more strange to me is the fact that I do get the Unhashed password back, but all other values are 0 or null.
I can put values inside the PrefViewModel to load the page and that works, but on Posting it's almost entirely empty.
Any ideas?
edit: Would it make any difference that I did change the default model to one that I made up myself? Cause when I Call the Create action for example, I do get the values back in my post (from create offcourse). I'm getting a bit desperate
edit2: this is what was posted:
__RequestVerificationToken:-JYcw0CH2zZ7WrGUiYJM6-R6VxfL41ykTD5EHUjgtyyFcN01AaUU61BYuaRNr4oPdEvDq09aYsOFdb8fObJTXMnTKulADVkGY8CrBG3U71QXw0g7Th86WKl1up4059Zy7mW0SlrWGJpehed586v_5g2
user.Unhashed:Jonas1234-
user.Unhashed:Jonas1234-
ddlLand:1
ddlProvincie:3
(can't add picture with my reputation, so here a link to the full post: http://postimg.org/image/id95wjcxp/ )
Ok, when I change the name of the dropdownlists to the PrefViewModel property name those values get returned correct.
It appears that you have overriden the names of the drop down lists to some values which are different than the property names in your view model. That's why the values are not successfully bound back. Make sure that your input fields respect the same names as the properties on your view model if you want the default model binder to be able to bind them back to the view model.
Also your username textbox has the disabled flag:
#Html.TextBoxFor(model => model.user.UserName, new { disabled = "disabled", #readonly = "readonly" })
so it will not be submitted back to the server. You might need to add an additional hidden field if you want those values to travel back. Or simply use readonly without disabled attribute. Both attributes prevent the user from modifying the value in the corresponding input field but in addition to that the disabled attribute strips it from the POST payload when the form is submitted.
So you may use:
#Html.TextBoxFor(model => model.user.UserName, new { #readonly = "readonly" })

Categories