Недавно я создал службу с веб-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."
}
Любая помощь приветствуется.
Думаю, я нашел решение. Я ошибался, так как 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()));
}
}
Также можно разобрать любой пользовательский тип в пользовательских форматах. Очень гибкий!
Попробуйте это в 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) в промежуток времени.
TimeSpan
... Должен быть какой-то способ ...