I’ve been getting a lot of questions in the last few months regarding the URLs that WCF REST uses. WCF REST – new in .NET 3.5 – provides a pure Web based Binding (webHttpBinding specifically) that allows for creating REST based URLs that return non SOAP Http results in Xml, JSON, RSS, Atom or raw binary formats. One of the nice things that you can do with WCF REST endpoints is that you can specify a UriTemplate that allows you to customize the way the URL looks when accessing the endpoint.
For example I might have a method endpoint defined like this:
[OperationContract]
[WebInvoke(
Method = "GET",
ResponseFormat = WebMessageFormat.Xml,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate="Quotes/{symbol}")
]
StockQuote GetStockQuote(string symbol);
Normally a WCF endpoint is accessed through the .SVC file plus a path that specifies the method name plus any parameters specified on the query string (or in POST data). Without the URI template to load a stock would look like this:
http://localhost/wcfAjax/RestStockService.svc/GetStockQuote?symbol=MSFT
Using the UriTemplate the URL becomes a little nice and more readable:
http://localhost/wcfAjax/RestStockService.svc/Quotes/MSFT
Note that once you apply a UriTemplate the method/parameter syntax no longer works – only a single endpoint URL maps to the specific endpoint.
Url formatting is of course mostly semantics – the latter URL is easier to parse by a human consumer having more of the feel of a breadcrumb that leads you down the path. You can also create your own ‘rules’ for this so if you also wanted to return that same data in a different format like Json (where the original returned XML) you might want to set up a second endpoint that does this and looks like this:
[OperationContract]
[WebInvoke(
Method = "GET",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped,
UriTemplate = "Quotes/{symbol}/json")
]
StockQuote GetStockQuoteJson(string Symbol);
and can be called like this:
http://localhost/wcfAjax/RestStockService.svc/Quotes/MSFT/json
WCF REST allows for some welcome URL flexibility which is a nice feature when dealing with REST based endpoints.
One important limitation with UriTemplate to keep in mind that parameter mapping ( inside of {} ) only works with string parameters, so even simple type mapping like int, bool, or dates don’t work with them.
Getting rid of the pesky .SVC Extension
One of the first questions I usually get when I talk about the WCF REST features usually is: How do I get rid of the .SVC extension in those URLs? Back to semantics, eh? If you look at the URLs above you notice that each of the URLs references the .SVC file including its .SVC extension. The UriTemplate defined on the WCF endpoint method specifies the a path that follows the .svc file, so whatever template you set up it is appended to the main service URL which ends in the .Svc file.
Here are two approaches you can use to remove the .svc extension and both of them work through Url Redirection at the server level.
IIS 7 Rewrite Module
A couple of months ago Microsoft released the IIS 7 Rewrite module which allows you to easily and declaratively define Url Rewrite rules in your Web.config file. The module provides RegEx based replace syntax and is ideal for fixing up .SVC urls. What’s nice is that it also includes a nice UI for the IIS 7 Management console so that you can visually see all rewrite rules installed and even experiment and test them out.
To get a URL like this:
http://localhost/wcfAjax/Rest/RestStockService/Quotes/MSFT/json
You can use the following rule definition:
<?xml version="1.0" encoding="UTF-8"?><configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="RestStockService" stopProcessing="true">
<match url="^rest/reststockservice/(.*)$"/>
<action type="Rewrite" url="rest/reststockservice.svc/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
You use RegEx expressions to match the service Url without the .svc extension. The RegEx expression is based on virtual directory as its base path – it’s important that you consider any subdirectory paths in the URL. As you can see my service lives in a Rest folder underneath the virtual directory root and that’s reflected in the match expression for the URL searched for. Note the RegEx group after the service name – an explicit group is used to capture the entire extra path of the URL so it can be used in the rewrite operation. You can reference any of the group matches as {R:1} for the first group match ( {R:0} is the entire match).
This is a pretty painless way to do URL routing that’s easy to modify declaratively and store in a configuration file.
You can download the IIS RewriteModule here:
http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1691
I’d highly recommend this module even if you’re not using it for WCF REST Urls. It’s been really useful to me in setting many URL rewriting tasks that I previously used custom modules for.
Custom Http Module
The IIS Rewrite module only work in IIS 7 only unfortunately so if you’re running IIS 6/5 or you need more sophisticated URL routing than RegEx expressions can provide you can create a custom module.
IIS 5/6 requires Wildcard Mapping
If you’re not running IIS 7 in order for this to work you need to enable Wildcard Script Mapping and map the wild card map to ASP.NET. Wildcard script mapping sets up all URLs to be routed through the ASP.NET pipeline so every resource – static or otherwise - is fired through ASP.NET so you can run HttpModules against them. This allows even extensionless Urls to (like http://localhost/wcfAjax/Rest/RestStockService/Quotes/MSFT/json ) fire through your ASP code and hit your modules.
Once you have requests firing into the ASP.NET pipeline you can then set up an HttpModule that reroutes requests. The following is a somewhat generic .svc rerouting mechanism where you can just specify a part fragment that is treated as a service.
/// <summary>
/// Redirect module that allows specifying a set of .svc urls
/// by stripping the svc extension off and accessing without it.
///
/// To use add any non-svc path segments (ie. service.svc should be service)
/// to the ServiceMap below.
///
/// Note that any path that uses one of these service map entries needs to
/// end with a trailing backslash.
/// </summary>
public class ServiceRedirector : IHttpModule
{
static List<string> ServiceMap = new List<string>
{
"reststockservice",
"ajaxservice",
"jsonstockservice"
};
public void Dispose()
{
}
public void Init(HttpApplication app)
{
app.BeginRequest += delegate
{
HttpContext ctx = HttpContext.Current;
string path = ctx.Request.AppRelativeCurrentExecutionFilePath.ToLower();
foreach (string mapPath in ServiceMap)
{
if (path.Contains("/" + mapPath + "/") || path.EndsWith( "/" + mapPath) )
{
string newPath = path.Replace("/" + mapPath + "/", "/" + mapPath + ".svc/");
ctx.RewritePath(newPath, null, ctx.Request.QueryString.ToString(), false);
return;
}
}
};
}
}
The idea is that you provide a list of Urls (statically in this case but you could easily modify this to load the list dynamically either from config settings or some external source) that are to be routed as simple strings. In short the names of the URL that you want routed without the SVC extension. This code still assumes you want to route to the same name as the SVC file (ie. /reststockservice/ becomes /reststockservice.svc/).
Other than that the code just runs through list and and tries to match it to the current URL. If found it replace the match by adding the .svc extension to it and off it goes.
To hook up the module you’ll need to add it to your web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<httpModules>
<add name="ServiceRedirector" type="WcfAjax.ServiceRedirector"/>
</httpModules>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<add name="ServiceRedirector" type="WcfAjax.ServiceRedirector"/>
</modules>
</system.webServer>
</configuration>
The <system.web> section is for IIS 5/6, <system.webServer> for IIS 7. The module’s code is a pretty simplistic approach that uses simple string replacement to expand non-.SVC Urls to .SVC urls, but it should give you enough of an idea on how to create something more flexible should you need it. This approach works both in IIS 5/6 with the wildcard script mapping or in IIS 7 where no mapping is required as all requests already fire through the ASP.NET pipeline.
While it’d by nice if WCF had a native mechanism for removing the .SVC extension from URLs, you can see that’s it’s pretty straightforward to do so either with the IIS 7 Rewrite Module or by creating a small custom module.
Does it really matter?
I’m not sure if changing a Url by removing an extension is really providing any benefits, perceived or otherwise. To me this is semantics. Sure a Url without the .svc looks nicer, but who cares? For the most part COMPUTERS not people access those Urls and computers have no sense of esthetics <s> at least last time I checked. And if it’s an API, it’s still only a developer who looks at it once and surely a developer can determine the difference between a URL with or without an extension buried in the extra path.
I think what it really comes down to is having a clean URL structure that's natural to follow. Certainly an API that is consistent will be easier to learn for a developer to use and easier to work with in the long run. Whether stripping of an .SVC extension helps that cause or whether you use a query string vs. a path separator is arguable at least in my mind. The biggest concern for an API is consistency - it should be logical, readable and most of all consistent. And frankly query strings are the easiest way to be consistent because they are non-positional unlike path separators which are.
However, as I mentioned at the outset of this post – a lot of people actually seem to care quite vehemently about the Url format, because it’s easily one of the most frequent requests I have heard since I started writing/speaking about WCF. Hence this post.
So, what’s it to you? Does the URL matter and if so why?
Other Posts you might also like