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
Related
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).
If I have an ActionResult method like so:
public ActionResult AllSummaries(int? page, DateTime? yesterday)
Instead of the route being like:
http://serverName/projectName/controllerName/AllSummaries?yesterday=04/03/2017
I would like it to be:
http://serverName/projectName/controllerName/AllSummaries/04/03/2017
So on top of the ActionResult, how do I add a constraint to make the datetime only show the date in format MM/dd/yyyy?
[Route("allsummaries/yesterday/{yesterday:}")]
public ActionResult AllSummaries(int? page, DateTime? yesterday)
I do not need to validate against whether or not the date and day are 1 digit or 2 digit.. it will always be 2 digit.
Any help is appreciated.
UPDATE
Now getting 404 errors:
Here is the link that I am using:
http://serverName/projectName/controllerName/allsummaries/yesterday/3/4/2017
Here is my action:
[Route("controllerName/allsummaries/yesterday/{month?}/{day?}/{year?}")]
[ValidateInput(false)]
public ActionResult AllSummaries(int? page, int? day, int? month, int? year)
{
if (day.HasValue && month.HasValue && year.HasValue)
{
var yesterday = new DateTime(year.Value, month.Value, day.Value);
}
The route that I am generating is from a console application that is going to send out emails automatically via windows service, so I can't use #Url.Action...etc.. I am hardcoding the link like so:
mail.Body = mail.Body + "<div>" + "<p>" +
"http://serverName/projectName/controllerName/allsummaries/yesterday/" +
DateTime.Today.AddDays(-1).Day +
"/" + DateTime.Today.AddDays(-1).Month + "/" +
DateTime.Today.AddDays(-1).Year + "</p>" + "</div>";
The issue is the slashes in the date, which will be interpreted as path separators. The routing framework only parses params between path separators, unless you use the greedy param syntax, i.e. {*yesterday}. However, if you do that any further portions of the URL path will be consumed. For example, if a user changed the URL to something like allsummaries/yesterday/04/03/2017/foo, then 04/03/2017/foo would be passed in as yesterday and your action explodes.
You have two options.
You can use a different date format, like ISO: yyyy-MM-dd, which would make your URL /allsummaries/yesteday/2017-04-03, and you could capture the date portion with a single param: {yesterday}.
[Route("allsummaries/yesterday/{yesterday}")]
You can break up the date components and then recompose them into a DateTime in the action:
[Route("allsummaries/yesterday/{month?}/{day?}/{year?}")]
Then, in your action:
public ActionResult AllSummaries(int? page, int? month, int? day, int? year)
{
var yesterday = DateTime.Today.AddDays(-1); // default
if (month.HasValue && day.HasValue && year.HasValue)
{
yesterday = new DateTime(year.Value, month.Value, day.Value);
}
EDIT
I didn't want to confuse the main issue, but if you choose to follow the second option, breaking up the date into components, there is an issue you'll need to be aware of. If you have an explicit URL like /allsummaries/yesterday/04/03/2017, the modelbinder will be able to parse the "04" and "03" into ints. However, if you try to create the URL, using something like Url.Action, Url.RouteUrl, etc., you will need to feed the params values like "04", rather than an int, or you'll end up with URLs like /allsummaries/yesterday/4/3/2017. You could do that via something like:
#Url.Action("AllSummaries", new
{
month = date.ToString("MM"),
day = date.ToString("dd"),
year = date.Year
})
In other words, you would need to use ToString to get the two digit value, rather than date.Month or date.Day directly.
You should also probably protect the URL a little from tampering, by adding a regex contraint to these params:
[Route("allsummaries/yesterday/{month?:regex([0-9]{2})}/{day?:regex([0-9]{2})}/{year?:regex([0-9]{4}}")]
You can parse day, month and year alone,
and then create the date.
Your code will be like this:
[Route("allsummaries/yesterday/{day}/{month}/{year}")]
public ActionResult AllSummaries(int? page, int day, int month, int year)
{
var yesterday = new Date(day, month, year);
}
[Route("allsummaries/yesterday")]
public ActionResult AllSumaries(int? page)
{
}
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; }
I have an ASP.NET MVC app. My app has a Razor view that generates some JavaScript. That block of code looks like this:
<script type="text/javascript">
#if (Model == null)
{
var amount = 0;
}
else
{
<text>var amount = #Convert.ToString(Model.DailyAmount.Value);</text>
}
...
</script>
When a user's culture is set to Germany ("de-DE"), the view gets rendered as:
var amount = 0,00;
DailyAmount is a decimal? Notice how the value that gets rendered (0,00) has a comma to represent a decimal. However, I want to always render the value as 0.00 instead. How do I do this?
Thank you!
Specify exact culture for your amount - i.e.:
#Convert.ToString(Model.DailyAmount.Value, CultureInfo.InvariantCulture)
Maybe it's just the way my mind works, but I have a very hard time understanding how you're supposed to do custom unobtrusive validators. The C# part is easy enough, but the jqueryui adapters are where i get lost.
I've been trying to make a validator that requires a date to be a certain amount of time in the past. I use this for age validation, to make sure someone has entered a date that is 18 years in the past.
I finally decided to just make it a remote validator, that way the validation uses the same code both client and server side. Still, i'd be interested in the jquery to make this work.
I wish the Data Annotation Extensions had date functions.
You can find a lot of information in Brad Wilson's blog article about unobtrusive validation with asp.net mvc, including creating custom validators.
Based on the following html (should be the output of the TextBox helper)
<input type="text" name="Age"
data-val="true"
data-val-required="This field is required"
data-val-minage="You should be 18 years or older, go get your parents!"
data-val-minage-value="18" />
<input type="submit"/>
You can add the following javascript to get things validated on the client side:
// Extend date with age calculator
Date.prototype.age = function (at) {
var value = new Date(this.getTime());
var age = at.getFullYear() - value.getFullYear();
value = value.setFullYear(at.getFullYear());
if (at < value) --age;
return age;
};
// Add adapter for minimum age validator. Wrap in closure
(function ($) {
$.validator.unobtrusive.adapters.addSingleVal("minage", "value");
} (jQuery));
// Add client side minimum age validator
$.validator.methods.minage = function (value, element, params) {
// If no value is specified, don't validate
if (value.length == 0) {
return true;
}
var dob = new Date(Date.parse(value));
if (dob.age(new Date()) < params || dob == 'Invalid Date') {
return false;
}
return true;
};
Credits for the age calculator are due to Dave
The one thing missing here is globalization, but I figured it was out of the question's scope. btw very easy to implement using the jquery Globalize plugin
Hope this helps