Передача DateTimeOffset в качестве строки запроса WebAPI

у меня есть действие WebAPI, которое выглядит так:

[Route("api/values/{id}")]
public async Task<HttpResponseMessage> Delete(string id, DateTimeOffset date) {
    //do stuff
}

но когда я вызываю это из HttpClient экземпляр, делая URL как:

string.Format("http://localhost:1234/api/values/1?date={0}", System.Net.WebUtility.UrlEncode(DateTimeOffset.Now.ToString()));
// -> "http://localhost:1234/api/values/1?date=17%2F02%2F2015+7%3A18%3A39+AM+%2B11%3A00"

Я 400 ответ назад, говоря, что ненулевой параметр date не существует.

Я также попытался добавить [FromUri] атрибут параметра, но он по-прежнему не отображается. Если я изменю его на DateTimeOffset? Я вижу, что он оставлен как null и смотрит на Request.RequestUri.Query значение есть, просто не сопоставляется.

наконец, я попытался не делать WebUtility.UrlEncode и ничего не изменилось.

7 ответов


проблема точно описывается ответным сообщением 400, хотя это могло быть более ясно. Маршрут, определенный атрибутом, ожидает только параметр id, но метод Delete ожидает другой параметр с именем дата.

Если вы хотите предоставить это значение с помощью строки запроса, вам нужно сделать этот параметр nullable, используя " DateTimeOffset?", что также превратит его в факультативный параметр. Если дата является обязательным полем, подумайте о ее добавлении в маршрут, например:

[Route("api/values/{id}/{date}")]

OK, игнорируйте то, что я набрал выше, это просто проблема форматирования. Web API не может определить культуру, необходимую для анализа данного значения, но если вы попытаетесь передать DateTimeOffset, используя формат JSON в строке запроса, например 2014-05-06T22:24:55Z, это должно сработать.


ответ

Отправить DateTimeOffset ваш API, формат такой:

2017-04-17T05:04:18.070Z

полный URL API будет выглядеть следующим образом:

http://localhost:1234/api/values/1?date=2017-04-17T05:45:18.070Z

форматируя его таким образом, я могу пройти в DateTimeOffset параметр my ApiController что я сделал с помощью мобильных служб Azure.

код

можно использовать ToString(yyy-MM-ddTHH:mm:ss.fffZ) для анализа DateTimeOffset.

var dateTimeOffsetAsAPIParameter = DateTimeOffset.UtcNow.ToString("yyy-MM-ddTHH:mm:ss.fffZ");

string.Format("http://localhost:1234/api/values/1?date={0}", dateTimeOffsetAsAPIParameter);

создайте пользовательский конвертер типов следующим образом:

public class DateTimeOffsetConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;

        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(DateTimeOffset))
            return true;

        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        var s = value as string;

        if (s != null)
        {
            if (s.EndsWith("Z", StringComparison.OrdinalIgnoreCase))
            {
                s = s.Substring(0, s.Length - 1) + "+0000";
            }

            DateTimeOffset result;
            if (DateTimeOffset.TryParseExact(s, "yyyyMMdd'T'HHmmss.FFFFFFFzzz", CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
            {
                return result;
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

в последовательности запуска, например WebApiConfig.Register, добавьте этот конвертер типов динамически в DateTimeOffset struct:

TypeDescriptor.AddAttributes(typeof(DateTimeOffset),
                             new TypeConverterAttribute(typeof(DateTimeOffsetConverter)));

теперь вы можете просто передать DateTimeOffset значения в компактной форме ISO8601, который опускает дефисы и двоеточия, которые мешают URL:

api/values/20171231T012345-0530
api/values/20171231T012345+0000
api/values/20171231T012345Z

обратите внимание, что если у вас есть доли секунды, вам может понадобиться добавлять косую черту в URL-адрес.

api/values/20171231T012345.1234567-0530/

вы также можете поместить его в строку запроса, если хотите:

api/values?foo=20171231T012345-0530

для достижения этого я использую

internal static class DateTimeOffsetExtensions
{
    private const string Iso8601UtcDateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ";

    public static string ToIso8601DateTimeOffset(this DateTimeOffset dateTimeOffset)
    {
        return dateTimeOffset.ToUniversalTime().ToString(Iso8601UtcDateTimeFormat);
    }
}

используйте ISO 8601 формат datetime описатель:

$"http://localhost:1234/api/values/1?date={DateTime.UtcNow.ToString("o")}"

или

$"http://localhost:1234/api/values/1?date={DateTime.UtcNow:o}"

лучший способ узнать-попросить WebAPI создать ожидаемый формат URL:

public class OffsetController : ApiController
{
    [Route("offset", Name = "Offset")]
    public IHttpActionResult Get(System.DateTimeOffset date)
    {
        return this.Ok("Received: " + date);
    }

    [Route("offset", Name = "Default")]
    public IHttpActionResult Get()
    {
        var routeValues = new { date = System.DateTimeOffset.Now };
        return this.RedirectToRoute("Offset", routeValues);
    }
}

при вызове / offset ответ возвращает 302 url-адрес, содержащий параметр "date" в строке запроса. Это будет выглядеть примерно так:

http://localhost:54955/offset?дата=17.02.2015 09:25:38 +11:00

Я не смог найти перегрузку DateTimeOffset.ToString (), который будет генерировать строковое значение в этом формате, за исключением явного определения формата в строковом формате:

DateTimeOffset.Now.ToString("dd/MM/yyyy HH:mm:ss zzz")

надеюсь, что это поможет.


вот самый простой способ для тех, кто ищет какой-то синхронизации между клиентом и сервером с помощью datetime. Я реализовал это для мобильного приложения. Она независима от культуры клиента. потому что мое мобильное приложение поддерживает несколько культур и скучно использовать форматирование между этими культурами. спасибо, что .net имеет идеальные функции под названием ToFileTime и FromFileTime

Действие Контроллера-Сервера:

        [HttpGet("PullAsync")]
     public async Task<IActionResult> PullSync(long? since = null, int? page = null, int? count = null)
{
                if (since.HasValue) 
                    DateTimeOffset date = DateTimeOffset.FromFileTime(since.Value);

                }
}

клиент Сторона

     DateTimeOffset dateTime = DateTime.Now.ToFileTime();
url= $"/PullAsync?since={datetime}&page={pageno}&count=10";