Attribute Routing with Constraint - c#

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)
{
}

Related

Get DateTime from FileName

I have a file named test-2000_01_02-10_12_14.xml.
How do I only get the date from the file?
I was able to get the date if the file has this name: 2000_01_02-10_12_14
with this (b is a StorageFile):
DateTime dateVal;
bool parsed = DateTime.TryParseExact(b.DisplayName,
"yyyy_MM_dd-H_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out dateVal);
I then tried to change yyyy_MM_dd-H_mm_ss to something like this *-yyyy_MM-dd-H-mm_ss but it does not seem to be the solution
There are a boatload of ways to do this, it really rather depends on how regular the naming of your files is - is there always some junk text followed by a hyped, then the year?
Post up another 10 different examples if you want more tailored advice. Here's a way for the one you've posted:
DateTime.TryParseExact(
Path.GetFileNameWithoutExtension(b.DisplayName.Substring(b.DisplayName.IndexOf('-')+1)),
"yyyy_MM_dd-H_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out dateVal
);
This uses Substring with only one argument (no length) to remove everything after the first hyphen up to the end of the string, and GetFileNameWithoutExtension to remove the .xml - this effectively turns anythinghere-2000_01_01-00_00_00.xml into 2000_01_01-00_00_00 ready for parsing
I could also have gone for a .Remove("last index of period") type thing but it does get a bit messy because you have to subtract the start Index of the hyphen etc
MJWill's comment about splitting on hyphen is also a good one - you could split then take the [1] and [2] indexes and join then back together for parsing..
Lastly don't forget that the file itself might have a created date that is already a good candidate for the date of creation rather than the filename (which might be mangled by humans) so long as it hasn't been transmitted somewhere and re-saved. Take a look at the FileInfo.CreationTime property for that - https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo?view=netframework-4.8
First, we have to extract (match) the datetime part from a file name:
using System.Text.RegularExpressions;
...
// Aggravated task: dots and minuses within file's name
string source = #"bla-bla-bla-test.me-2000_01_02-10_12_14.xml";
string datetime = Regex.Match(
Path.GetFileNameWithoutExtension(source),
"[0-9]{4}_[0-9]{2}_[0-9]{2}-[0-9]{2}_[0-9]{2}_[0-9]{2}$").Value;
Then we can parse it
if (DateTime.TryParseExact(
datetime,
"yyyy_MM_dd-H_m_s",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out DateTime result) {
// result is the parsed date
}
else {
// File doesn't contain valid date and time
}
I would suggest you to use regular expression assuming that your file name will be always following the same format you can do something like this:
var pattern = #"\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2}";
var fileName = "test-2000_01_02-10_12_14.xml";
var match = new Regex(pattern);
var result = match.Match(fileName);
if (result.Success)
{
DateTime.TryParseExact(result.Value,
"yyyy_MM_dd-HH_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out DateTime dateVal);
}

Use DateTime.ToFileTime and FromFileTime through file name string

I want to save to a specific space a single file at several states. So i thought about stamping it with the time of creation to tell one from another.
I have created a dummy program that goes through exactly the same procedure my full program does but concentrated.
Here is the code:
DateTime first = DateTime.Now;
long ft = first.ToFileTime();
string time = "" + ft;
long timeLong = Convert.ToInt64(time);
DateTime normalDate = DateTime.FromFileTime(timeLong);
string normalName = ""+ normalDate;
DateTime thisDate = DateTime.Parse(normalName);
long fit = thisDate.ToFileTime();
So it goes through those steps:
Create time and convert to long for file.
Save it as string inside the files name(i pass it like that but in "" is the files name)
Then i retrieve it, i cast it into long, to create a DateTime format readable for the human with FromFileTime.Then to string fro the user to read it in a comboBox.
Then the user selects and it becomes DateTime , to get into long through tge TiFileTime ( at this point i have the problem as it appears to loose everything smaller than seconds)
I want to cast it back into long at the end to be able to run through the files again and find the one that matches with the one that the user choose.
here is some output values:
Edit : As you can see form the results above. I miss something . The initial and the final values are not the same.So i cant find the same file again.
The Parse() method don't have all the information that the DateTime structure holds (you can find its members here).
string normalName = ""+ normalDate;
DateTime thisDate = DateTime.Parse(normalName);
Will not return the exact value (including Ticks) as the original one.
You should do it as follows, using Ticks:
string normalName = "" + normalDate.Ticks;
DateTime thisDate = new DateTime(long.Parse(normalName));
long fit = thisDate.ToFileTime();

Trying to check if int value is indeed an int

I'm trying to check a passed variable to ensure that it is indeed of type Int. However, I keep having an error appear that tells me that the variable "year" cannot be converted from an int to a string. I'm stuck because I'm not trying to convert it over so I'm confused as to what I'm missing.
[HttpGet("[action]")]
[Authorize]
public ListResult ProblemsYTD(int year = "")
{
var sql = "SELECT DatePart(yyyy, CLL.Call_Log_Date) as [ProblemsYear], CLL.Service as [Service], Sum((DATEDIFF(dd, CLL.Call_Log_Date, GetDate()))) as [DaysOpen] " +
"FROM VMWareSM_Test.dbo.RV_CALL as CLL " +
"Where CLL.IPK_Stream_Ref = '19' And DatePart(yyyy, CLL.Call_Log_Date)";
int myInt;
if (!int.TryParse(year, out myInt))
{
year = "%" + year + "%";
sql += " = #year";
}
sql += " Group by CLL.Service, DatePart(yyyy, CLL.Call_Log_Date) "+
"Order by CLL.Service DESC; ";
SqlParameter[] sqlParams =
{
new SqlParameter
{
ParameterName = "#year",
Value = year,
DbType = DbType.Int32,
Direction = ParameterDirection.Input
}
};
Let's start from your use of the variable year. It is used as part of a where condition where also a DatePart function call is involved. Now DataPart returns an integer so you need an integer for your checks, not a string.
At this point your declaration of the method should be simple an integer without a default value or you could use a default value of zero.
You cannot use an empty string as default value for an integer. C# doesn't allow this free implicit conversions between types like an Option Strictless VB.NET
So the call should be simply
public ListResult ProblemsYTD(int year = 0)
at this point all the code that checks if the caller has passed an integer is useless because the caller cannot pass any other kind of type or value. Just integers or types that can be cast to an integer without loosing information (byte, short but not long) You declare the method to receive an integer and the compiler blocks any different type with a compilation error.
However you could add a check for a reasonable value for your year variable.
For example you could limit the upper and lower values with something like this
if (year >= 2000 && year <= DateTime.Today.Year)
{
sql += " = #year";
}
Note that you cannot concatenate a "%" symbol to an integer for the same reason that you cannot assign an empty string to an integer.
Your function should be declared like this
public ListResult ProblemsYTD(string year = "")
{
...
}
also year = "%" + year + "%";
make no since the % is used in LIKE statements
should be something like this :
if (int.TryParse(year, out myInt))
{
sql += " = #" + year;
}
else
{
throw new Exception("Year is not in correct format");
}
This line looks fishy:
public ListResult ProblemsYTD(int year = "")
You're providing an empty string ("") as the default value for year. I think you mean to have the parameter be a string when it is passed in.
public ListResult ProblemsYTD(string year = "")
I'm trying to check a passed variable to ensure that it is indeed of type Int.
Most likely you just need to use a Route Constraint
Example Route:
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"}
);
Controller:
public class ProductController : Controller
{
public ActionResult Details(int productId)
{
return View();
}
}
with the following producing an error:
/Product/blah
/Product/apple
You can add a route constraint to only route to the controller/action if it is an int:
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"},
new {productId = #"\d+" }
);
This means that your controller is not in charge of type checking (it probably shouldn't anyway) and you can create a another route to catch non-ints and display a different view accordingly.
int year = ""
You cannot set an integer variable to a string value.

Send a null value through AJAX - ASP.NET MVC

I have a method that would take 3 parameters, but a dillema comes by. One of the parameters has to be located on the 3rd place, but sometimes the 1st and 2nd paramaters can take a null value. So in order for the first parameter to Not take in the 3rd value, but the 3rd parameter, I have to send in 1, or 2 null values.
To make this more clear, I have a method:
public ActionResult TurnoverPerItem(string startDate = null, string endDate = null,
int extra = 0) {
//...
}
and a JS/JQuery function that sends a request:
$.get(url, function (data) {
//....
}
but, now, the variable url can be one of the following (with all of the parameters (startDate, endDate and extra)):
path/to/action/15-09-2015/23-09-2015/0
or with only a startDate and extra
path/to/action/15-09-2015/0
or even just extra
path/to/action/0
but in the second example the parameter endDate would take in the value od 0 instead of extra, while extra would be null and in the third example, the parameter startDate would take in the extra value, while the others would be null.
My question would be, whether it is possible to send in a null value to the controller? ie. something like (one of the methods that I tried)
path/to/action/null/null/1
the web application is pretty robust, so there is a valid reason why the parameter extra has to take in 3rd place, but I really hope I won't have to go into details about that.
EDIT: the rout config:
routes.MapRoute(
name: "Default-Date",
url: "{controller}/{action}/{startDate}/{endDate}/{extra}",
defaults: new { controller = "reports", startDate = UrlParameter.Optional, endDate = UrlParameter.Optional, extra = UrlParameter.Optional }
);
I have a very simple one liner answer for your question.
Just try to create separate routing for each URL pattern.
Just try above solution, this work around will really solve your issue.
I'm not saying this is a perfect solution, but it did solve the problem for me. So from what I've got through research, you can't pass a null value through a GET request.
What I can do, though, is send a string or a number. startDate and endDate are both string types and are usually formatted like:
12-09-1983 (dd-MM-yyyy) | or | 12-09-1983-13-45 (dd-MM-yyyy-hh-mm)
While creating separate Routes is a good idea (and I'm thankful to Chetan Sharma and DPac for answering), it isn't an option due to some backend reasons that I won't go into.
What I did is simply fill in the blank spots. So if I only send in extra, it'll look like: link/to/ajax/null/null/0
If I only send in startDate: link/to/ajax/12-12-2012/null/0
Or if I'm sending a full value, normally through: link/to/ajax/12-12-2012/09-03/2015/0
so ajax can also send in a string = "null", the only step needed would be to test out the string and treat it as a normal null value.
public ActionResult TurnoverPerItem(string startDate = null, string endDate = null,
int extra = 0) {
startDate = (startDate != null && startDate.Equals("null")) ? null : startDate;
endDate = (endDate != null && endDate.Equals("null")) ? null : endDate;
//...
}
and afterwards the code will continue running normally.
More info : <Click here>

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

Categories