MVC4, Code First, C# project
When populating a money field with either a explicit value or from a table read the TextBoxFor displays the value with 2 decimal places. If the field is populated from a money field in another class it displays 4 decimal places.
public class Class1
{
[Column(TypeName = "money")]
public decimal Field1 { get; set; }
}
public class Class2
{
[Column(TypeName = "money")]
public decimal Field1 { get; set; }
}
public class Table1
{
public int Id { get; set; } public decimal Value { get; set; }
}
Scenario 1:
Class1.Field1 = 14.95M;
Scenario 2:
Class2.Field1 = Table1.Value;
Scenario 3:
Class1.Field1 = Class2.Field1
View
#Html.TextBoxFor(m => m.Class1.Field1, new { style = "width:70px;" })
For Scenario 1 & 2 the TextBoxFor correctly displays 2 decimal places, with Scenario 3 it displays 4 decimal places in the edit box. I need to use TextBoxFor so I can pass html attributes.
The instance of Class2 is itself pre-populated from values in a Table generated by Class2. I've examined everything with SSMS [all the applicable fields in the tables are (money, not null)] and in debug and cannot find any discrepancies.
Why does the TextBoxFor incorrectly display the money format for Scenario 3 (I understand that SQL stores decimals with 4 decimal precision)?
More importantly how do I get my edit box to always display money values with 2 decimals?
In my MVC app I wanted a text box to display like $4.95. I used editor template.
#if(Model != null && Model.GetType() == typeof(string))
{
#Html.TextBox(
"",
string.Format("{0:c}", (decimal)decimal.Parse(Model))
)
}
#if(Model != null && Model.GetType() == typeof(decimal))
{
#Html.TextBox(
"",
string.Format("{0:c}", (decimal) Model),new {#class="no-number-validation"}
)
}
#if(Model == null)
{
#Html.TextBox("",null,new {#class="no-number-validation"})
}
And then obviously I wanted to be able to send back $4.95 to the server and have the Model Binder still handle it automatically. This example handles % signs too.
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
{
if (valueResult.AttemptedValue.StartsWith("$"))
{
actualValue = decimal.Parse(valueResult.AttemptedValue, NumberStyles.Currency);
}
if (valueResult.AttemptedValue.EndsWith("%"))
{
actualValue = decimal.Parse(valueResult.AttemptedValue.Replace("%", "").Trim(),
CultureInfo.CurrentCulture);
}
if (actualValue == null)
actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
This is nice because I don't have to use a string as my property type for the currency textbox in order to handle the $ sign. By the time the object gets populated there is no money sign and the value gets assigned right into the decimal type.
You can round to 2 decimals (original values are 2 decimal precision so I'm not worrying about rounding errors).
decimal.Round(Value, 2)
Class1.Field1 = decimal.Round(Class2.Field1,2)
It can then be implemented through an extension method.
public static decimal dR2(this decimal ip) { return decimal.Round(ip, 2); }
Class1.Field1 = Class2.Field1.dR2();
Related
I have many classes with int, decimal variables in my C# application.
Consider the following example.
public class Employee
{
decimal TotalSalary;
public decimal Salary
{
get
{
return TotalSalary;
}
set
{
TotalSalary = value;
}
}
public string GetSalary()
{
return TotalSalary.ToString();
}
}
public class Contract
{
Employee emp1 = new Employee();
public void ProcessSalary()
{
emp1.Salary = 100000;
SendToLabel(emp1.GetSalary());
}
}
In the above example whenever I use "ToString" of any decimal/int variable in my application, it should give me the number in Indian numbering format like below.
100000 should render as 1,00,000
10000 should render as 10,000
This should happen globally in my C# .NET application.
Can I do this using CultureInfo in global.asax page.
Right now for formatting date i am using the following code.
CultureInfo newCulture = (CultureInfo) System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
newCulture.DateTimeFormat.ShortDatePattern = "dd-MMM-yyyy";
newCulture.DateTimeFormat.DateSeparator = "-";
Thread.CurrentThread.CurrentCulture = newCulture;
Following code will be useful to you,
public string GetSalary()
{
CultureInfo inr = new CultureInfo("hi-IN");
return string.Format(inr, "{0:#,#}", TotalSalary);
}
whenever I use "ToString" of any decimal/int variable in my application, it should give me the number in Indian numbering format
You could also make a new method as extension for the types decimal and int:
public static class MyExtensions
{
public static string MyOutput(this decimal number)
{
return number.ToString("#,#.##", new CultureInfo(0x0439));
}
public static string MyOutput(this int number)
{
return number.ToString("#,#", new CultureInfo(0x0439));
}
}
All culture codes for further reference.
Then you can use it throughout your programm for variables of the decimal/int types:
public string GetSalary()
{
return TotalSalary.MyOutput();
}
Output: for decimal asd = 1000000.23m;
10,00,000.23
The signature of the GetSalary will be like this:
public string GetSalary()
{
return String.Format("{0:n}", TotalSalary);
}
Working Example
You can use "{0:n3}" if you want to round off decimals to 3 digits.
I'm using Steve Sandersons BeginCollectionItem extension to help with binding lists of items. This works fine for primitive types. The problem I'm having is that for a custom model binder that I've written I can't see how to generate the full name and index of the item that I'm binding to.
Currently my model binder looks like this:
public class MoneyModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Amount");
if (valueResult != null)
{
var value = valueResult.AttemptedValue;
var currencyCode = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Iso3LetterCode").AttemptedValue;
var money = (Money) bindingContext.Model;
Money parsedValue;
if (String.IsNullOrEmpty(value))
{
money.Amount = null;
return;
}
var currency = Currency.FromIso3LetterCode(currencyCode);
if (!Money.TryParse(value, currency, out parsedValue))
{
bindingContext.ModelState.AddModelError("Amount", string.Format("Unable to parse {0} as money", value));
}
else
{
money.Amount = parsedValue.Amount;
money.Currency = parsedValue.Currency;
}
}
else
{
base.OnModelUpdated(controllerContext, bindingContext);
}
}
}
My ViewModel Lokks like this (some propertis omitted for clarity):
public class EditFeeEarningCapacityViewModel
{
public List<FeeEarner> FeeEarners { get; set; }
public class FeeEarner
{
public Money AverageChargeOutRate { get; set; }
}
}
My Edit Template for the Money type looks like this:
#model Core.Money
#{
int decimalPlaces;
if(!int.TryParse(string.Format("{0}", ViewData["DecimalPlaces"]), out decimalPlaces))
{
decimalPlaces = 0;
}
}
<div class="input-prepend">
<span class="add-on">#Model.Currency.Symbol</span>#Html.TextBoxFor(m => m.Amount,
new
{
placeholder = string.Format("{0}", Model.Currency),
#class = "input-mini",
Value = String.Format("{0:n" + decimalPlaces + "}", Model.Amount)
})
</div>
#Html.HiddenFor(x => x.Iso3LetterCode)
For a form that has post values like this:
FeeEarners.index 3fa91d09-0617-4bea-ae3f-d84862be8c04
FeeEarners[3fa91d09-0617-4bea-ae3f-d84862be8c04].feeEarner.AverageChargeOutRate.Amount 500
FeeEarners[3fa91d09-0617-4bea-ae3f-d84862be8c04].feeEarner.AverageChargeOutRate.Iso3LetterCode GBP
I can't see how to detect the index of the item or the property name that I'm binding to. So essentially, how do I find the index of the item I'm trying to bind to and the name of the property that I'm trying to bind the data from?
I am not fimilar with that Helper but for collection i am doing a bit different trick.
define key
var key = "EditModel[{0}].{1}";
var index = 0;
then build form
foreach(var fee in Model.FeeEarners){
#Html.TextBox(string.Format(key, index, "PropertyNameFromYourFeeClass"));
//It will build text box and set value
}
On Controller side
create action with input parameter
public ActionResult Save(EditFeeEarningCapacityViewModel editModel){
...your code here
}
I want to format the decimal places displayed and captured in a DataGridView, I have a minimum number of decimal places and a maximum number of decimal places.
For example:
If caught, "120.0" display "120.00"
If caught "120.01" display "120.01"
If caught "120,123" display "120,123"
If caught "120.1234" display "120.1234"
If caught "120.12348" display "120.1235" (round)
In the DataGridView column "txtcDecimal" has the property (from the designer)
txtcDecimal.DefaultCellStyle.Format = "N2";
txtcDecimal.DefaultCellStyle.Format = "0.00##"; // IS ANSWER. I do not work for an event that interfered
the mask "0.00##" work as "n2" only get 2 decimals
which does the correct rounding to two decimal places but just do not like what I need (as I show in the example)
How I can do it in a simple manner without consuming many resources?
Thanks harlam357 & Tom Garske
To format between 2 and 4 decimal places you can use a custom format string.
txtcDecimal.DefaultCellStyle.Format = "0.00##"
To go a bit further...
public partial class Form1
{
public Form1()
{
InitializeComponent();
var list = new List<Data>();
list.Add(new Data { Prop1 = 1, Prop2 = 1.2 });
list.Add(new Data { Prop1 = 2, Prop2 = 1.234567 });
dataGridView1.Columns.Add("Prop1", "Prop1");
dataGridView1.Columns["Prop1"].DataPropertyName = "Prop1";
dataGridView1.Columns.Add("Prop2", "Prop2");
dataGridView1.Columns["Prop2"].DataPropertyName = "Prop2";
dataGridView1.Columns["Prop2"].DefaultCellStyle.Format = "0.00##";
dataGridView1.DataSource = list;
}
class Data
{
public int Prop1 { get; set; }
public double Prop2 { get; set; }
}
}
To format between 2 and 4 decimal places you can use a custom format string. (As provided by Harlam357)
txtcDecimal.DefaultCellStyle.Format = "0.00##"
I verified it with the following simple console application:
double myDouble = 13.1;
double myDouble2 = 13.12345;
Console.WriteLine(myDouble.ToString("0.00##"));
Console.WriteLine(myDouble2.ToString("0.00##"));
Console.Read();
The output was:
13.10
13.1235
Harlam's answer is clearly correct....
UPDATE: This is how I implemented it in forms:
public Form1()
{
InitializeComponent();
DataTable dt = new DataTable();
dt.Columns.Add("a");
dt.Rows.Add(123.4);
dt.Rows.Add(123.45);
dt.Rows.Add(123.456);
dt.Rows.Add(123.4567);
dt.Rows.Add(123.45678);
this.dataGridView1.DataSource = dt;
this.dataGridView1.CellFormatting += new DataGridViewCellFormattingEventHandler(dataGridView1_CellFormatting);
}
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex != this.dataGridView1.NewRowIndex)
{
double d = double.Parse(e.Value.ToString());
e.Value = d.ToString("0.00##");
}
}
SOURCE: http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/95e7e7ef-2e71-412f-abe5-ffbee2c12c18/
OUTPUT:
Create a custom formatter.
public class MyFormatProvider : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
// Check whether this is an appropriate callback
if (!this.Equals(formatProvider))
return null;
//if argument/ value is null we return empty string
if (arg == null)
return null;
string resultString = arg.ToString();
//transform resultString any way you want (could do operations based on given format parameter)
//return the resultant string
return resultString;
}
}
SOURCE: How to custom format data in datagridview during databinding
I have the following code in a Calculations.cs class:
public decimal decPaymentPlan(QuoteData quoteData)
{
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else
return PriceQuote.pricePaymentPlanChapter7; //may want to switch
//to Chapter13 value
}
public decimal CalculateChapter7(QuoteData quoteData)
{
decimal total = PriceQuote.priceChapter7;
total += this.decPaymentPlan(quoteData); //want to be able to tell
//which to use, 7 or 13
return total;
}
I am trying to see if I can avoid an extra decPaymentPlan where the final return is pricePaymentPlanChapter13. I thought there might be a way to switch it out.
Otherwise, I'd have to do the following:
public decimal decPaymentPlanChapter7(QuoteData quoteData)
{
...
else
return PriceQuote.pricePaymentPlanChapter7;
}
public decimal decPaymentPlanChapter13(QuoteData quoteData)
{
...
else
return PriceQuote.pricePaymentPlanChapter13;
}
...
//the following will appear anyway, but rather than just using
//one method call which switches the choice based on something
public decimal CalculateChpater7(QuoteData quoteData)
{
...
//instead of decPaymentPlan(quoteData) + something to switch
total+= this.decPaymentPlanChapter7(quoteData);
...
}
public decimal CalculateChpater13(QuoteData quoteData)
{
...
//instead of decPaymentPlan(quoteData) + something to switch
total+= this.decPaymentPlanChapter13(quoteData);
...
}
Is something like this doable (and how)? Thanks. Appreciate any code samples or guidance.
UPDATE:
This is my controller:
public ActionResult EMailQuote()
{
Calculations calc = new Calculations();
QuoteData quoteData = new QuoteData
{
StepFilingInformation = new Models.StepFilingInformation
{
//just moking user input here temporarily to test out the UI
PaymentPlanRadioButton = Models.StepFilingInformation.PaymentPlan.Yes,
}
};
var total = calc.CalculatePrice(quoteData);
ViewBag.CalculatePrice = total; // ADDED THIS LINE
return View(quoteData);
}
Also, I set a value in PriceQuote for Chapter7 and Chapter 13 (e.g., public static decimal priceChapter7 { get { return 799; } }
Hard to be sure of a suggestion without understanding more about what you are doing, but if the only difference between your methods are a set of values to use (one set for chapter7, the other for chapter13) it may make sense to take these values out of PriceQuote and create a base type to hold these values. Then your decPaymentPlan and other methods would only take an instance of that type. For example:
class Chapter // for lack of a better name
{
public decimal PaymentPlan { get; set; }
public decimal Price { get; set; }
....
}
Then, change your methods to take a Chapter parameter
public decimal decPaymentPlan(QuoteData quoteData, Chapter chapter)
{
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else
return chapter.PaymentPlan;
}
public decimal Calculate(QuoteData quoteData, Chapter chapter)
{
decimal total = chapter.Price;
total += this.decPaymentPlan(quoteData, chapter);
return total;
}
Now all you would need are two instances of Chapter, one for 7 and the other for 13, and call your calculate method accordingly.
UPDATE: To elaborate a bit on what I mean by 'call your calculate method accordingly', lets say for example you had two static variables (somewhere that makes sense in your application, perhaps in Calculations.cs)
static Chapter Chapter7 = new Chapter() { Price = 799.99, PaymentPlan = 555.55 };
static Chapter Chapter13 = ...
Then in your controller, you would be able to write
ViewBag.Chapter7Total = calc.CalculatePrice(quoteData, Chapter7);
ViewBag.Chapter13Total = calc.CalculatePrice(quoteData, Chapter13);
What's the difference between 7 and 13? I would just opt into doing:
if (quoteData.StepFilingInformation.PaymentPlanRadioButton ==
StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else if (//whatever fulfills ch. 7)
return PriceQuote.pricePaymentPlanChapter7;
else //ch. 13
return PriceQuote.pricePaymentPlanChapter13;
It looks like you could create an Enumeration of the Chapters and pass that in as a second parameter to the decPaymentPlan method yes?
You are mixing your business logic with your visualization layer:
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
A better design would be to have a model on which changes are applied e.g. MVC, MVP, MVVM.
Example:
public class View
{
private Model _model = new Model();
public View()
{
}
public Controller Controller
{
get;
set;
}
private void OnButton1Click(object sender, EventArgs args)
{
_model.Option = Options.Option1;
}
private void OnSaveClick(object sender, EventArgs args)
{
if (Controller != null)
Controller.ApplyChanges(_model);
}
}
The controller can then apply business logic free of the view structure, so that you can change either of the two freely.
E.g.
public class Controller
{
Model Model
{
get;
set;
}
decimal CalculateSum()
{
return Model.Items.Aggregate((a, b) => a + b);
}
}
Is it possible to use [Range] annotation for dates?
something like
[Range(typeof(DateTime), DateTime.MinValue.ToString(), DateTime.Today.ToString())]
I did this to fix your problem
public class DateAttribute : RangeAttribute
{
public DateAttribute()
: base(typeof(DateTime), DateTime.Now.AddYears(-20).ToShortDateString(), DateTime.Now.AddYears(2).ToShortDateString()) { }
}
Docs on MSDN says you can use the RangeAttribute
[Range(typeof(DateTime), "1/2/2004", "3/4/2004",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public datetime Something { get; set;}
jQuery validation does not work with [Range(typeof(DateTime),"date1","date2"] --
My MSDN doc is incorrect
Here is another solution.
[Required(ErrorMessage = "Date Of Birth is Required")]
[DataType(DataType.Date, ErrorMessage ="Invalid Date Format")]
[Remote("IsValidDateOfBirth", "Validation", HttpMethod = "POST", ErrorMessage = "Please provide a valid date of birth.")]
[Display(Name ="Date of Birth")]
public DateTime DOB{ get; set; }
The simply create a new MVC controller called ValidationController and past this code in there. The nice thing about the "Remote" approach is you can leverage this framework to handle any kind of validations based on your custom logic.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mail;
using System.Web;
using System.Web.Mvc;
namespace YOURNAMESPACEHERE
{
public class ValidationController : Controller
{
[HttpPost]
public JsonResult IsValidDateOfBirth(string dob)
{
var min = DateTime.Now.AddYears(-21);
var max = DateTime.Now.AddYears(-110);
var msg = string.Format("Please enter a value between {0:MM/dd/yyyy} and {1:MM/dd/yyyy}", max,min );
try
{
var date = DateTime.Parse(dob);
if(date > min || date < max)
return Json(msg);
else
return Json(true);
}
catch (Exception)
{
return Json(msg);
}
}
}
}
For those rare occurrences when you are forced to write a date as a string (when using attributes), I highly recommend using the ISO-8601 notation.
That eliminates any confusion as to whether 01/02/2004 is january 2nd or february 1st.
[Range(typeof(DateTime), "2004-12-01", "2004-12-31",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public datetime Something { get; set;}
I use this approach:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class DateRangeAttribute : ValidationAttribute
{
public DateTime Minimum { get; }
public DateTime Maximum { get; }
public DateRangeAttribute(string minimum = null, string maximum = null, string format = null)
{
format = format ?? #"yyyy-MM-dd'T'HH:mm:ss.FFFK"; //iso8601
Minimum = minimum == null ? DateTime.MinValue : DateTime.ParseExact(minimum, new[] { format }, CultureInfo.InvariantCulture, DateTimeStyles.None); //0 invariantculture
Maximum = maximum == null ? DateTime.MaxValue : DateTime.ParseExact(maximum, new[] { format }, CultureInfo.InvariantCulture, DateTimeStyles.None); //0 invariantculture
if (Minimum > Maximum)
throw new InvalidOperationException($"Specified max-date '{maximum}' is less than the specified min-date '{minimum}'");
}
//0 the sole reason for employing this custom validator instead of the mere rangevalidator is that we wanted to apply invariantculture to the parsing instead of
// using currentculture like the range attribute does this is immensely important in order for us to be able to dodge nasty hiccups in production environments
public override bool IsValid(object value)
{
if (value == null) //0 null
return true;
var s = value as string;
if (s != null && string.IsNullOrEmpty(s)) //0 null
return true;
var min = (IComparable)Minimum;
var max = (IComparable)Maximum;
return min.CompareTo(value) <= 0 && max.CompareTo(value) >= 0;
}
//0 null values should be handled with the required attribute
public override string FormatErrorMessage(string name) => string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Minimum, Maximum);
}
And use it like so:
[DateRange("2004-12-01", "2004-12-2", "yyyy-M-d")]
ErrorMessage = "Value for {0} must be between {1} and {2}")]
I found issues with the [Range(typeof(DateTime)] annotation and would describe it as "clunky at best" it leaves too much to chance IF it works.
Remote validation seems to be a good way of: avoiding javascript in views and maintaining server side code integrity, personally never like sending code to a client to execute if I can avoid it.
Using #StackThis answer as a base and reference to an article on remote validation in MVC3
Model
public class SomeDateModel
{
public int MinYears = 18;
public int MaxYears = 110;
[Display(Name = "Date of birth", Prompt = "e.g. 01/01/1900")]
[Remote(action: "ValidateDateBetweenYearsFromNow", controller: "Validation", areaReference: AreaReference.UseRoot, AdditionalFields = "MinYears,MaxYears", HttpMethod = "GET" ,ErrorMessage = "Subject must be over 18")]
public DateTime? DOB { get; set; }
}
Controller - Deployed at the root directory
namespace Controllers
{
public class ValidationController : Controller
{
[HttpGet]
[ActionName("ValidateDateBetweenYearsFromNow")]
public JsonResult ValidateDateBetweenYearsFromNow_Get()
{
//This method expects 3 parameters, they're anonymously declared through the Request Querystring,
//Ensure the order of params is:
//[0] DateTime
//[1] Int Minmum Years Ago e.g. for 18 years from today this would be 18
//[2] int Maximum Years Ago e.g. for 100 years from today this would be 100
var msg = string.Format("An error occured checking the Date field validity");
try
{
int MinYears = int.Parse(Request.QueryString[1]);
int MaxYears = int.Parse(Request.QueryString[2]);
//Use (0 - x) to invert the positive int to a negative.
var min = DateTime.Now.AddYears((0-MinYears));
var max = DateTime.Now.AddYears((0-MaxYears));
//reset the response error msg now all parsing and assignmenst succeeded.
msg = string.Format("Please enter a value between {0:dd/MM/yyyy} and {1:dd/MM/yyyy}", max, min);
var date = DateTime.Parse(Request.QueryString[0]);
if (date > min || date < max)
//switch the return value here from "msg" to "false" as a bool to use the MODEL error message
return Json(msg, JsonRequestBehavior.AllowGet);
else
return Json(true, JsonRequestBehavior.AllowGet);
}
catch (Exception)
{
return Json(msg, JsonRequestBehavior.AllowGet);
}
}
}
}
The msg variable is displayed as part of the Html helper ValidationSummary or the Html helper ValidationFor(x=>x.DATETIME)
View
It's important to note that the fields passed as parameter 2 and 3 must exist in the view in order for the remote validation to pass the values to the controller:
#Html.EditorFor(m => m.DOB)
#Html.HiddenFor(m => m.MinYears)
#Html.HiddenFor(m => m.MaxYears)
#Html.ValidationSummary()
The model and Html helpers will do all the jquery work for you.