Come passare un parametro datetime?


91

Come passare le date UTC all'API Web?

Il passaggio 2010-01-01funziona bene, ma quando passo una data UTC come 2014-12-31T22:00:00.000Z(con un componente orario), ottengo una risposta HTTP 404. Così

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

restituisce una risposta di errore 404, mentre

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

funziona bene.

Come passare le date UTC all'API Web, o almeno specificare data e ora?


2
":" Nella data è un sospetto? Prova a scappare. http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z
shahkalpesh

2
Fuggire non aiuta. Ancora 404.
Nickolodeon

Puoi abilitare il debug in modo da capire perché la traduzione dalla stringa passata alla data non riesce? L'idea è di capire quale metodo viene utilizzato per tradurre la data che hai passato usando l'URL a DateTime- che presumo sia il tipo di dati del parametro sul tuo metodo.
shahkalpesh

4
Lo farò. Il metodo prevede il parametro .NET DateTime. Penso che sia ridicolo che io non riesca a passare la componente del tempo e non riesca a trovare documenti su come farlo!
Nickolodeon

2
Pubblica la tua soluzione quando hai finito. Può aiutare altre persone che hanno problemi simili. Grazie.
shahkalpesh

Risposte:


34

Il problema è duplice:

1. Il .percorso

Per impostazione predefinita, IIS tratta tutti gli URI con un punto in essi come risorsa statica, tenta di restituirlo e di saltare l'ulteriore elaborazione (dall'API Web) del tutto. Questo è configurato nel tuo Web.config nella sezione system.webServer.handlers: il gestore predefinito gestisce path="*.". Non troverai molta documentazione riguardo la strana sintassi in questo pathattributo (l'espressione regolare avrebbe avuto più senso), ma ciò che questo apparentemente significa è "tutto ciò che non contiene un punto" (e qualsiasi carattere dal punto 2 sotto). Da qui la parola "Extensionless" nel nome ExtensionlessUrlHandler-Integrated-4.0.

Sono possibili molteplici soluzioni, secondo me in ordine di 'correttezza':

  • Aggiungi un nuovo gestore specifico per le rotte che devono consentire un punto. Assicurati di aggiungerlo prima del valore predefinito. Per fare ciò, assicurati di rimuovere prima il gestore predefinito e di aggiungerlo nuovamente dopo il tuo.
  • Cambia l' path="*."attributo in path="*". Quindi prenderà tutto. Nota che da quel momento in poi, la tua API web non interpreterà più le chiamate in arrivo con punti come risorse statiche! Se stai ospitando risorse statiche sulla tua API web, questo non è consigliato!
  • Aggiungi quanto segue al tuo Web.config per gestire incondizionatamente tutte le richieste: sotto <system.webserver>:<modules runAllManagedModulesForAllRequests="true">

2. Il :nel percorso

Dopo aver modificato quanto sopra, per impostazione predefinita, riceverai il seguente errore:

Un valore Request.Path potenzialmente pericoloso è stato rilevato dal client (:).

È possibile modificare i caratteri predefiniti non consentiti / non validi nel file Web.config. Sotto <system.web>, aggiungere la seguente: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. Ho rimosso :dall'elenco standard di caratteri non validi.

Soluzioni più facili / sicure

Sebbene non sia una risposta alla tua domanda, una soluzione più sicura e più semplice sarebbe quella di modificare la richiesta in modo che tutto ciò non sia richiesto. Questo può essere fatto in due modi:

  1. Passa la data come parametro della stringa di query, ad esempio ?date=2012-12-31T22:00:00.000Z.
  2. Elimina .000ogni richiesta. Dovresti comunque consentire :'s (cfr punto 2).

La tua "Soluzione più semplice" fondamentalmente lo ha fatto per me perché non avevo bisogno di secondi.
Neville

Sei un
salvavita

1
Nella tua "Soluzione più semplice", invece di consentire :s, penso che tu possa semplicemente usare %3Aal posto di :e dovrebbe andare bene.
Mayer Spitzer

22

nel controller dell'API Web del prodotto:

[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();
        }
    }
}

prova il metodo GetProductPeriodOrders in Fiddler - Compositore:

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');

18

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


In WebAPI method you can have datetime parameters as nullable DateTime (DateTime? startDateString, DateTime? endDateDtring)
DotNet Fan

Thanks for the mention of toISOString - that saved me. My RESTful WCF Service works fine with two dates in the URI, so didn't need your complex date conversions. Maybe its a quirk with Web API not liking the colons despite the config setting...odd though.
Neville

@Simon, the endDate would work if the request Url included a trailing forward slash. Unfortunately I can't recall where I came across this information, nor do I know of a way around this.
Pooven

24h clock users who want to use this should change the hh to HH in the date format.
snaits

1
This is the correct answer. StackOverflow, STOP DOWNVOTING ANSWERS!
mghaoui

9

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"

1
is it? could you provide any example where this does work? I did the same workaround and it doesn't work.
anatol

@anatol what result do you get? The provided code is a working example, with the pre-condition that dateObject is an initialized Date object.
Bondolin

This should probably be voted up a bit. This solved my problem by changing UTC to ISO. Simples
Regianni

1
@Regianni glad it helped :-)
Bondolin

This worked for me using stackoverflow.com/a/115034/1302730 to get the date in ISO format
BugLover

7

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


4

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()
},

3

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.


0

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)

0

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;
}

0

One possible solution is to use Ticks:

public long Ticks { get; }

Then in the controller's method:

public DateTime(long ticks);

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.