UserControl
UserControl

Reputation: 15159

Parsing of custom format DateTime values in Web API routes

I'd like to use DateTime values in my routes like this:

/api/items/after/2015-08-01T08:00:00

but it's not working for the obvious reason:

System.Web.HttpException: A potentially dangerous Request.Path value was detected from the client (:).

I don't want to escape :. Apparently, I need different format like 2015-08-01T08-00-00 for nice looking urls. It can be solved by changing default DateTime serialization format in Json.NET but in this case it will be applied globally. Or, I can try writing a custom binder. I wonder if there is a better (easier) approach. Maybe some configuration parameter the framework offers out of the box?

Upvotes: 2

Views: 784

Answers (1)

JotaBe
JotaBe

Reputation: 39045

The easies way to do this id the following:

  • implement a class that has a DateTime property where you will deserialize the DateTime value, for example DateTimeWrapper
  • define the route parameter as being of this class, instead of DateTime
  • create a type converter which can convert the url segment string into your DateTimeWrapper class, by inheriting from TypeConverter. You can call it DateTimeWrapperConverter
  • decorate your DateTimeWrapper class with an attribute like this [TypeConverter(typeof(DateTimeWrapperConverter))]

Once you've done this, when you receive the request, any route parameter of DateTimeWrapper class will be handled by your converter. Then you simply have to read the DateTime property of the DateTimeWrapper created by your custom converter.

In this way you avoid modifying the general behaviour for DateTime values.

This is a sample implementation, but the parsing itself is missing:

[TypeConverter(typeof(DateTimeWrapperConverter))]
public class DateTimeWrapper
{
    public DateTime Value { get; set; }
}

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

    public override object ConvertFrom(
         ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            DateTimeWrapper wrapper = new DateTimeWrapper
            {
                Value = new DateTime() // parse the url segment !!
            };
            return wrapper;
        }
        return base.ConvertFrom(context, culture, value);
    }
}

You can test is wit a controller like this:

public class TestController : ApiController
{
    // GET api/<controller>/5
    public string Get(DateTimeWrapper id)
    {
        return id.Value.ToString(CultureInfo.InvariantCulture);
    }
}

As the id is of type DateTimeWrapper it's converted with the custom converter.

If you want you can change the DateTimeWrapper so that it has an implicit convertion to DateTime, so that you don't need to read the Value property. Add this to the DateTimeWrapper class:

public static implicit operator DateTime(DateTimeWrapper wrapper)
{
    return wrapper.Value;
}

After this you can use an object of this class whenever a DateTime is expected.

NOTE: changing the JSON.NET serializer would do nothing in this instance. It's only used to deserialize the payload of the HTTP request, but does nothing about the route parameters.

Upvotes: 2

Related Questions