¿Cómo pasar un parámetro de fecha y hora?

86

¿Cómo pasar las fechas UTC a la API web?

Pasar 2010-01-01funciona bien, pero cuando paso una fecha UTC como 2014-12-31T22:00:00.000Z(con un componente de tiempo), obtengo una respuesta HTTP 404. Entonces

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

produce una respuesta de error 404, mientras que

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

funciona bien.

¿Cómo pasar las fechas UTC a la API web entonces, o al menos especificar la fecha y la hora?

Nickolodeon
fuente
2
¿Es ":" en la fecha un sospechoso? Intenta escapar. http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z
shahkalpesh
2
Escapar no ayuda. Still 404.
Nickolodeon
¿Puede habilitar la depuración para averiguar por qué falla la traducción de la cadena pasada a la fecha? La idea es averiguar qué método se está utilizando para traducir la fecha que pasó usando la URL DateTime, que supongo que es el tipo de datos del parámetro en su método.
shahkalpesh
4
Yo haré eso. El método espera el parámetro .NET DateTime. ¡Creo que es ridículo que no pueda pasar el componente de tiempo y no pueda encontrar documentos sobre cómo hacerlo!
Nickolodeon
2
Publique su solución cuando haya terminado. Puede ayudar a otras personas que tengan problemas similares. Gracias.
shahkalpesh

Respuestas:

33

El problema es doble:

1. El .en la ruta

De forma predeterminada, IIS trata todos los URI con un punto como recurso estático, intenta devolverlo y omitir el procesamiento adicional (por API web) por completo. Esto se configura en su Web.config en la sección system.webServer.handlers: los manejadores del manejador predeterminado path="*.". No encontrará mucha documentación sobre la sintaxis extraña en este pathatributo (la expresión regular habría tenido más sentido), pero lo que aparentemente significa es "cualquier cosa que no contenga un punto" (y cualquier carácter del punto 2 a continuación). De ahí el 'Sin extensión' en el nombre ExtensionlessUrlHandler-Integrated-4.0.

Son posibles múltiples soluciones, en mi opinión en el orden de 'corrección':

  • Agregue un nuevo controlador específicamente para las rutas que deben permitir un punto. Asegúrese de agregarlo antes del predeterminado. Para hacer esto, asegúrese de eliminar primero el controlador predeterminado y agregarlo nuevamente después del suyo.
  • Cambie el path="*."atributo a path="*". Entonces lo atrapará todo. Tenga en cuenta que a partir de ese momento, su API web ya no interpretará las llamadas entrantes con puntos como recursos estáticos. Si está alojando recursos estáticos en su API web, ¡esto no se recomienda!
  • Agregue lo siguiente a su Web.config para manejar incondicionalmente todas las solicitudes: en <system.webserver>:<modules runAllManagedModulesForAllRequests="true">

2. El :en la ruta

Una vez que haya cambiado lo anterior, de forma predeterminada, obtendrá el siguiente error:

Se detectó un valor Request.Path potencialmente peligroso del cliente (:).

Puede cambiar los caracteres no permitidos / no válidos predefinidos en su Web.config. Bajo <system.web>, añada la siguiente: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. Eliminé el :de la lista estándar de caracteres no válidos.

Soluciones más fáciles / seguras

Aunque no es una respuesta a su pregunta, una solución más segura y sencilla sería cambiar la solicitud para que todo esto no sea necesario. Esto se puede hacer de dos maneras:

  1. Pase la fecha como un parámetro de cadena de consulta, como ?date=2012-12-31T22:00:00.000Z.
  2. Quite el .000de cada solicitud. Aún necesitaría permitir :'s (cfr punto 2).
Vincent Sels
fuente
Su "Solución más fácil" básicamente lo hizo por mí, ya que no necesité segundos.
Neville
Eres un salvavidas :)
Moeez
1
En su "Solución más fácil", en lugar de permitir :s, creo que puede usarlo %3Aen lugar de :y debería estar bien.
Mayer Spitzer
20

en el controlador de API web de su producto:

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

probar el método GetProductPeriodOrders en Fiddler - Composer:

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

Formato de fecha y hora:

yyyy-MM-ddTHH:mm:ss

parámetro de paso 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
fuente
18

Siento tu dolor ... otro formato de fecha y hora ... ¡justo lo que necesitabas!

Con Web Api 2, puede usar atributos de ruta para especificar parámetros.

así que con los atributos en su clase y su método puede codificar una URL REST usando este formato utc con el que tiene problemas (aparentemente es ISO8601, presumiblemente llegó 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)

.... PERO, aunque esto funciona con una fecha (startDate), por alguna razón no funciona cuando endDate está en este formato ... depurado durante horas, la única pista es que la excepción dice que no le gustan los dos puntos ":" (incluso aunque web.config está configurado con:

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

Entonces, hagamos otro formato de fecha (tomado del polyfill para el formato de fecha ISO) y agréguelo a la fecha de Javascript (por brevedad, solo convierta a 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';
        };

    }());
}

Luego, cuando envíe las fechas al método Web API 2, puede convertirlas de cadena a fecha:

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

entonces la url sería

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

Hanselman da información relacionada aquí:

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

Simon Dowdeswell
fuente
En el método WebAPI, puede tener parámetros de fecha y hora como DateTime anulable (DateTime? StartDateString, DateTime? EndDateDtring)
DotNet Fan
Gracias por la mención de toISOString, eso me salvó. Mi servicio WCF RESTful funciona bien con dos fechas en el URI, por lo que no necesitaba sus complejas conversiones de fechas. Tal vez sea una peculiaridad que a la API web no le gusten los dos puntos a pesar de la configuración de configuración ... aunque extraño.
Neville
@Simon, endDatefuncionaría si la URL de la solicitud incluyera una barra diagonal al final. Desafortunadamente, no puedo recordar dónde encontré esta información, ni conozco una forma de evitarlo.
Pooven
Los usuarios de reloj de 24 horas que quieran usar esto deben cambiar hh a HH en el formato de fecha.
Snaits
Esta es la respuesta correcta. StackOverflow, ¡DEJA DE VOTAR RESPUESTAS!
mghaoui
8

Como alternativa similar a la respuesta de sk, puedo pasar una fecha formateada por Date.prototype.toISOString()en la cadena de consulta. Este es el formato estándar ISO 8601 y es aceptado por los controladores .Net Web API sin ninguna configuración adicional de la ruta o acción.

p.ej

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"
Bondolin
fuente
1
¿Lo es? ¿Podría proporcionar algún ejemplo en el que esto funcione? Hice la misma solución y no funciona.
anatol
@anatol ¿qué resultado obtienes? El código proporcionado es un ejemplo funcional, con la condición previa de que dateObjectes un Dateobjeto inicializado .
Bondolin
Esto probablemente debería votarse un poco hacia arriba. Esto resolvió mi problema cambiando UTC a ISO. Simples
Regianni
1
@Regianni me alegro de haber ayudado :-)
Bondolin
Esto funcionó para mí usando stackoverflow.com/a/115034/1302730 para obtener la fecha en formato ISO
BugLover
7

Esta es una solución y un modelo de posibles soluciones. Use Moment.js en su cliente para formatear fechas, convertir a tiempo Unix.

 $scope.startDate.unix()

Configure los parámetros de su ruta para que sean largos.

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

Cree un método de extensión para la hora Unix. Método DateTime de Unix

Kentonbmax
fuente
4

Solía ​​ser una tarea dolorosa, pero ahora podemos usar toUTCString ():

Ejemplo:

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

Ponga lo siguiente en la solicitud de publicación de Ajax

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

De hecho, especificar parámetros explícitamente como? Date = 'fulldatetime' funcionó a las mil maravillas. Entonces, esta será una solución por ahora: no use comas, use el antiguo enfoque GET.

Nickolodeon
fuente
0

Como he codificado el sistema operativo ISO-8859-1, el formato de fecha "dd.MM.yyyy HH: mm: sss" no se reconoció, lo que funcionó fue usar la cadena InvariantCulture.

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

Al mirar su código, supongo que no le preocupa la 'Hora' del objeto DateTime. Si es así, puede pasar la fecha, el mes y el año como parámetros enteros. Consulte el siguiente código. Este es un ejemplo práctico de mi proyecto actual.

La ventaja es; este método me ayuda a evitar problemas de formato de fecha y hora e incompatibilidades culturales.

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

Si también está interesado en ver la interfaz, no dude en leer el código a continuación. Desafortunadamente, eso está escrito en Angular. Así es como normalmente paso un DateTime como un parámetro de consulta en las solicitudes 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
fuente
0

Una posible solución es utilizar Ticks:

public long Ticks {get; }

Luego, en el método del controlador:

Public DateTime (tics largos);

nikolai.serdiuk
fuente