Search code examples
c#asp.net-mvcasp.net-mvc-routingasp.net-web-api

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.


Solution

  • 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.