Nickolodeon
Nickolodeon

Reputation: 2956

How to pass a datetime parameter?

How to pass UTC dates to Web API?

Passing 2010-01-01 works fine, but when I pass a UTC date such as 2014-12-31T22:00:00.000Z (with a time component), I get a HTTP 404 response. So

http://domain/api/controller/action/2012-12-31T22:00:00.000Z

yields a 404 error response, while

http://domain/api/controller/action/2012-12-31

works fine.

How to pass UTC dates to Web API then - or at least specify date and time?

Upvotes: 125

Views: 318939

Answers (13)

nikolai.serdiuk
nikolai.serdiuk

Reputation: 762

One possible solution is to use Ticks:

public long Ticks { get; }

Then in the controller's method:

public DateTime(long ticks);

Upvotes: 3

Vincent Sels
Vincent Sels

Reputation: 2889

The problem is twofold:

1. The . in the route

By default, IIS treats all URI's with a dot in them as static resource, tries to return it and skip further processing (by Web API) altogether. This is configured in your Web.config in the section system.webServer.handlers: the default handler handles path="*.". You won't find much documentation regarding the strange syntax in this path attribute (regex would have made more sense), but what this apparently means is "anything that doesn't contain a dot" (and any character from point 2 below). Hence the 'Extensionless' in the name ExtensionlessUrlHandler-Integrated-4.0.

Multiple solutions are possible, in my opinion in the order of 'correctness':

  • Add a new handler specifically for the routes that must allow a dot. Be sure to add it before the default. To do this, make sure you remove the default handler first, and add it back after yours.
  • Change the path="*." attribute to path="*". It will then catch everything. Note that from then on, your web api will no longer interpret incoming calls with dots as static resources! If you are hosting static resources on your web api, this is therefor not advised!
  • Add the following to your Web.config to unconditionally handle all requests: under <system.webserver>: <modules runAllManagedModulesForAllRequests="true">

2. The : in the route

After you've changed the above, by default, you'd get the following error:

A potentially dangerous Request.Path value was detected from the client (:).

You can change the predefined disallowed/invalid characters in your Web.config. Under <system.web>, add the following: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. I've removed the : from the standard list of invalid characters.

Easier/safer solutions

Although not an answer to your question, a safer and easier solution would be to change the request so that all this is not required. This can be done in two ways:

  1. Pass the date as a query string parameter, like ?date=2012-12-31T22:00:00.000Z.
  2. Strip the .000 from every request, and encode the url, so replace all :'s with %3A, e.g. by using HttpUtility.UrlEncode().

Upvotes: 55

Lee Smith
Lee Smith

Reputation: 6747

For external APIs (where you do not know what type of client will call your service), Unix Time should be used both on the input parameters and outputted date fields. https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimeseconds?view=net-6.0

.Net provides ToUnixtimeSeconds and FromUnixtimeSeconds to easily convert to DateTime or DateTimeOff

Unix Time should be preferred over ISO formats because it is just a integer and can be passed in the URL string without encoding.
The 'Ticks' property is similar to Unix time but (I believe) should only be use between a .net client and server.

Most well know APIs will use Unix Time, for example see Stripe's API: https://stripe.com/docs/api

The obvious downsides of using Unix time are:

  • They are not human readable
  • They cannot be created by humans - making it difficult to call the API without code

Upvotes: 0

Use binary format.

to send the info in url use dateTimeVar.ToBinary() it will be something like

http://domain/api/controller/action/637774955400000000

when you reciebe the data will get like Long variable and use the static function of DateTime Class to transform to DateTime type again.

DateTime MyDateTime = DateTime.FromBinary(BinaryDateTime);

Cheers

Upvotes: -1

cfairchi
cfairchi

Reputation: 1

Passing the date as a string and then parsing it worked for me. Probably want to add try catch on the parse, but this is the basic code.

[HttpGet("name={name}/date={date}", Name = "GetByNameAndDate")]
public IActionResult GetByNameAndDate(string name, string date) {
    DateTimeOffset dto = DateTimeOffset.Parse(date);
}

Then the request can look like this

https://localhost/api/Contoller/name=test/date=2022-02-18T13:45:37.000Z

Upvotes: 0

Kushan Randima
Kushan Randima

Reputation: 2282

By looking at your code, I assume you do not have a concern about the 'Time' of the DateTime object. If so, you can pass the date, month and the year as integer parameters. Please see the following code. This is a working example from my current project.

The advantage is; this method helps me to avoid DateTime format issues and culture incompatibilities.

    /// <summary>
    /// Get Arrivals Report Seven Day Forecast
    /// </summary>
    /// <param name="day"></param>
    /// <param name="month"></param>
    /// <param name="year"></param>
    /// <returns></returns>
    [HttpGet("arrivalreportsevendayforecast/{day:int}/{month:int}/{year:int}")]
    public async Task<ActionResult<List<ArrivalsReportSevenDayForecastModel>>> GetArrivalsReportSevenDayForecast(int day, int month, int year)
    {
        DateTime selectedDate = new DateTime(year, month, day);
        IList<ArrivalsReportSevenDayForecastModel> arrivingStudents = await _applicationService.Value.GetArrivalsReportSevenDayForecast(selectedDate);
        return Ok(arrivingStudents);
    }

If you are keen to see the front-end as well, feel free to read the code below. Unfortunately, that is written in Angular. This is how I normally pass a DateTime as a query parameter in Angular GET requests.

public getArrivalsReportSevenDayForecast(selectedDate1 : Date): Observable<ArrivalsReportSevenDayForecastModel[]> {
const params = new HttpParams();
const day = selectedDate1.getDate();
const month = selectedDate1.getMonth() + 1
const year = selectedDate1.getFullYear();

const data = this.svcHttp.get<ArrivalsReportSevenDayForecastModel[]>(this.routePrefix +
  `/arrivalreportsevendayforecast/${day}/${month}/${year}`, { params: params }).pipe(
  map<ArrivalsReportSevenDayForecastModel[], ArrivalsReportSevenDayForecastModel[]>(arrivingList => {
    // do mapping here if needed       
    return arrivingList;
  }),
  catchError((err) => this.svcError.handleError(err)));

return data;
}

Upvotes: 0

KnuturO
KnuturO

Reputation: 1635

Since I have encoding ISO-8859-1 operating system the date format "dd.MM.yyyy HH:mm:sss" was not recognised what did work was to use InvariantCulture string.

string url = "GetData?DagsPr=" + DagsProfs.ToString(CultureInfo.InvariantCulture)

Upvotes: 1

Bondolin
Bondolin

Reputation: 3121

As a similar alternative to s k's answer, I am able to pass a date formatted by Date.prototype.toISOString() in the query string. This is the standard ISO 8601 format, and it is accepted by .Net Web API controllers without any additional configuration of the route or action.

e.g.

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"

Upvotes: 16

FreeClimb
FreeClimb

Reputation: 706

in your Product Web API controller:

[RoutePrefix("api/product")]
public class ProductController : ApiController
{
    private readonly IProductRepository _repository;
    public ProductController(IProductRepository repository)
    {
        this._repository = repository;
    }

    [HttpGet, Route("orders")]
    public async Task<IHttpActionResult> GetProductPeriodOrders(string productCode, DateTime dateStart, DateTime dateEnd)
    {
        try
        {
            IList<Order> orders = await _repository.GetPeriodOrdersAsync(productCode, dateStart.ToUniversalTime(), dateEnd.ToUniversalTime());
            return Ok(orders);
        }
        catch(Exception ex)
        {
            return NotFound();
        }
    }
}

test GetProductPeriodOrders method in Fiddler - Composer:

http://localhost:46017/api/product/orders?productCode=100&dateStart=2016-12-01T00:00:00&dateEnd=2016-12-31T23:59:59

DateTime format:

yyyy-MM-ddTHH:mm:ss

javascript pass parameter use moment.js

const dateStart = moment(startDate).format('YYYY-MM-DDTHH:mm:ss');
const dateEnd = moment(endDate).format('YYYY-MM-DDTHH:mm:ss');

Upvotes: 36

s k
s k

Reputation: 5192

It used to be a painful task, but now we can use toUTCString():

Example:

[HttpPost]
public ActionResult Query(DateTime Start, DateTime End)

Put the below into Ajax post request

data: {
    Start: new Date().toUTCString(),
    End: new Date().toUTCString()
},

Upvotes: 4

Kentonbmax
Kentonbmax

Reputation: 958

This is a solution and a model for possible solutions. Use Moment.js in your client to format dates, convert to unix time.

 $scope.startDate.unix()

Setup your route parameters to be long.

[Route("{startDate:long?}")]
public async Task<object[]> Get(long? startDate)
{
    DateTime? sDate = new DateTime();

        if (startDate != null)
        {
            sDate = new DateTime().FromUnixTime(startDate.Value); 
        }
        else
        {
            sDate = null;
        }
         ... your code here!
  }

Create an extension method for Unix time. Unix DateTime Method

Upvotes: 7

Simon Dowdeswell
Simon Dowdeswell

Reputation: 1081

I feel your pain ... yet another date time format... just what you needed!

Using Web Api 2 you can use route attributes to specify parameters.

so with attributes on your class and your method you can code up a REST URL using this utc format you are having trouble with (apparently its ISO8601, presumably arrived at using startDate.toISOString())

[Route(@"daterange/{startDate:regex(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$)}/{endDate:regex(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$)}")]
    [HttpGet]
    public IEnumerable<MyRecordType> GetByDateRange(DateTime startDate, DateTime endDate)

.... BUT, although this works with one date (startDate), for some reason it doesnt work when the endDate is in this format ... debugged for hours, only clue is exception says it doesnt like colon ":" (even though web.config is set with :

<system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" requestPathInvalidCharacters="" />
</system.web>

So, lets make another date format (taken from the polyfill for the ISO date format) and add it to the Javascript date (for brevity, only convert up to minutes):

if (!Date.prototype.toUTCDateTimeDigits) {
    (function () {

        function pad(number) {
            if (number < 10) {
                return '0' + number;
            }
            return number;
        }

        Date.prototype.toUTCDateTimeDigits = function () {
            return this.getUTCFullYear() +
              pad(this.getUTCMonth() + 1) +
              pad(this.getUTCDate()) +
              'T' +
              pad(this.getUTCHours()) +
              pad(this.getUTCMinutes()) +
              'Z';
        };

    }());
}

Then when you send the dates to the Web API 2 method, you can convert them from string to date:

[RoutePrefix("api/myrecordtype")]
public class MyRecordTypeController : ApiController
{


    [Route(@"daterange/{startDateString}/{endDateString}")]
    [HttpGet]
    public IEnumerable<MyRecordType> GetByDateRange([FromUri]string startDateString, [FromUri]string endDateString)
    {
        var startDate = BuildDateTimeFromYAFormat(startDateString);
        var endDate = BuildDateTimeFromYAFormat(endDateString);
    ...
    }

    /// <summary>
    /// Convert a UTC Date String of format yyyyMMddThhmmZ into a Local Date
    /// </summary>
    /// <param name="dateString"></param>
    /// <returns></returns>
    private DateTime BuildDateTimeFromYAFormat(string dateString)
    {
        Regex r = new Regex(@"^\d{4}\d{2}\d{2}T\d{2}\d{2}Z$");
        if (!r.IsMatch(dateString))
        {
            throw new FormatException(
                string.Format("{0} is not the correct format. Should be yyyyMMddThhmmZ", dateString)); 
        }

        DateTime dt = DateTime.ParseExact(dateString, "yyyyMMddThhmmZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);

        return dt;
    }

so the url would be

http://domain/api/myrecordtype/daterange/20140302T0003Z/20140302T1603Z

Hanselman gives some related info here:

http://www.hanselman.com/blog/OnTheNightmareThatIsJSONDatesPlusJSONNETAndASPNETWebAPI.aspx

Upvotes: 19

Nickolodeon
Nickolodeon

Reputation: 2956

As a matter of fact, specifying parameters explicitly as ?date='fulldatetime' worked like a charm. So this will be a solution for now: don't use commas, but use old GET approach.

Upvotes: 3

Related Questions