Как передать параметр datetime?

86

Как передать даты UTC в веб-API?

Передача 2010-01-01работает нормально, но когда я передаю дату в формате UTC, например 2014-12-31T22:00:00.000Z(с компонентом времени), я получаю ответ HTTP 404. Так

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

дает ответ об ошибке 404, а

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

работает отлично.

Как тогда передать даты UTC в веб-API - или хотя бы указать дату и время?

Николодеон
источник
2
Находится ли ":" на свидании у подозреваемого? Попробуйте убежать от него. http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z
shahkalpesh
2
Бегство не помогает. Still 404.
Nickolodeon
Можете ли вы включить отладку, чтобы понять, почему не удалось выполнить перевод переданной строки на дату? Идея состоит в том, чтобы выяснить, какой метод используется для перевода даты, которую вы передали с помощью URL-адреса, DateTime- который, как я предполагаю, является типом данных paramater вашего метода.
shahkalpesh
4
Я это сделаю. Метод ожидает параметр .NET DateTime. Я думаю, это смешно, что я не могу передать компонент времени и не могу найти документацию о том, как это сделать!
Nickolodeon
2
Опубликуйте свое решение, когда закончите. Это может помочь другим людям, столкнувшимся с подобной проблемой. Спасибо.
shahkalpesh

Ответы:

33

Проблема двоякая:

1. На .маршруте

По умолчанию IIS обрабатывает все URI с точкой в ​​них как статический ресурс, пытается вернуть его и вообще пропустить дальнейшую обработку (через веб-API). Это настраивается в вашем Web.config в разделе system.webServer.handlers: обработчик по умолчанию обрабатывает path="*.". Вы не найдете много документации относительно странного синтаксиса этого pathатрибута (регулярное выражение имело бы больше смысла), но, по-видимому, это означает «все, что не содержит точки» (и любой символ из пункта 2 ниже). Отсюда и слово «без расширения» в названии ExtensionlessUrlHandler-Integrated-4.0.

На мой взгляд, в порядке «правильности» возможно несколько решений:

  • Добавьте новый обработчик специально для маршрутов, которые должны допускать точку. Обязательно добавьте его перед значением по умолчанию. Для этого сначала удалите обработчик по умолчанию и снова добавьте его после своего.
  • Измените path="*."атрибут на path="*". Тогда он все поймает. Обратите внимание, что с этого момента ваш веб-API больше не будет интерпретировать входящие вызовы с точками как статические ресурсы! Если вы размещаете статические ресурсы на своем веб-API, это не рекомендуется!
  • Добавьте в свой Web.config следующее, чтобы безоговорочно обрабатывать все запросы: в <system.webserver>:<modules runAllManagedModulesForAllRequests="true">

2. На :маршруте

После того, как вы изменили вышеуказанное, по умолчанию вы получите следующую ошибку:

Обнаружено потенциально опасное значение Request.Path от клиента (:).

Вы можете изменить предопределенные запрещенные / недопустимые символы в своем Web.config. Под <system.web>, добавить следующее: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. Я удалил :из стандартного списка недопустимых символов.

Более простые / безопасные решения

Хотя это не ответ на ваш вопрос, более безопасным и простым решением было бы изменить запрос, чтобы все это не требовалось. Это можно сделать двумя способами:

  1. Передайте дату как параметр строки запроса, например ?date=2012-12-31T22:00:00.000Z.
  2. Избавьтесь .000от каждого запроса. Вам все равно нужно разрешить :(см. Пункт 2).
Винсент Селс
источник
Ваше «Более простое решение» в основном сделало это за меня, поскольку мне не понадобились секунды.
Невилл,
Вы
спасаете
1
В вашем «Простом решении», вместо того, чтобы разрешать :s, я думаю, вы можете просто использовать %3Aвместо, :и это должно быть хорошо.
Mayer Spitzer
20

в вашем контроллере 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();
        }
    }
}

проверить метод GetProductPeriodOrders в Fiddler - Composer:

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

Формат даты и времени:

yyyy-MM-ddTHH:mm:ss

Параметр передачи javascript использовать moment.js

const dateStart = moment(startDate).format('YYYY-MM-DDTHH:mm:ss');
const dateEnd = moment(endDate).format('YYYY-MM-DDTHH:mm:ss');
FreeClimb
источник
18

Я чувствую вашу боль ... еще один формат даты и времени ... именно то, что вам нужно!

Используя Web Api 2, вы можете использовать атрибуты маршрута для указания параметров.

поэтому с атрибутами вашего класса и вашего метода вы можете закодировать URL-адрес REST, используя этот формат utc, с которым у вас возникли проблемы (по-видимому, его ISO8601, предположительно, был получен с помощью 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)

.... НО, хотя это работает с одной датой (startDate), по какой-то причине это не работает, когда endDate находится в этом формате ... отлаживается часами, только подсказка - исключение говорит, что ему не нравится двоеточие ":" (даже хотя web.config установлен с помощью:

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

Итак, давайте создадим другой формат даты (взятый из полифила для формата даты ISO) и добавим его к дате Javascript (для краткости, конвертируем только с точностью до минут):

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

    }());
}

Затем, когда вы отправляете даты в метод Web API 2, вы можете преобразовать их из строки в дату:

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

поэтому URL-адрес будет

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

Гансельман дает некоторую связанную информацию здесь:

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

Саймон Доудесвелл
источник
В методе WebAPI вы можете иметь параметры datetime как DateTime, допускающие значение NULL (DateTime? StartDateString, DateTime? EndDateDtring)
DotNet Fan
Спасибо за упоминание toISOString - это меня спасло. Моя служба RESTful WCF отлично работает с двумя датами в URI, поэтому вам не нужны сложные преобразования дат. Возможно, это причуда с веб-API, которому не нравятся двоеточия, несмотря на настройку конфигурации ... хотя и странно.
Невилл,
@Simon, endDateбудет работать, если URL-адрес запроса включал косую черту в конце. К сожалению, я не могу вспомнить, где я нашел эту информацию, и не знаю способа обойти это.
Pooven
Пользователи 24-часового формата, которые хотят использовать это, должны изменить hh на HH в формате даты.
snaits 07
Это правильный ответ. StackOverflow, НЕ ГОЛОСОВАТЬ ОТВЕТЫ!
mghaoui
8

В качестве альтернативы ответу sk я могу передать дату, отформатированную Date.prototype.toISOString()в строке запроса. Это стандартный формат ISO 8601, который принимается контроллерами .Net Web API без какой-либо дополнительной настройки маршрута или действия.

например

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"
Бондолин
источник
1
это? не могли бы вы привести какой-нибудь пример, где это работает? Я сделал то же самое обходное решение, но оно не работает.
анатол 04
@anatol какой результат вы получите? Предоставленный код представляет собой рабочий пример с предварительным условием dateObject- инициализированный Dateобъект.
Бондолин
Вероятно, за это следует проголосовать. Это решило мою проблему, изменив UTC на ISO. Simples
Regianni
1
@Regianni рад, что это помогло :-)
Бондолин
Это сработало для меня, используя stackoverflow.com/a/115034/1302730, чтобы получить дату в формате ISO
BugLover
7

Это решение и модель возможных решений. Используйте Moment.js в своем клиенте для форматирования дат, преобразования во время unix.

 $scope.startDate.unix()

Настройте параметры маршрута как длинные.

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

Создайте метод расширения для времени Unix. Метод DateTime в Unix

Kentonbmax
источник
4

Раньше это было болезненной задачей, но теперь мы можем использовать toUTCString ():

Пример:

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

Поместите ниже в почтовый запрос Ajax

data: {
    Start: new Date().toUTCString(),
    End: new Date().toUTCString()
},
sk
источник
3

Фактически, указание параметров в явном виде как? Date = 'fulldatetime' работало как шарм. Так что это будет решением на данный момент: не используйте запятые, а используйте старый подход GET.

Николодеон
источник
0

Поскольку я кодирую операционную систему ISO-8859-1, формат даты «dd.MM.yyyy HH: mm: sss» не был распознан, что действительно помогло использовать строку InvariantCulture.

string url = "GetData?DagsPr=" + DagsProfs.ToString(CultureInfo.InvariantCulture)
KnuturO
источник
0

Глядя на ваш код, я предполагаю, что вы не беспокоитесь о «времени» объекта DateTime. Если это так, вы можете передать дату, месяц и год как целочисленные параметры. См. Следующий код. Это рабочий пример из моего текущего проекта.

Преимущество; этот метод помогает мне избежать проблем с форматом DateTime и несовместимости культур.

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

Если вы хотите увидеть и внешний интерфейс, не стесняйтесь читать приведенный ниже код. К сожалению, это написано на Angular. Вот как я обычно передаю DateTime в качестве параметра запроса в запросах 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;
}
Кушан Рандима
источник
0

Одно из возможных решений - использовать Ticks:

общедоступные длинные тики {получить; }

Затем в методе контроллера:

общедоступный DateTime (длинные тики);

николай сердюк
источник