Can periods be used in Asp.Net Web Api Routes?

I'm working on moving an API project from raw http handlers where I'm using periods in the paths:

http://server/collection/id.format

I would like to follow the same URL schema in a Web Api (self-hosted) version, and tried this:

var c = new HttpSelfHostConfiguration(b);
c.Routes.MapHttpRoute(
    name: "DefaultApiRoute",
    routeTemplate: "{controller}/{id}.{format}",
    defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional },
    constraints: null
);

Unfortunately, that doesn't seem to resolve (consistent 404's on /foo, /foo/bar and /foo/bar.txt). A similar pattern using a slash before 'format' works fine:

var c = new HttpSelfHostConfiguration(b);
c.Routes.MapHttpRoute(
    name: "DefaultApiRoute",
    routeTemplate: "{controller}/{id}/{format}",
    defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional },
    constraints: null
);

I haven't yet delved into the code for the Web Api, and before I do thought I'd ask here to see if this is a known, or perhaps even justified limitation in Web Api.

UPDATE: I neglected to mention that "id" and "format" are strings, which turns out to be important for the solution to this question. Adding a constraint to exclude periods from the "id" token solves the 404 problem.

Answers


I am unable to reproduce the problem. This should work. Here's my setup:

  1. Create a new .NET 4.0 Console Application
  2. Switch to .NET Framework 4.0 profile
  3. Install the Microsoft.AspNet.WebApi.SelfHost NuGet
  4. Define a Product

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
  5. A corresponding API Controller:

    public class ProductsController : ApiController
    {
        public Product Get(int id)
        {
            return new Product
            {
                Id = id,
                Name = "prd " + id
            };
        }
    }
    
  6. And a host:

    class Program
    {
        static void Main(string[] args)
        {
            var config = new HttpSelfHostConfiguration("http://localhost:8080");
    
            config.Routes.MapHttpRoute(
                name: "DefaultApiRoute",
                routeTemplate: "{controller}/{id}.{format}",
                defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional },
                constraints: null
            );
    
            using (var server = new HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();
                Console.WriteLine("Press Enter to quit.");
                Console.ReadLine();
            }
        }
    }
    

Now when you run this console application you could navigate to http://localhost:8080/products/123.xml. But of course you could navigate to http://localhost:8080/products/123.json and you will still get XML. So the question is: How to enable content negotiation using a route parameter?

You could do the following:

    class Program
    {
        static void Main(string[] args)
        {
            var config = new HttpSelfHostConfiguration("http://localhost:8080");
            config.Formatters.XmlFormatter.AddUriPathExtensionMapping("xml", "text/html");
            config.Formatters.JsonFormatter.AddUriPathExtensionMapping("json", "application/json");

            config.Routes.MapHttpRoute(
                name: "DefaultApiRoute",
                routeTemplate: "{controller}/{id}.{ext}",
                defaults: new { id = RouteParameter.Optional, formatter = RouteParameter.Optional },
                constraints: null
            );

            using (var server = new HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();
                Console.WriteLine("Press Enter to quit.");
                Console.ReadLine();
            }
        }
    }

and now you can use the following urls:

http://localhost:8080/products/123.xml
http://localhost:8080/products/123.json

Now you might be wondering what's the relation between the {ext} route parameter that we used in our route definition and the AddUriPathExtensionMapping method because nowhere we did not specify it. Well, guess what: it's hardcoded in the UriPathExtensionMapping class to ext and you cannot modify it because it is readonly:

public class UriPathExtensionMapping
{
    public static readonly string UriPathExtensionKey;

    static UriPathExtensionMapping()
    {
        UriPathExtensionKey = "ext";
    }

    ...
}

All this to answer your question:

Can periods be used in Asp.Net Web Api Routes?

Yes.


I was able to achieve this by doing the following: replace "*." with "*" in system.webServer.handlers in web.config, i.e. remove the period.

<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Be careful to set runAllManagedModulesForAllRequests option in modules attribute in your web.config

<modules runAllManagedModulesForAllRequests="true">..</modules>

Otherwise it will not work in IIS (probably it would be handled by non-managed handlers).


I'm accepting Darin's answer (yes, periods can be used in route urls) because it was specifically correct to my example, yet unhelpful to me. This is my fault for not specifically indicating that "id" is a string, not an integer.

To use a period following a string parameter the routing engine needs hints in the form of a constraint:

var c = new HttpSelfHostConfiguration(b);
c.Routes.MapHttpRoute(
    name: "DefaultApiRoute",
    routeTemplate: "{controller}/{id}.{format}",
    defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional },
    constraints: new { id = "[^\\.]+" } // anything but a period
);

Adding the constraint to the preceding token allows inbound URLs to be correctly decomposed and processed. Without the hint, the "id" token can be interpreted to match the remaining extent of the URL. This is just a specific case of needing constraints to delineate boundaries between string parameters in general.

Yes, periods can be used in URL routes in the Asp.Net Web API, but if they are to follow a string parameter be sure to apply the correct constraint to the route.


IIS intercepts requests with a period as file downloads. In your web.config, you can configure IIS to ignore specific URL paths because the webapi will handle the requests instead. If you want IIS to handle file downloads as well as process webapi calls, you can add a ManagedDllExtension configuration to system.webServer.handlers in web.config.

      <add name="ManagedDllExtension" path="collection/*.*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Need Your Help

Do two synchronized methods execute simultaneously

java multithreading synchronized

I have 4 methods (m1, m2, m3 and m4) in a class. Method m1, m2 and m3 are synchronized methods. Also, I have 4 threads t1, t2, t3 and t4 respectively.

When to use lambda, when to use Proc.new?

ruby lambda proc

In Ruby 1.8, there are subtle differences between proc/lambda on the one hand, and Proc.new on the other.