Como passar um parâmetro de data e hora?

86

Como passar datas UTC para Web API?

Passar 2010-01-01funciona bem, mas quando passo uma data UTC, como 2014-12-31T22:00:00.000Z(com um componente de tempo), recebo uma resposta HTTP 404. então

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

produz uma resposta de erro 404, enquanto

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

funciona bem.

Então, como passar datas UTC para a API Web - ou pelo menos especificar data e hora?

Nickolodeon
fonte
2
":" Na data é um suspeito? Tente escapar. http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z
shahkalpesh
2
Fugir não ajuda. Ainda 404.
Nickolodeon
Você pode habilitar a depuração para descobrir por que a tradução da string passada para a data falhou? A ideia é descobrir qual método está sendo usado para traduzir a data que você passou usando URL DateTime- que presumo ser o tipo de dado do parâmetro em seu método.
shahkalpesh de
4
Eu farei isso. O método espera o parâmetro .NET DateTime. Acho ridículo que eu não consiga passar o componente de tempo e não consigo encontrar documentos sobre como fazer isso!
Nickolodeon
2
Publique sua solução quando terminar. Pode ajudar outras pessoas com problemas semelhantes. Obrigado.
shahkalpesh

Respostas:

33

O problema é duplo:

1. O .na rota

Por padrão, o IIS trata todos os URIs com um ponto como recurso estático, tenta retorná-lo e ignorar o processamento adicional (pela API da Web). Isso é configurado em seu Web.config na seção system.webServer.handlers: os identificadores do manipulador padrão path="*.". Você não encontrará muita documentação sobre a sintaxe estranha neste pathatributo (regex teria feito mais sentido), mas o que isso aparentemente significa é "qualquer coisa que não contenha um ponto" (e qualquer caractere do ponto 2 abaixo). Daí o 'Extensionless' no nome ExtensionlessUrlHandler-Integrated-4.0.

Múltiplas soluções são possíveis, na minha opinião na ordem de 'correção':

  • Adicione um novo manipulador especificamente para as rotas que devem permitir um ponto. Certifique-se de adicioná-lo antes do padrão. Para fazer isso, certifique-se de remover o manipulador padrão primeiro e adicioná-lo novamente após o seu.
  • Altere o path="*."atributo para path="*". Ele então pegará tudo. Observe que, a partir de então, sua API da web não interpretará mais as chamadas recebidas com pontos como recursos estáticos! Se você estiver hospedando recursos estáticos em sua API da web, isso não é recomendado!
  • Adicione o seguinte ao seu Web.config para lidar incondicionalmente com todas as solicitações: em <system.webserver>:<modules runAllManagedModulesForAllRequests="true">

2. O :na rota

Depois de alterar o acima, por padrão, você obteria o seguinte erro:

Um valor Request.Path potencialmente perigoso foi detectado do cliente (:).

Você pode alterar os caracteres não permitidos / inválidos predefinidos em seu Web.config. Sob <system.web>, adicione a seguinte: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. Eu removi o :da lista padrão de caracteres inválidos.

Soluções mais fáceis / seguras

Embora não seja uma resposta à sua pergunta, uma solução mais segura e fácil seria alterar a solicitação para que tudo isso não seja necessário. Isso pode ser feito de duas maneiras:

  1. Passe a data como um parâmetro de string de consulta, como ?date=2012-12-31T22:00:00.000Z.
  2. Retire o .000de cada pedido. Você ainda precisa permitir :'s (cfr ponto 2).
Vincent Sels
fonte
Sua "solução mais fácil" basicamente fez isso por mim, pois não precisei de segundos.
Neville,
Você é um salva-vidas :)
Moeez
1
Em sua "solução mais fácil", em vez de permitir :s, acho que você pode apenas usar %3Ano lugar de :e deve ficar tudo bem.
Mayer Spitzer
20

em seu controlador de API da Web do produto:

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

teste o método GetProductPeriodOrders no Fiddler - Composer:

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

Formato DateTime:

yyyy-MM-ddTHH:mm:ss

parâmetro de passagem javascript use moment.js

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

Eu sinto sua dor ... mais um formato de data e hora ... exatamente o que você precisava!

Usando a Web Api 2, você pode usar atributos de rota para especificar parâmetros.

portanto, com atributos em sua classe e seu método, você pode codificar um URL REST usando este formato utc com o qual está tendo problemas (aparentemente, seu ISO8601, provavelmente chegou a usar 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)

.... MAS, embora isso funcione com uma data (startDate), por algum motivo não funciona quando o endDate está neste formato ... depurado por horas, apenas a dica é exceção diz que não gosta de dois pontos ":" (mesmo embora web.config seja definido com:

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

Então, vamos fazer outro formato de data (retirado do polyfill para o formato de data ISO) e adicioná-lo à data Javascript (por brevidade, converta apenas em minutos):

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

    }());
}

Então, quando você envia as datas para o método Web API 2, pode convertê-las de string em data:

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

então o url seria

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

Hanselman fornece algumas informações relacionadas aqui:

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

Simon Dowdeswell
fonte
No método WebAPI, você pode ter parâmetros de data e hora como DateTime anulável (DateTime? StartDateString, DateTime? EndDateDtring)
DotNet Fan
Obrigado pela menção de toISOString - isso me salvou. Meu serviço WCF RESTful funciona bem com duas datas no URI, portanto, não precisa de suas conversões de datas complexas. Talvez seja uma peculiaridade da API Web não gostar dos dois pontos, apesar da configuração ... estranho.
Neville
@Simon, endDatefuncionaria se o URL da solicitação incluísse uma barra à direita. Infelizmente, não consigo me lembrar de onde encontrei essa informação, nem sei de uma maneira de contornar isso.
Pooven
Os usuários de relógio 24h que desejam usar isso devem alterar o hh para HH no formato de data.
Surge
Essa é a resposta correta. StackOverflow, PARE DE BAIXAR RESPOSTAS!
mghaoui de
8

Como alternativa semelhante à resposta de sk, posso passar uma data formatada por Date.prototype.toISOString()na string de consulta. Este é o formato padrão ISO 8601 e é aceito pelos controladores .Net Web API sem nenhuma configuração adicional da rota ou ação.

por exemplo

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"
Bondolin
fonte
1
é isso? você poderia fornecer algum exemplo de onde isso funciona? Eu fiz a mesma solução alternativa e não funciona.
anatol
@anatol que resultado você obtém? O código fornecido é um exemplo prático, com a pré-condição que dateObjecté um Dateobjeto inicializado .
Bondolin
Isso provavelmente deve ser um pouco votado. Isso resolveu meu problema, alterando UTC para ISO. Simples
Regianni
1
@Regianni que bom que ajudou :-)
Bondolin
Isso funcionou para mim usando stackoverflow.com/a/115034/1302730 para obter a data no formato ISO
BugLover
7

Esta é uma solução e um modelo para possíveis soluções. Use Moment.js em seu cliente para formatar datas, converter para hora unix.

 $scope.startDate.unix()

Configure seus parâmetros de rota para serem longos.

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

Crie um método de extensão para o tempo Unix. Método Unix DateTime

Kentonbmax
fonte
4

Costumava ser uma tarefa difícil, mas agora podemos usar toUTCString ():

Exemplo:

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

Coloque o seguinte na solicitação de postagem do Ajax

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

Na verdade, especificar parâmetros explicitamente como? Date = 'fulldatetime' funcionou perfeitamente. Portanto, esta será uma solução por enquanto: não use vírgulas, mas use a abordagem GET antiga.

Nickolodeon
fonte
0

Como eu tenho a codificação do sistema operacional ISO-8859-1, o formato de data "dd.MM.yyyy HH: mm: sss" não foi reconhecido, o que funcionou foi usar a string InvariantCulture.

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

Olhando para o seu código, suponho que você não se preocupe com o 'Time' do objeto DateTime. Se sim, pode passar a data, mês e ano como parâmetros inteiros. Por favor, veja o seguinte código. Este é um exemplo prático do meu projeto atual.

A vantagem é; esse método me ajuda a evitar problemas de formato DateTime e incompatibilidades de cultura.

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

Se você também deseja ver o front-end, sinta-se à vontade para ler o código abaixo. Infelizmente, isso está escrito em Angular. É assim que eu normalmente passo um DateTime como um parâmetro de consulta em solicitações 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
fonte
0

Uma solução possível é usar Ticks:

public long Ticks {get; }

Então, no método do controlador:

DateTime público (tiques longos);

nikolai.serdiuk
fonte