I want to allow user insert values of price like this:
12
12,34
12,3
(number with comma or wihout it and maximum 2 digits after that)
ViewModel class:
[DataType(DataType.Currency)]
[RegularExpression(#"^\d+.\d{0,2}$")]
public decimal Price { get; set; }
View:
#Html.EditorFor(model => model.Price, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.Price, "", new { #class = "validation-error" })
Above code gives me always error:
"Field Price must be a number"
I can write sth like 12.56, but dot is not what I want to do, because in my country comma is appropriate for currencies values.
What I tried so far is:
1) I added DecimalModelBinder class like this :
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState {Value = valueResult};
object actualValue = null;
try
{
actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
And in Global.asax i added in Application_Start method those:
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
It didn't worked!
2) In web.config I tried with different values of global-culture, like:
<globalization culture="en-US" uiCulture="en" />
'auto' value also didn't help in there..
So anything that I found in different sites via google didn't help me :/
Related
I have a simple TextBoxFor, which I am using on a decimal. My decimal is simply defined as the following in my viewmodel:
[DisplayName("Beløb")]
public decimal Amount { get; set; }
I use this in my form, using:
<div class="form-group row">
#Html.LabelFor(c => c.Amount, new { #class = "col-lg-3 control-label text-lg-right pt-2" })
<div class="col-lg-6">
#Html.TextBoxFor(c => c.Amount, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.Amount)
</div>
</div>
Now, in my web.config, I have set globalization under system.web:
<globalization culture="da-DK" uiCulture="da-DK" />
My problem is the following:
The following are how we would write a number in Denmark:
500,50 <-- fivehundred and fifty cents
If I POST this to the server, the comma is ignored, and my controller receives 50050.
What am I doing wrong here? I thought the web.config would be enough.
Turns out this was an error no one on StackOverflow would've been able to fix, because I did not provide the full picture. I will leave the answer here just in case someone else Googles this and it's just one more place to look in desperate times.
The error was in my Global.asax. I had set up two model binders for decimals:
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
And my code was:
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult.AttemptedValue.Equals("N.aN") ||
valueProviderResult.AttemptedValue.Equals("NaN") ||
valueProviderResult.AttemptedValue.Equals("Infini.ty") ||
valueProviderResult.AttemptedValue.Equals("Infinity") ||
string.IsNullOrEmpty(valueProviderResult.AttemptedValue))
{
return 0m;
}
try
{
return Convert.ToDecimal(valueProviderResult.AttemptedValue, new CultureInfo("en-US"));
}
catch (Exception)
{
return 0m;
}
}
}
After fixing this, it just worked. Obviously. I'm an idiot ;)
I am validation for Html.TextBoxFor. Here is my code on the view
#Html.TextBoxFor(m => m.Amount, new {#class = "form-control", Value = String.Format("{0:C}", Model.Amount) })
This code takes the double value from the database like 5000.00 and displays on the UI as $5,000.00. However when the user hits the submit button, a validation error is displayed that
The value '$5,000.00' is not valid for Amount.
My validation annotation on the Model is
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid dollar amount")]
To get it to submit, I had to retype as 5000.00. How can I fix this? Thanks.
When you do the value = string.Format("{0:C}", Model.Amount) in the htmlAttributes, razor will execute the C# code and return the value,"$125.67", (Assuming the value of your Amount property is 125.67M) which is a string. So the markup generated by your view will be
<input value="$125.67" class="form-control" id="Amount" name="Amount" type="text">
Now since $125.67 is not not a valide decimal value, but a string. it cannot map the value of this textbox to the Amount property of your view model which is of type decimal/doube.
What you can do is, create a new property in your view model of type string to store this formatted string value and when user submits the form, try to parse it back to a decimal variable and use it.
So add a new property to your view model
public class CreateOrderVm
{
public int Id { set;get;}
public string AmountFormatted { set;get;} // New property
public decimal Amount { set;get;}
}
And in your view, which is strongly typed to CreateOrderVm
#model CreateOrderVm
#using(Html.BeginForm())
{
#Html.TextBoxFor(m => m.AmountFormatted, new { #class = "form-control",
Value = String.Format("{0:C}", Model.Amount) })
<input type="submit" />
}
And in your HttpPost action
[HttpPost]
public ActionResult Create(CreateOrderVm model)
{
decimal amountVal;
if (Decimal.TryParse(vm.AmountFormatted, NumberStyles.Currency,
CultureInfo.CurrentCulture, out amountVal))
{
vm.Amount = amountVal;
}
else
{
//add a Model state error and return the model to view,
}
//vm.Amount has the correct decimal value now. Use it to save
// to do :Return something
}
You can create you own Binder object to handle this. First create this object:
public class DoubleModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
if (!string.IsNullOrEmpty(valueResult.AttemptedValue))
actualValue = Convert.ToDouble(valueResult.AttemptedValue.Replace("$", ""), System.Globalization.CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
if (bindingContext.ModelState.ContainsKey(bindingContext.ModelName))
bindingContext.ModelState[bindingContext.ModelName] = modelState;
else
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
Then in your Global.asax.cs file in the Application_Start function, add this:
ModelBinders.Binders.Add(typeof(double?), new DoubleModelBinder());
I'm working on an mvc .net web application and I'm using Entity Framework for generating Model. I have classes that contain attributes that are doubles. My problem is that when I use #HTML.EditorFor(model => model.Double_attribute) and test my application I can't type a double in that editor, I only can type integers. (I'm using Razor engine for views)How to solve this? Thanks.
Update : I discovered that I can type a double having this format #,### (3 numbers after the comma but I do not want to make user type a specific format, I want to accept all formats (1 or more numbers after the comma)
Does anyone have an idea how to solve this? Regards
You could use add notations :
[DisplayFormat(DataFormatString = "{0:#,##0.000#}", ApplyFormatInEditMode = true)]
public double? Double_attribute{ get; set; }
And now... voila : you can use the double in your view :
#Html.EditorFor(x => x.Double_attribute)
For other formats you could check this or just google "DataFormatString double" your desired option for this field.
try to use custom databinder:
public class DoubleModelBinder : IModelBinder
{
public object BindModel( ControllerContext controllerContext,
ModelBindingContext bindingContext )
{
var valueResult = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
var modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
actualValue = Convert.ToDouble( valueResult.AttemptedValue,
CultureInfo.InvariantCulture );
}
catch ( FormatException e )
{
modelState.Errors.Add( e );
}
bindingContext.ModelState.Add( bindingContext.ModelName, modelState );
return actualValue;
}
}
and register binder in global.asax:
protected void Application_Start ()
{
...
ModelBinders.Binders.Add( typeof( double ), new DoubleModelBinder() );
}
I am pretty new to ASP.Net MVC (and razor) and I have a few questions.
1)
I created an HTML extension to create a check box list as below:
public static HtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<InputItemInfo> ItemInfo)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentException("The argument must have a value", "name");
if (ItemInfo == null)
throw new ArgumentNullException("ItemInfo");
if (ItemInfo.Count < 1)
throw new ArgumentException("The list must contain at least one value", "ItemInfo");
StringBuilder sb = new StringBuilder();
ItemInfo.Insert(0, new InputItemInfo("*", "Select All", ItemInfo.All(i => i.IsChecked)));
foreach (InputItemInfo info in ItemInfo)
{
TagBuilder builder = new TagBuilder("input");
if (info.IsChecked) builder.MergeAttribute("checked", "checked");
builder.MergeAttribute("type", "checkbox");
builder.MergeAttribute("value", info.Value);
builder.MergeAttribute("name", name);
builder.InnerHtml = info.DisplayText;
sb.Append(builder.ToString(TagRenderMode.Normal));
sb.Append("<br />");
}
return new HtmlString(sb.ToString());
}
I was able to use this in my views and also get the values in the controller as shown below:
#model List<AppTest.Models.InputExtensionsViewModel>
#{
ViewBag.Title = "Check";
}
<h2>Check</h2>
#using (Html.BeginForm())
{
<table border="0" style="border:0px;">
<tr>
<td valign="top">
#Html.Partial("CheckBoxList", Model[0])
</td>
</tr>
</table>
<br />
<input type="submit" value="Go" />
}
<div style="font-weight:bolder">
#ViewData["data"]
</div>
controller:
public ActionResult Check()
{
var model = new List<InputExtensionsViewModel>();
var model1 = new InputExtensionsViewModel
{
Title = "Facilities",
InputElementName = "facilities",
InputElements = // a list
};
model.Add(model1);
return View(model);
}
[HttpPost]
public ActionResult Check(string[] facilities)
{
...
}
The model is:
public class InputExtensionsViewModel
{
public string Title { get; set; }
public string InputElementName { get; set; }
public List<InputItemInfo> InputElements { get; set; }
public void SetSelected(string[] items)
{
if (items == null)
return;
this.InputElements.ForEach(delegate(InputItemInfo info)
{
if (items.Contains(info.Value))
info.IsChecked = true;
});
}
}
My question is, is there a way by which I could bind the array items to a property in the InputExtensionsViewModel model? If I just add a property called facilities to the view model, it's not bound automatically and I can understand why, as I am not binding that in my view. But I cannot seem to think of a way by which I could do that.
This check box list is a user control and I just wanted to avoid having too many string[] array for my action methods.
[EDIT] - Okay, I was able to do this when I tried now. Not sure why it didn't work before.
2) And, I was checking for alternatives and found out this answer in SO:
CheckboxList in MVC3.0
And I was able to replicate this but my question is, how do i bind a label to this checkbox? My labels are dynamic and part of the model and so cannot be hard-coded. I was trying to use a Html.LabelFor but that didn't work. In the editor template, if I just #Model.Text, it won't work and will be lost after a post-back as its not bound to a property
I googled and found suggestions to create HTML helpers which is what I did earlier (my 1st question is about that).
Please let me know if something is unclear. I could elaborate. Any input is appreciated!
Thanks in advance!
Ah, I found the solutions!
1) As indicated in my edit - adding a property with a similar name to a model and using it in the [HttpPost] enabled action method works fine. Guess last time I missed the getter and setters.
2) For this, in the editor template for MyViewModel, we just need to add this (** and **, needless to say, remove **!):
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.IsChecked) **#Model.Text
#Html.HiddenFor(x => x.Text)**
EDIT:
I have changed this template to do more. Now there is a label control and it is associated to the checkboxes through jquery as shown below.
#model EncorPlusTest.Infrastructure.InputItemInfo
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) <label for="">#Model.Text</label>
#Html.HiddenFor(model => model.Text)
<br />
Then in jquery:
$('input:checkbox').each(function () {
var lbl = $(this).next('input:hidden').next('label');
var forID = $(this).attr('id');
$(lbl).attr('for', forID);
});
Hope its helpful for others!
To answer part 2, you can easily add your label text as a property such as:
public class MyViewModel
{
public int Id { get; set; }
public bool IsChecked { get; set; }
public string Text { get; set; }
}
Then your template would look similar to this:
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.LabelFor(x => x.Text)
The only downside to the above is that the label wouldn't be linked directly to the checkbox. You can accomplish this by doing something such as: CheckboxList in MVC3
Depending on the chance for re-usability, you can always create your own HtmlHelper as you were doing in the first part of this and wrap in the suggestions from the URL I pasted above.
You don't jQuery to solve this problem. If you wrap the input with a label you get the same behavior.
By the way, another option, instead of a editor template, is a HTML Helper. Take a look at this:
public static class HtmlHelperExtensions
{
#region CheckBoxList
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo)
{
return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)null));
}
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo, object htmlAttributes)
{
return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)new RouteValueDictionary(htmlAttributes)));
}
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> selectListItems, IDictionary<string, object> htmlAttributes)
{
// Verify arguments
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name", "Name cannot be null");
if (selectListItems == null)
throw new ArgumentNullException("selectList", "Select list cannot be null");
if (selectListItems.Count() < 1)
throw new ArgumentException("Select list must contain at least one value", "selectList");
// Define items
StringBuilder items = new StringBuilder();
int index = 0;
// Loop through items)
foreach (SelectListItem i in selectListItems)
{
// hidden value
TagBuilder hiddenValue = new TagBuilder("input");
hiddenValue.MergeAttribute("type", "hidden");
hiddenValue.MergeAttribute("value", i.Value);
hiddenValue.MergeAttribute("id", string.Format("{0}_{1}__Value", name, index));
hiddenValue.MergeAttribute("name", string.Format("{0}[{1}].Value", name, index));
// check box
TagBuilder checkbox = new TagBuilder("input");
if (i.Selected)
checkbox.MergeAttribute("checked", "checked");
checkbox.MergeAttribute("id", string.Format("{0}_{1}__Selected", name, index));
checkbox.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
checkbox.MergeAttribute("type", "checkbox");
checkbox.MergeAttribute("value", "true");
// wrapper label
TagBuilder wrapperLabel = new TagBuilder("label");
wrapperLabel.InnerHtml = checkbox.ToString(TagRenderMode.SelfClosing);
wrapperLabel.InnerHtml += i.Text;
// hidden selected
TagBuilder hiddenSelected = new TagBuilder("input");
hiddenSelected.MergeAttribute("type", "hidden");
hiddenSelected.MergeAttribute("value", i.Selected.ToString().ToLower());
hiddenSelected.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
// label for checkbox
TagBuilder checkBoxLabel = new TagBuilder("label");
checkBoxLabel.MergeAttribute("for", checkbox.Attributes["id"]);
checkBoxLabel.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
checkBoxLabel.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));
// hidden text
TagBuilder hiddenText = new TagBuilder("input");
hiddenText.MergeAttribute("type", "hidden");
hiddenText.MergeAttribute("value", i.Text);
hiddenText.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
hiddenText.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));
// Add item
items.AppendLine(hiddenValue.ToString(TagRenderMode.SelfClosing));
items.AppendLine(wrapperLabel.ToString(TagRenderMode.Normal));
items.Append(hiddenSelected.ToString(TagRenderMode.SelfClosing));
items.AppendLine(hiddenText.ToString(TagRenderMode.SelfClosing));
items.AppendLine();
index++;
}
return MvcHtmlString.Create(items.ToString());
}
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var name = ExpressionHelper.GetExpressionText(expression);
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return CheckBoxList(htmlHelper, name, metadata.Model as List<SelectListItem>);
}
#endregion
}
i've created my own custom model binder to handle a Section DropDownList defined in my view as:
Html.DropDownListFor(m => m.Category.Section, new SelectList(Model.Sections, "SectionID", "SectionName"), "-- Please Select --")
And here is my model binder:
public class SectionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (bindingContext.ModelType.IsAssignableFrom(typeof(Section)) && value != null)
{
if (Utilities.IsInteger(value.AttemptedValue))
return Section.GetById(Convert.ToInt32(value.AttemptedValue));
else if (value.AttemptedValue == "")
return null;
}
return base.BindModel(controllerContext, bindingContext);
}
}
Now within my controller i can say:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
var category = new Category();
if (!TryUpdateModel(category, "Category")
return View(new CategoryForm(category, _sectionRepository().GetAll()));
...
}
This validates nicely and the correct value for the section is assigned when the model is updated, however it does not select the correct value if another property doesn't validate.
I'd appreciate it if someone could show me how to do this. Thanks
Problem solved by saying:
Html.DropDownListFor(m => m.Category.Section, new SelectList(Model.Sections.Select(s => new { Text = s.SectionName, Value = s.SectionID.ToString() }), "Value", "Text"), "-- Please Select --")
The issue seems to resolve around the SectionID being an integer. When you convert it to a string everything works fine. Hope this helps.