asp.net mvc validation annotation for dollar currency - asp.net

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());

Related

asp.net mvc form includes for loop over form controllers

i want to submit a form with for loop inside of it that loops over the form controllers since i want to pass list of model into a post method action in such a way that each and every form controller ( TextBoxFor ) is populated by corresponding value of my list of model
my Model :
public class ExcelRowData
{
public string LastName { get; set; }
public string FirstName { get; set; }
public string Score { get; set; }
public string EnrollmentTime { get; set; }
public string GraduationTime { get; set; }
}
and this is my view :
#model List<NewTest.Models.ExcelRowData>
#using (Html.BeginForm("Delete", "MyTest", FormMethod.Post))
{
for (var i = 0 ; i < Model.Count ; i++)
{
<tr>
<td>#Html.TextBoxFor(p => Model[i].LastName)</td>
<td>#Html.TextBoxFor(p => Model[i].FirstName)</td>
<td>#Html.TextBoxFor(p => Model[i].Score)</td>
<td>#Html.TextBoxFor(p => Model[i].EnrollmentTime)</td>
<td>#Html.TextBoxFor(p => Model[i].GraduationTime)</td>
<td><input type="submit" formaction="#Url.Action("Delete", "MyTest", new {Ln = Model[i].LastName})" value="Delete"/></td>
</tr>
}
}
i populated my model from excel file then i show the excel data in above table in view and then there is a delete button in case of deleting a record
and in my delete action i delete the selected record by the parameter passed from submit button , after that i repopulate my model and again i pass it to the same view
this is the delete action in my controller :
[HttpPost]
public ActionResult Delete(ExcelRowData evm, string Ln)
{
var exceldata = new List<ExcelRowData>();
var del = evm.FirstOrDefault(p => p.LastName == Ln);
evm.Remove(del);
foreach (var item in evm)
{
exceldata.Add(
new ExcelRowData { LastName = item.LastName, FirstName = item.FirstName, Score = item.Score, EnrollmentTime = item.EnrollmentTime, GraduationTime = item.GraduationTime}
);
}
return View("ExcelTable", exceldata);
}
the problem is when i press delete button in any row , it delets the record from bottom of the list , it doesn't matter which row i click to delete , it deletes from bottom of the list.
You posting back the whole collection, so all the property values have been added to ModelState, and the HtmlHelper methods that generate form controls (except PasswordFor()) use the values from ModelState (if they exist) rather the the property value.
You can solve this by adding
ModelState.Clear();
before you return the view, however the correct approach is to follow the PRG (Post, Redirect Get) pattern, and redirect to your GET method.
To understand why the HtmlHelper methods use ModelState, refer this answer.
As a side note, you can improve performance by using ajax to remove the item (and if successfully deleted, remove the corresponding row from the DOM), but that means you also need to include a hidden input for the collection indexer so that you can post back non-zero/non-consecutive indexers if your also posting back the updated collection elsewhere in your view. The input would be <input type="hidden" name="Index" value = "#i" />

Convert IQueryable object to a List and add a new Item

I have the following IQueryable object:
var user = from d in _context.Users
join userRole in _context.UserRoles on d.Id equals userRole.UserId
join role in _context.Roles on userRole.RoleId equals role.Id
where role.Name == "Liquidador"
select d;
Which then is send as a ViewBag to the View:
ViewBag.UserID = new SelectList(user.AsNoTracking(), "UserName", "Name", selectedUser);
The Problem:
I need to add a new Item to the result of the IQueryable. So I've proceeded like this:
var UserNameList = user.Select(s => new { s.Name, s.UserName }).ToList();
However, I'm missing something when I'm trying to add the new item:
UserNameList.Insert(0, new *NewWhat?* { Name = "Liquidador", UserName = "--Select--"} );
Usually I declare a new element of a specific model but in this case I don't know which model to declare for this IQueryable. Any recomendations?
Thanks
EDIT:
The IQueryable object goes to the Get Method of the View as part of a function:
public async Task<IActionResult> Management()
{
PopulateUserDropDownList();
var Tiendas = await _context.Stores.ToListAsync();
StoreEmployee model = new StoreEmployee
{
Stores = Tiendas
};
return View(model);
}
This list is then presented in a dropdownlist, inside a table:
<td class="col-md-2">
<div class="form-group" form="#(String.Format("{0}{1}","form",item.StoreID))">
<div>
<select asp-for="#item.Usuario" class="form-control" asp-items="ViewBag.UserId" form="#(String.Format("{0}{1}","form",item.StoreID))"></select>
<span asp-validation-for="#item.Usuario" class="text-danger"></span>
</div>
</div>
</td>
It seems it would be cleaner if you define a class UserDTO (or any other name that likes you more)
public class UserDTO
{
public string Name { get; set; }
public string UserName { get; set; }
}
and then you do
var UserNameList = user
.Select(s => new UserDTO { Name = s.Name, UserName = s.UserName })
.ToList();
UserNameList.Insert(0, new UserDTO { Name = "Liquidador", UserName = "--Select--"} );
OTOH... smells a little to add the empty element as part of the data array, my recommendation is to handle that on razor view and just send UserNameList with real data. Dropdown razor methods contains overloads to specify the empty element text.
If you show your HTML, we may help you to implement a better solution.

ASP.NET Allow decimal value with comma in EditorFor

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 :/

ASP.Net MVC 3 - CheckBoxList - Need some suggestions

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
}

Custom Model Binder for DropDownList not Selecting Correct Value

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.

Resources