Как принять параметр WebAPI TimeSpan в формате ISO

1

Недавно я создал службу с веб-API и там API, который должен принять параметр TimeSpan в формате ISO 8601 (например, PT5M).

Здесь мой контроллер api:

[RoutePrefix("api")]
public class StatisticsController : ApiController
{
    [Route("statistics")]
    public async Task<IEnumerable<StatPoint>> GetStatistics([FromUri] TimeSpan duration)
    {
        // ...
    }
}

И мне нужно получить доступ к этому API с помощью url like /api/statistics?duration=PT5M.

Я попытался добавить JsonConverter как JsonConverter некоторые люди в Интернете:

public class IsoTimeSpanConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var ts = (TimeSpan) value;
        var tsString = XmlConvert.ToString(ts);
        serializer.Serialize(writer, tsString);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        var value = serializer.Deserialize<String>(reader);
        return XmlConvert.ToTimeSpan(value);
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof (TimeSpan) || objectType == typeof (TimeSpan?);
    }
}

И в GlobalConfiguration.Configure (GlobalConfiguration.Configure в Application_start()):

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new IsoTimeSpanConverter());

Однако это не сработало, я всегда получаю это сообщение об ошибке:

{
    message: "The request is invalid.",
    messageDetail: "The parameters dictionary contains a null entry for parameter 'duration' of non-nullable type 'System.TimeSpan' for method 'System.Threading.Tasks.Task'1[System.Collections.Generic.IEnumerable'1[WebRole.Api.Models.StatPoint]] GetStatistics(System.TimeSpan)' in 'WebRole.Api.StatisticsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}

Любая помощь приветствуется.

  • 0
    ты пробовал отлаживать? какое значение ReadJson () возвращает?
  • 0
    @H.MahidaХ.Махида Да. Проблема в том, что ReadJson () не вызывается вообще.
Теги:
asp.net-web-api

2 ответа

3
Лучший ответ

Думаю, я нашел решение. Я ошибался, так как JsonConverter обрабатывает сериализацию тела, а синтаксический анализ URL-адреса обрабатывается ModelBinder. Это два разных механизма.

Я прочитал эту статью и создал следующий ModelBinder:

public class IsoTimeSpanModelBinder: IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if(bindingContext.ModelType != typeof(TimeSpan))
        {
            return false;
        }

        var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if(val == null)
        {
            return false;
        }

        var key = val.RawValue as string;
        if(key == null)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type for TimeSpan");
            return false;
        }

        try
        {
            bindingContext.Model = XmlConvert.ToTimeSpan(key);
            return true;
        }
        catch (Exception e)
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Cannot convert value to TimeSpan: " + e.Message);
            return false;
        }
    }
}

Затем config, чтобы использовать его для анализа всего TimeSpan:

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalConfiguration.Configure(Register);
    }

    public static void Register(HttpConfiguration config)
    {
        // Parse TimeSpan in ISO format (e.g. PT5M)
        config.Services.Insert(
            typeof(ModelBinderProvider), 0,
            new SimpleModelBinderProvider(typeof(TimeSpan), new IsoTimeSpanModelBinder()));
    }
}

Также можно разобрать любой пользовательский тип в пользовательских форматах. Очень гибкий!

  • 0
    Если значение по умолчанию EntityFramework для полей времени SQL равно TimeSpan, можно подумать, что больше людей в мире технологий MS столкнулись бы с этой проблемой.
0

Попробуйте это в Application_start()

var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new IsoTimeSpanConverter());

Обновить:

Есть ли причина, по которой вы используете параметр как функцию TimeSpan?

Вы можете взять продолжительность строки, а затем запустить свою функцию с помощью

        try
        {
            TimeSpan tt = XmlConvert.ToTimeSpan(duration);
        }
        catch(Exception ex)
        {

        }

так как ваш пользовательский код делает то же самое преобразование строки (время ISO) в промежуток времени.

  • 0
    Пожалуйста, проверьте мой вопрос. Я уже сделал это ...
  • 0
    Спасибо за ответ. Это правильный обходной путь, но я просто не хочу добавлять логику разбора строк для каждого API, принимающего TimeSpan ... Должен быть какой-то способ ...
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню