5

I'm having some problems getting a date range query accepted by my WebAPI. As as far as I can tell from everything I've read this should be working but I still get 400 Bad Request responses.

My API route looks like this:

    [System.Web.Http.HttpGet]
    [GET("range/{start:datetime}/{end:datetime}")]
    public HttpResponseMessage Get(DateTime start, DateTime end)

I'm using the AttributeRouting library and according to this page the URL I'm requesting should be fine.

My request URL looks like this:

http://localhost:51258/plots/range/2013-07-29T21:58:39/2013-08-05T21:58:39

I have this set on the controller RoutePrefix("plots") which is where the plots bit of the URL route comes from.

If I strip the times off the DateTime objects everything works fine but I need the times passed.

Jammer
  • 9,969
  • 11
  • 68
  • 115
  • It might be because that isn't a standard DateTime format in C#. The date part is fine, and so is the time, however the T in between them makes it non standard. I would try replacing it with a space. If that works then you'll know what needs to be done. – evanmcdonnal Aug 05 '13 at 21:11
  • I've added the T since it didn't work with a space or an encoded space either. According to Mike Wasson (in the page link) the addition of the T is legal and should be working. – Jammer Aug 05 '13 at 21:19
  • Have you added the regex he mentioned (in the link) to your DateTime? Then only dates in the form "yyyy-mm-dd" will match. – Gloria Aug 05 '13 at 21:28
  • No, I've kept it as simple as possible until I get this basic implementation working. – Jammer Aug 05 '13 at 21:30
  • I just visited my routes.axd page on the API and tried to execute a request `http://localhost:51258/plots/range/2013-07-29T21:58:39/2013-07-29T21:58:39` and got this error `A potentially dangerous Request.Path value was detected from the client (:).` So it seems that `:` might actually be an illegal character ... – Jammer Aug 05 '13 at 22:02
  • Based on the error I was seeing I decided to try it with just one date parameter as I was thinking that it might have been because of multiple dates but even with just one date I get the same error with this URL `http://localhost:51258/plots/range/2013-07-29T23:45:40`, no idea how to fix this at all ... – Jammer Aug 05 '13 at 22:48
  • @evanmcdonnal - Not sure where you got the idea that it was nonstandard. That's the [ISO8601](http://en.wikipedia.org/wiki/ISO_8601) standard - including the `T`. And it is understood by .Net. Look at the `"o"` and `"s"` patterns [here](http://msdn.microsoft.com/en-us/library/az4se3k1.aspx) – Matt Johnson-Pint Aug 06 '13 at 21:41
  • @MattJohnson yeah I was just plain wrong. I was looking at a table on MSDN and reading the wrong values, didn't think it was there. – evanmcdonnal Aug 07 '13 at 00:34

2 Answers2

7

After doing a lot of reading it appears that it is possible to do what I was attempting to do but it requires relaxing a lot of useful security measures in order to do so. Since there is a simple workaround it just doesn't make sense to relax these measures in light of increased security risks.

The error I was getting at the API was:

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

Obviously this is the colon characters used to separate the elements of the time portion of the DateTime string. So I have made the following changes.

My Api action method now looks like this:

[System.Web.Http.HttpGet]
[GET("range?{startDate:datetime}&{endDate:datetime}")]
public HttpResponseMessage Get(DateTime startDate, DateTime endDate)

The dates are now defined as part of the query string rather than parts of the path itself.

To handle the creation of the query string I also have the following extension method:

public static string ToQueryString(this NameValueCollection source, bool removeEmptyEntries)
{
    return source != null ? "?" + String.Join("&", source.AllKeys
        .Where(key => !removeEmptyEntries || source.GetValues(key).Any(value => !String.IsNullOrEmpty(value)))
        .SelectMany(key => source.GetValues(key)
            .Where(value => !removeEmptyEntries || !String.IsNullOrEmpty(value))
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), value != null ? HttpUtility.UrlEncode(value) : string.Empty)))
        .ToArray())
        : string.Empty;
}

Which is used in my client code like this:

var queryStringParams = new NameValueCollection
    {
        {"startDate", start.ToString(_dateService.DefaultDateFormatStringWithTime)},
        {"endDate", end.ToString(_dateService.DefaultDateFormatStringWithTime)}
    };

var response = httpClient.GetAsync(ApiRootUrl + "plots/range" + queryStringParams.ToQueryString(true)).Result;

The date service in my application simply provides the default date formatting string and uses this pattern:

"yyyy-MM-ddTHH:mm:ss"

The complete URI that is produced from this looks like:

http://localhost:51258/plots/range?startDate=2013-07-30T21%3A48%3A26&endDate=2013-08-06T21%3A48%3A26

Hope this helps someone else in the future.

Jammer
  • 9,969
  • 11
  • 68
  • 115
1

: is a reserved character in the URL but it's acceptable in a query string. So if it works for your routing you could simply use:

[System.Web.Http.HttpGet]
[GET("range")]
public HttpResponseMessage Get(DateTime start, DateTime end)

and your URL request can look like this:

http://localhost:51258/plots/range?start=2013-07-29T21:58:39&end=2013-08-05T21:58:39