Simple URL routes in WCF Rest 4.0 without trailing slash
Solution 1
The primary issue that you're running into is that the current version of WCF REST causes a 307 redirect (to the "/") when you have an empty string for the UriTemplate in your WebGet attribute. As far as I know, there is no getting around this in the current version.
However, there are a couple of "middle ground" solution to your problem given that you want a solution that 1) allows you to differentiate services, and 2) have (relatively) short URIs.
First Solution You can put this in your global.asax file (per this example). You can do a service route for each service:
RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHostFactory(), typeof(CarService)));
RouteTable.Routes.Add(new ServiceRoute("trucks", new WebServiceHostFactory(), typeof(TruckService)));
At this point you can populate your UriTemplate in each service:
[WebGet(UriTemplate = "all")]
CarPool GetAllCars();
[WebGet(UriTemplate = "{carName}")]
Car GetCar(string carName);
This will allow you URIs of:
www.domain.com/cars/all
www.domain.com/cars/123 or www.domain.com/cars/honda
similarly for trucks:
www.domain.com/trucks/all
www.domain.com/trucks/123 or www.domain.com/trucks/ford
Second Solution Use the service host from the REST Starter Kit (i.e., the WebServiceHost2Factory).
RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHost2Factory(), typeof(CarService)));
This does not result in a 307 redirect when using the URIs that you're attempting to use above and thus, gives you exactly what you need. Though I realize that feels a little weird using that service host factory rather than the one that ships with WCF 4.
Solution 2
Try putting this in the Global.asax.cs
protected void Application_BeginRequest(object sender, EventArgs e)
{
string rawUrl = HttpContext.Current.Request.RawUrl.ToLower();
if (rawUrl.EndsWith("/cars"))
{
HttpContext.Current.RewritePath(rawUrl + "/"); // append trailing slash
}
}
Solution 3
You need a UriTemplate, Try something like this:
[ServiceContract()]
public interface ICarService
{
[OperationContract]
[WebGet(UriTemplate = "/Car")]
CarPool GetAllCars();
[OperationContract]
[WebGet(UriTemplate = "/Car/{carName}")]
Car GetCar(string carName);
}
Solution 4
Older question but here's how I solved the problem with a WCF4 REST service (using the RouteTable in Global.asax to add ServiceRoutes). IIS7 is configured so that by the time the service is invoked I have an empty relative path so the handling method's UriTemplate is empty like Will's Car example. I used a rewrite rule in the service's web.config file to add a "/" if needed. It always matches the path then checks the original URI ({REQUEST_URI}) to see if it contains a path without a trailing "/".
<rewrite>
<rules>
<!--
This rule will append a "/" after "/car" if
the client makes a request without a trailing "/".
ASP however must have a trailing "/" to find
the right handler.
-->
<rule name="FixCarPath" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{REQUEST_URI}" pattern="/car\?" />
</conditions>
<action type="Rewrite" url="{PATH_INFO}/" />
</rule>
</rules>
</rewrite>
Solution 5
A bit more reusable:
public class Global : NinjectHttpApplication
{
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("login", new NinjectWebServiceHostFactory(), typeof(LoginService)));
RouteTable.Routes.Add(new ServiceRoute("incidents", new NinjectWebServiceHostFactory(), typeof(IncidentService)));
SetRoutePrefixes();
}
//This is a workaround for WCF forcing you to end with "/" if you dont have a urlTemplate and redirecting if you dont have
protected void Application_BeginRequest(object sender, EventArgs e)
{
string rawUrl = HttpContext.Current.Request.RawUrl.ToLower();
if (_routePrefixes.Any(rawUrl.EndsWith))
{
HttpContext.Current.RewritePath(rawUrl + "/"); // append trailing slash
}
}
private static List<string> _routePrefixes;
private static void SetRoutePrefixes()
{
_routePrefixes = new List<string>();
foreach (var route in RouteTable.Routes)
{
var r = route as Route;
var routePrefix = r.Url.Split('/').First();
_routePrefixes.Add(routePrefix);
}
}
Related videos on Youtube
Will
Updated on June 19, 2020Comments
-
Will almost 4 years
I have a WCF REST 4.0 project based on the the WCF REST Service Template 40(CS). I'd like to expose simple service endpoint URLs without trailing slashes. For example:
- CarService.cs
- http://www.domain.com/cars - GET returns a list of all cars
- http://www.domain.com/cars/123 - GET returns a single car with ID 123
- TruckService.cs
- http://www.domain.com/trucks - GET returns a list of all trucks
- http://www.domain.com/trucks/456 - GET returns a single truck with ID 456
I look at the above URLs as resource requests (not directories), which is why I don't think trailing slashes are appropriate here.
Unfortunately, I can't seem to get the behavior I want because I am always redirected to /cars/ and /trucks/ with a trailing slash.
Here's how I've defined the "cars" route and service method - note that I have not included any slashes in any of the route or URI template definitions:
// Global.asax.cs RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHostFactory(), typeof(CarService))); // CarService.cs [WebGet(UriTemplate = "")] public List<Car> GetCollection() { return DataContext.GetAllCars(); }
Note that MVC does not work this way. With the MapRoute method I can route requests directly to http://www.domain.com/about without a redirect to /about/. How can I get the same behavior in WCF REST 4.0?
- CarService.cs
-
Will over 13 yearsYes, but I want both a CarService and a TruckService, so I would have to add two routes in Global.asax.cs, right? And to differentiate between them, the URLs would be very verbose: e.g. domain.com/carservice/cars and domain.com/truckservice/trucks. I'd prefer to have multiple service classes and short URLs: e.g. domain.com/cars and domain.com/trucks
-
Will over 13 yearsYou are correct that I can have slashless URLs if I was willing to list all my service methods in a single service class, however. But that could be hundreds of methods for a large API.
-
Will over 13 yearsThanks for the info, Steve. I'd considered using your "all" suggestion already (it's a decent workaround), but I'm reluctant to give up on the canonical REST syntax I originally hoped for. I'm looking into the differences between the shipped WebServiceHostFactory and WebServiceHost2Factory, and what it is about the latter one that prevents the 307 redirect. If it's a minor change that I can grok, I'll go with that approach.
-
Will over 13 yearsActually, I just tried using WebServiceHost2Factory and get the same redirect result.
-
Steve Michelotti over 13 yearsOops - strike my second solution - you are correct in that the WebServiceHost2Factory results in the same behavior. (when I tested it, I did not have an empty UriTemplate). But the first solution will still work.
-
Andrew almost 11 years@SteveMichelotti but this raise a problem, can you point out a soultion for this Overriding existing Routes . i am facing the same situation as this
-
sheikhjabootie almost 10 years+1 I had a service that I wanted to invoke with or without the trailing slash. Your solution works great.