passing decimals to REST API from multilingual frontend - c#

I have a REST API in .Net Core 3 and front end in Angular 8. My front is a multilingual admin panel where I need to configure price for a product. The issue is I am not able receive price with decimal values.
My default culture of .NET Core API is "en-US" but my client is using "nl-NL" from front end. As you know Netherland they use "," instead of "." for decimal points therefore I am not getting price in my submitted model. Here are code snippets;
REST API
Entity
public class Product{
public Guid Id {get;set;}
public string Name {get;set;}
public decimal Price {get;set;}
}
Controller Method
[HttpPost]
public Task<IActionResult> SaveProduct([FromForm]Product model){
....code to save the product....
}
Stratup.cs
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("de"),
new CultureInfo("fr"),
new CultureInfo("sv")
};
options.RequestCultureProviders = new List<IRequestCultureProvider>()
{
new AcceptLanguageHeaderRequestCultureProvider()
};
options.FallBackToParentCultures = true;
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
I tried setting Requestdefault culture to "nl" but then "en" values doesn't work. Can any one please help me how to pass decimal points from multilingual frontend to a REST API.
Thanks.

If you use [FromForm],you could create your own custom mobel binder for the Price property:
1.Create a DecimalModelBinder
public class DecimalModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null)
{
return Task.CompletedTask;
}
var value = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
// Replace commas and remove spaces
value = value.Replace(",", ".").Trim();
decimal myValue = 0;
if (!decimal.TryParse(value, out myValue))
{
// Error
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
"Could not parse MyValue.");
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Success(myValue);
return Task.CompletedTask;
}
}
2.Use it on Price property
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
[BindProperty(BinderType = typeof(DecimalModelBinder))]
public decimal Price { get; set; }
}
3.Action
[HttpPost]
public Task<IActionResult> SaveProduct([FromForm]Product model){
....code to save the product....
}

Based on your question, it appears that your problem is that your front-end code is submitting the price with a comma on it. This can only be possible if you are sending a string representation of the price to the server, rather than a numeric one.
You should modify your front end code to always store and transmit the price value as a number. You can control how a price is displayed to a user in strings using Intl.NumberFormat().
As the user has to enter a price, you're probably using a <input type=text /> tag to capture the cost of the product. Since we want users to be able to enter commas here, we can't use <input type=number /> - instead, we can simply modify the value we get from this input tag on the client side by replacing occurences of commas in the string with a period - amount.replace(',', '.') - and then attempting to parse the number from the string - parseInt(amount, 10).
If parseInt returns NaN, you should display a validation error to the user.

Getting Decimal values for a price from the client is a terrible idea. I remember stories where a haphazard developer had put the shopping cart into the cookie, including prices. It took not that long for this mistake to be found and exploited. The company had the bill, because they had made their shop faulty.
Never trust input from the user. Especially if that user is on the Internet.
As for the specific problem: You basically have the issue that frontend and backend culture varies. I got 3 items of advice for transmitting numbers between processes:
never transmit them as String. String is arguably the 2nd worst format for processing. The only thing worse is raw binary
if you have to transmit them as string, make certain you pick fixed culture and encoding at both endpoints. Stuff like XML and JSON tends to take care of that for you. But you may have to pick something like the Invariant Culture.
If you are transmitting, storing or retrieving a DateTime, always do so as UTC. And hope you do not have that rare application that has to store the original Timezone (like a calendar).

Related

Entity Framework decimal problem on insert after publish

I have a data model named Gold with a decimal property for the price of gold like this:
public class Gold
{
[key]
public int GoldId { get; set; }
public decimal RateValue { get; set; }
}
I know that EF decimal default precision and scale is (18,2) and this is enough for me. I don't want to change it and I do not have any problem on insert decimal values on localhost.
But after publishing my website on a host, when I try to insert a decimal like 1210.40 it does not insert or sometimes it inserts 1210.00 (with wrong scale) in db without any error!
I completely confused because I don't have this problem on local running of my code.
No problem with decimals like 1210 without the right of dot digits!!!
This is my code for insert a decimal:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="RateValue")]Gold manualPrices)
{
Gold ons = new Gold() {RateValue=(decimal)manualPrices.RateValue,};
db.GoldRepository.Insert(ons);
db.Save();
}
Razor Code:
#Html.EditorFor(model => model.RateValue, new { htmlAttributes = new { #class = "form-control" } })
more info: I am using MVC .Net Framework 4.5.2 , Code first, Unit of Work, MS SQL 2016.
Thanks to #StephenMuecke and #heuristican
The culture have been changed to Persian culture in global. This affects the character used for the decimal separator from dot(.) to slash (/) so decimals with dot as separator don't even bind.
Nothing changed in code. Just typing decimals like 1210/40 instead of 1210.40 for insert. Problem solved.

Passing date as string - prevent json.net from parsing date

I have a Web API middle layer which consumes an API which exposes a field which carries a timestamp as string (the field is string and it contains a value like "2016-05-31T14:12:45.753Z").
The proxy classes in the middle tier are generated using Visual Studio from Swagger endpoint and under the hood the object is deserialized using Json.NET.
I can see that the field was received as string (that's good):
inputObject {{ "When": "2016-05-31T14:12:45.753Z" }} Newtonsoft.Json.Linq.JToken {Newtonsoft.Json.Linq.JObject}
However, even though the target field is string the value of inputObject["When"] is a parsed as a timestamp.
inputObject["When"] {31/05/2016 14:12:45} Newtonsoft.Json.Linq.JToken {Newtonsoft.Json.Linq.JValue}
Then
JToken whenValue = inputObject["When"];
if (whenValue != null && whenValue.Type != JTokenType.Null)
{
this.When = ((string)whenValue);
}
In the end this.When is a string with value 31/05/2016 14:12:45.
Is there an option to prevent json.net from parsing the date and then casting it to string again?
Please remember that this transformation happens in auto generated code so I'm looking for some way of decorating the field on the server side which would make Swagger mark it somehow and then the generated classes would avoid the deserialize/serialize issue.
Something like:
[JsonProperty("This really is a string, leave it alone")]
public string When { get; private set; }
(Answering my own question)
I needed a solution quickly and this is my temporary solution, for the record.
I format the date as
"When": "2016-05-31 14:12:45"
and not
"When": "2016-05-31T14:12:45.753Z"
This prevents it from being interpreted. The front end (javascript) code knows that timestamps from the API are UTC and it appends 'Z' before transforming the timestamp to local time and formatting for display, e.g:
<td>{{vm.prettyDateTimeFormat(item.StatusDate+'Z')}}</td>
The ctrl code:
vm.prettyDateTimeFormat = function (dateString)
{
var momentDate = moment(dateString, "YYYY-MM-DD HH:mm:ssZZ");
if (typeof(momentDate) === "undefined" || (!momentDate.isValid()))
{
return dateString;
}
//The format needs to be sortable as it ends up in the grid.
var nicePrettyDate = momentDate.format('YYYY-MM-DD HH:mm:ss');
return nicePrettyDate;
}
As far as I don't like this solution it carried us through the demo. This issue is obviously in the back log now to be addressed properly.
[JsonIgnore]
public string When { get; private set; }

DataGridView: How to allow comma and dot as a separators at the same time

I have a DataGridViewTextBoxColumn which is binded to a property. I want to allow user to input numbers no matter what he uses to separate decimals. Also I don't need spaces or commas to separate thousads.
It's simple:
1.908 = 1.908
1,908 = 1.908
And if there is no way to specify format string, can i Replace(",",".") before binding? Or any other way?
Thank you. (Sorry for my English)
Crete another property of String type which will be bounded to that column.
Then set/read value of original property through this
public class YourBindableItem
{
public decimal OriginalValue { get; set; }
public decimal ParsedValue
{
get { return this.OriginalValue.ToString(); }
set
{
string forParse =
value.Replace(",", Globalization.CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator);
decimal temp = 0;
if(decimal.TryParse(forParse,
out temp,
Globalization.CultureInfo.InvariantCulture) == true)
{
this.OriginalValue = temp;
}
//if value wasn't parsed succesfully, original value will be returned
this.RaiseOnPropertyChanged(nameOf(this.ParsedValue));
}
}
}
The DataGridView already formats according to the regional settings for the current user, at least if you data bind to an object data source and the property is numeric (i.e. not a string).
You can test this by opening Region and Language in Windows and switching between e.g. the English (United States) format and Swedish (Sweden). In the former case, the input 2.718 will parse correctly while in the second 2,718 will. You'll have to run without debugging in VS in order to load fresh settings.
(I would not suggest trying to parse both comma and dot as a decimal separator for the same user, if you're thinking of doing that. That's not the expected behavior for most users and it would lead to bugs if the user should happen to use the thousand separator too.)

Getting null datetime value from view to controller when i change CultureInfo

net mvc, i have a controller which gets a datetime parameter from view using jquery datepicker and then i pass
the value to the controller using json ,
it all works fine, except when i change language cultureInfo to German in my case, the value of datetime parameter is always null.
This is the controller:
public JsonResult GetDetails(DateTime? from, DateTime? to)
{
//Do something..
}
The model:
public class UsagesModel
{
public DateTime From
{
get;
set;
}
public DateTime To
{
get;
set;
}
}
The view in which data gets chosen and then pass to controller:
<input type="text" id="from" value="#Model.From.ToString("dd/MM/yyyy")" class="datepicker" />
<input type="text" id="to" value="#Model.To.ToString("dd/MM/yyyy")" class="datepicker" />
$("#filter").click(function (e) {
fromdate = $("#from").val();
todate = $("#to").val();
$.getJSON('#Response.ApplyAppPathModifier(#Url.Action("GetDetails"))', {
'from': StringToJSONDate(fromdate),
'to': StringToJSONDate(todate)
}, function (groupusages) {
.....Do Something....
}).error(function (xhr, textStatus, errorThrown) {
//document.location.href = "/Login";
});
});
//function for parsing data to json
function StringToJSONDate(stringDate) {
var dateParts = stringDate.split("/");
var date = new Date(dateParts[2], (dateParts[1] - 1), dateParts[0]);
return date.toJSON();
}
What can i do, where is the problem because it works fine in english and french culture. Please help me!
One thing you could do is to change your method signature to do something like this. You could spend a lot of time working on getting the right format for your mvc app for different cultures.
public JsonResult GetDetails(string from, string to)
{
var fromDate = DateTime.Parse(from);
var toDate = DateTime.Parse(to);
//Do something..
}
As Khan mentioned in his comment, you could make this a DateTime.ParseExact() so that you don't run into other culture issues.
https://msdn.microsoft.com/en-us/library/System.DateTime.ParseExact(v=vs.110).aspx
I do not know the exact change, but it may be because of a difference in the date/time format that Germany uses versus English and French speaking locations. I would try adding in a few alerts from the javascript to see at various points that you have a value there and then see where it gets screwy. I suspect the value still comes through but is not happy with the date formatting, should be a quick fix with some .split("/") and .join("/") function calls and addressing the index of the array

Decimal numbers in ASP.NET MVC 5 app

I have a problem with decimal numbers.
If I use .(dot) instead of ,(comma) in the textbox it comes null in controller.
I know its a language issue because in spanish we use comma instead of dot for decimals but I need to use dot.
It is possible to change this?
It is strange because in controller I have to use .(dot) for decimals i.e:
I can do float x = 3.14 but I can not do float x = 3,14 so I do not understand this... In some cases I have to use dot... In others I have to use comma...
This is my code:
In model:
[Display(Name = "Total")]
public double Total { get; set; }
In view:
#Html.EditorFor(model => model.Total, new { id = "Total", htmlAttributes = new {#class = "form-control" } })
In controller:
public ActionResult Create([Bind(Include = "ID,Codigo,Fecha,Trabajo,Notas,BaseImponible,Iva,Total,Verificado,FormaDePagoID,ClienteID")] Presupuesto presupuesto)
{
Thanks everybody. I found this code from Phil Haack that works pretty well.
Create a class in any folder of your project
public class ModelBinder
{
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
object result = null;
// Don't do this here!
// It might do bindingContext.ModelState.AddModelError
// and there is no RemoveModelError!
//
// result = base.BindModel(controllerContext, bindingContext);
string modelName = bindingContext.ModelName;
string attemptedValue =
bindingContext.ValueProvider.GetValue(modelName).AttemptedValue;
// Depending on CultureInfo, the NumberDecimalSeparator can be "," or "."
// Both "." and "," should be accepted, but aren't.
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
if (attemptedValue.IndexOf(wantedSeperator) == -1
&& attemptedValue.IndexOf(alternateSeperator) != -1)
{
attemptedValue =
attemptedValue.Replace(alternateSeperator, wantedSeperator);
}
try
{
if (bindingContext.ModelMetadata.IsNullableValueType
&& string.IsNullOrWhiteSpace(attemptedValue))
{
return null;
}
result = decimal.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
return result;
}
}
}
Add this to Application_Start() method in Global.asax
ModelBinders.Binders.Add(typeof(decimal), new ModelBinder.DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new ModelBinder.DecimalModelBinder());
Now use decimal type instead of float or double and everything will go fine !!
Thank you mates see you around !.
Your controller uses C#. The language specific states that . is the decimal separator. Period. It's not language specific, that's just it.
Your database or UI (which uses the server's language settings) might use another decimal separator than the default (US) language setting C# uses. That's why you have to use , as separator there.
you would need to use a custom model binder.
See this blog post http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx/
If you want your comma(,) separated decimal input in UI as per UI culture, to be converted to dot(.) to bind to C# decimal number, you can go for Asp.Net MVC's custom model binder, where take the comma separated decimal string and replace the comma with a dot and then assign to the C# decimal property.
The advantage is, its reusable across the application, where you might be having recurring scenarios for decimal conversions.
Hope following links could help you:
ASP.Net MVC Custom Model Binding explanation
http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx/

Categories