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>
Related
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 });
}
I have a text area where users have to write a description. I was wondering how to set a text box so that it is on multiple lines, allowing the user to see what they are writing. What I am looking for is similar to the environment that I am writing this question in.
<div class="form-group">
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
In your model you need to add MultilineText as below:
[DataType(DataType.MultilineText)]
public string Title { get; set; }
Or you code have just changed the EditorFor to TextAreaFor like Below
#Html.TextAreaFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
I have a form with multiple values for a custom question. The idea being a user can make their own custom question with custom answers. One of the properties of the Model object is a list of objects:
public class CustomQuestion
{
public int Id { get; set; }
[Required]
public string QuestionName{ get; set; }
public virtual List<CustomQuestionAnswer> Answers { get; set; }
}
public class CustomQuestionAnswer
{
public int Id { get; set; }
[Required]
public string Answer { get; set; }
}
Now I have this in razor which works to create the answers (I have omitted the rest of the question):
foreach (var answer in Model.Answers)
{
if (answer.IsActive)
{
<div class="form-group answer">
<div class="col-md-10">
#Html.Hidden("answers[" + count + "].Id", answer.Id)
#Html.TextBox("answers[" + count + "].Answer", answer.Answer, new { #class = "form-control" })
validation code will go here
</div>
<div class="col-md-2">
#Html.CheckBox("answers[" + count + "].IsActive", answer.IsActive)
</div>
</div>
count++;
}
}
Now everything is working fine and saving to the database, except the data validation is not working. I have the data validation messages working for the CustomQuestion's QuestionName (so I know it is not an issue of jquery loading properly), but not for the answers. I have tried putting these lines in the razor form:
#Html.ValidationMessageFor(m => answer.Answer, "", new { #class = "text-danger" })
#Html.ValidationMessage("Answers[" + count + "].Answer", "", new { #class = "text-danger" })
But neither works. Also, when I click on the save button, I get this error message in the browser console:
Failed to load resource: the server responded with a status of 400 ({"Answers[0].Answer":["The Answer field is required."]})
Any ideas as to what I need for the validation code?
If your model is (or contains) an indexed collection, it is better to use a for-loop along with fully specified variables and with ...For helpers, like this:
for (var i = 0; i < Model.Answers.Count; i++)
{
if (Model.Answers[i].IsActive)
{
<div class="form-group answer">
<div class="col-md-10">
#Html.HiddenFor(m => Model.Answers[i].Id)
#Html.TextBoxFor(m => Model.Answers[i].Answer, new { #class = "form-control" })
#Html.ValidationMessageFor(m => Model.Answers[i].Answer, null, new { #class = "text-danger" })
</div>
<div class="col-md-2">
#Html.CheckBoxFor(m => Model.Answers[i].IsActive)
</div>
</div>
}
}
It may seem awkward or even unnecessary to keep referencing the full Model.Answers[i] everywhere, but MVC uses reflection on these expressions to come up with the full names to output, which are needed for Model Binding to succeed, and also for pairing it with ModelState which uses the same object structure.
The use of a temporary or helper variable such as your answer will cause MVC to not be able to do this anymore, and then the burden becomes your own.
You should use strongly typed helpers if you have ViewModel. To make right binging you should use for loop instead of foreach.
Like this:
for (int i = 0; i < Model.Answers.Count(); i++)
{
if (Model.Answers[i].IsActive)
{
<div class="form-group answer">
<div class="col-md-10">
#Html.HiddenFor(x => Model.Answers[i].Id)
#Html.TextBoxFor(x => Model.Answers[i].Answer, new { #class = "form-control" })
#Html.ValidationMessageFor(m => x => Model.Answers[i].Answer, null, new { #class = "text-danger" })
</div>
<div class="col-md-2">
#Html.CheckBox(x => Model.Answers[i].IsActive)
</div>
</div>
}
}
you can customize you message in the model
like:
[Required]
[DataType(DataType.EmailAddress, ErrorMessage = "You need a valid email adress")]
[MaxLength(40, ErrorMessage = "the length of an Email address must be at least 40 chars")]
[Display(Name = "Email")]
public string CurrentMail { get; set; }
and then you make a simple
#Html.TextBoxFor(m => m.CurrentMail)
instead Of #Html.TextBox(m => m.CurrentMail)
For Validation you can use this code :
#Html.ValidationMessageFor(m => answer.Answer,null, new { #class = "text-danger" })
Please check it and let me know..
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.
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" })