Jak przekazać parametr datetime?

86

Jak przekazać daty UTC do Web API?

Przekazywanie 2010-01-01działa dobrze, ale kiedy przekażę datę UTC, taką jak 2014-12-31T22:00:00.000Z(ze składnikiem czasu), otrzymuję odpowiedź HTTP 404. Więc

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

zwraca odpowiedź o błędzie 404, podczas gdy

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

działa w porządku.

Jak więc przekazać daty UTC do Web API - lub przynajmniej określić datę i godzinę?

Nickolodeon
źródło
2
Czy „:” w dacie jest podejrzanym? Spróbuj uciec. http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z
shahkalpesh
2
Ucieczka nie pomaga. Wciąż 404.
Nickolodeon
Czy możesz włączyć debugowanie, aby dowiedzieć się, dlaczego tłumaczenie przekazanego do tej pory ciągu kończy się niepowodzeniem? Chodzi o to, aby dowiedzieć się, jaka metoda jest używana do tłumaczenia daty przekazanej za pomocą adresu URL DateTime- co, jak zakładam, jest typem danych parametru w Twojej metodzie.
shahkalpesh
4
Zrobię to. Metoda oczekuje parametru .NET DateTime. Myślę, że to niedorzeczne, że nie mogę zaliczyć składnika czasu i nie mogę znaleźć dokumentów, jak to zrobić!
Nickolodeon,
2
Po zakończeniu opublikuj swoje rozwiązanie. Może pomóc innym osobom mającym podobny problem. Dzięki.
shahkalpesh

Odpowiedzi:

33

Problem jest dwojaki:

1. Na .trasie

Domyślnie usługi IIS traktują wszystkie identyfikatory URI z kropką jako zasoby statyczne, próbują je zwrócić i całkowicie pominąć dalsze przetwarzanie (przez interfejs API sieci Web). Jest to skonfigurowane w pliku Web.config w sekcji system.webServer.handlers: domyślne uchwyty obsługi path="*.". Nie znajdziesz zbyt wiele dokumentacji na temat dziwnej składni tego pathatrybutu (regex miałby większy sens), ale najwyraźniej oznacza to „wszystko, co nie zawiera kropki” (i dowolny znak z punktu 2 poniżej). Stąd w nazwie słowo „Extensionless” ExtensionlessUrlHandler-Integrated-4.0.

Moim zdaniem możliwych jest wiele rozwiązań w kolejności `` poprawności '':

  • Dodaj nową procedurę obsługi specjalnie dla tras, które muszą zezwalać na kropkę. Pamiętaj, aby dodać go przed wartością domyślną. Aby to zrobić, najpierw usuń domyślną procedurę obsługi i dodaj ją ponownie po swojej.
  • Zmień path="*."atrybut na path="*". Wtedy złapie wszystko. Zwróć uwagę, że od tego momentu Twój internetowy interfejs API nie będzie już interpretował połączeń przychodzących z kropkami jako zasobów statycznych! Jeśli hostujesz zasoby statyczne w swoim interfejsie API sieci Web, nie jest to zalecane!
  • Dodaj do swojego pliku Web.config, aby bezwarunkowo obsługiwać wszystkie żądania: pod <system.webserver>:<modules runAllManagedModulesForAllRequests="true">

2. Na :trasie

Po zmianie powyższego domyślnie pojawia się następujący błąd:

Wykryto potencjalnie niebezpieczną wartość Request.Path z klienta (:).

Możesz zmienić predefiniowane niedozwolone / nieprawidłowe znaki w pliku Web.config. Pod <system.web>dodaj następujący: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. Usunąłem :ze standardowej listy nieprawidłowych znaków.

Łatwiejsze / bezpieczniejsze rozwiązania

Chociaż nie jest to odpowiedź na Twoje pytanie, bezpieczniejszym i łatwiejszym rozwiązaniem byłaby zmiana wniosku tak, aby to wszystko nie było wymagane. Można to zrobić na dwa sposoby:

  1. Przekaż datę jako parametr ciągu zapytania, na przykład ?date=2012-12-31T22:00:00.000Z.
  2. Usuń .000z każdego żądania. Nadal musiałbyś zezwolić :(por. Punkt 2).
Vincent Sels
źródło
Twoje „Łatwiejsze rozwiązanie” w zasadzie zrobiło to za mnie, ponieważ nie potrzebowałem sekund.
Neville
Jesteś
ratownikiem
1
W swoim „łatwiejszym rozwiązaniu”, zamiast pozwalać na :s, myślę, że możesz po prostu użyć %3Azamiast :i powinno być dobrze.
Mayer Spitzer
20

w kontrolerze Product Web API:

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

przetestuj metodę GetProductPeriodOrders w programie Fiddler - Composer:

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

Format daty i godziny:

yyyy-MM-ddTHH:mm:ss

parametr przejścia javascript użyj moment.js

const dateStart = moment(startDate).format('YYYY-MM-DDTHH:mm:ss');
const dateEnd = moment(endDate).format('YYYY-MM-DDTHH:mm:ss');
FreeClimb
źródło
18

Czuję twój ból ... kolejny format daty i czasu ... dokładnie to, czego potrzebujesz!

Korzystając z interfejsu Web Api 2, możesz użyć atrybutów tras do określenia parametrów.

więc dzięki atrybutom w swojej klasie i metodzie możesz zakodować adres URL REST za pomocą tego formatu utc, z którym masz problem (najwyraźniej jego ISO8601, prawdopodobnie przy użyciu 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)

.... ALE, chociaż to działa z jedną datą (startDate), z jakiegoś powodu nie działa, gdy endDate jest w tym formacie ... debugowany przez wiele godzin, jedyną wskazówką jest wyjątek mówiący, że nie działa jak dwukropek ":" (nawet chociaż web.config jest ustawiony za pomocą:

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

Stwórzmy więc inny format daty (wzięty z polyfill dla formatu daty ISO) i dodajmy go do daty JavaScript (dla zwięzłości, konwertuj tylko do minut):

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

    }());
}

Następnie wysyłając daty do metody Web API 2, możesz przekonwertować je z ciągu na datę:

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

więc będzie to adres URL

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

Hanselman podaje tutaj kilka powiązanych informacji:

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

Simon Dowdeswell
źródło
W metodzie WebAPI można mieć parametry datetime jako wartości null DateTime (DateTime? StartDateString, DateTime? EndDateDtring)
DotNet Fan
Dzięki za wzmiankę o toISOString - to mnie uratowało. Moja usługa WCF RESTful działa dobrze z dwiema datami w identyfikatorze URI, więc nie potrzebujesz złożonych konwersji dat. Może to dziwactwo, ponieważ interfejs API sieci Web nie lubi dwukropków pomimo ustawienia konfiguracji ... dziwne jednak.
Neville
@Simon, endDatezadziałałoby, gdyby adres URL żądania zawierał końcowy ukośnik. Niestety nie mogę sobie przypomnieć, gdzie znalazłem te informacje, ani nie wiem, jak to obejść.
Pooven,
Użytkownicy zegara 24h, którzy chcą z tego skorzystać, powinni zmienić hh na HH w formacie daty.
sidła
To jest poprawna odpowiedź. StackOverflow, PRZESTAŃ ODBIERAĆ ODPOWIEDZI!
mghaoui,
8

Jako podobną alternatywę dla odpowiedzi sk, jestem w stanie przekazać datę sformatowaną przez Date.prototype.toISOString()w ciągu zapytania. Jest to standardowy format ISO 8601 i jest akceptowany przez kontrolery .Net Web API bez dodatkowej konfiguracji trasy lub akcji.

na przykład

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"
Bondolin
źródło
1
czy to jest czy możesz podać przykład, gdzie to działa? Zrobiłem to samo obejście i to nie działa.
anatol
@anatol jaki wynik uzyskasz? Podany kod jest działającym przykładem z warunkiem wstępnym, którym dateObjectjest zainicjowany Dateobiekt.
Bondolin
Prawdopodobnie powinno to zostać nieco przegłosowane. To rozwiązało mój problem, zmieniając UTC na ISO. Simples
Regianni
1
@Regianni cieszę się, że pomogło :-)
Bondolin
To zadziałało dla mnie, używając stackoverflow.com/a/115034/1302730, aby uzyskać datę w formacie ISO
BugLover
7

To jest rozwiązanie i wzór możliwych rozwiązań. Użyj Moment.js w swoim kliencie, aby sformatować daty i przekonwertować na czas unix.

 $scope.startDate.unix()

Ustaw długie parametry trasy.

[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!
  }

Utwórz metodę rozszerzenia dla czasu uniksowego. Metoda Unix DateTime

Kentonbmax
źródło
4

Kiedyś było to bolesne zadanie, ale teraz możemy użyć metody toUTCString ():

Przykład:

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

Umieść poniżej w żądaniu postu Ajax

data: {
    Start: new Date().toUTCString(),
    End: new Date().toUTCString()
},
sk
źródło
3

W rzeczywistości jawne określanie parametrów jako? Date = 'fulldatetime' działało jak urok. Na razie to będzie rozwiązanie: nie używaj przecinków, ale użyj starego podejścia GET.

Nickolodeon
źródło
0

Ponieważ mam kodowanie systemu operacyjnego ISO-8859-1, format daty „dd.MM.yyyy HH: mm: sss” nie został rozpoznany, co zadziałało, to użycie ciągu InvariantCulture.

string url = "GetData?DagsPr=" + DagsProfs.ToString(CultureInfo.InvariantCulture)
KnuturO
źródło
0

Patrząc na swój kod, zakładam, że nie martwisz się o „Time” obiektu DateTime. Jeśli tak, możesz przekazać datę, miesiąc i rok jako parametry całkowite. Zobacz poniższy kod. To jest działający przykład z mojego obecnego projektu.

Zaletą jest; ta metoda pomaga mi uniknąć problemów z formatem DateTime i niezgodności kultur.

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

Jeśli chcesz zobaczyć również front-end, przeczytaj poniższy kod. Niestety, jest to napisane w Angular. W ten sposób zwykle przekazuję DateTime jako parametr zapytania w żądaniach Angular GET.

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;
}
Kushan Randima
źródło
0

Jednym z możliwych rozwiązań jest użycie Kleszczy:

public long Ticks {get; }

Następnie w metodzie kontrolera:

public DateTime (długie tiki);

nikolai.serdiuk
źródło