Notes
- I have been informed that in .NET 3.5, there are smoother ways to do this. I do say that later in the article, but please be aware that this is a 2.0 only workaround.
- I have had a warning that IIS6.0 doesn't like this workaround. I have only tried it on IIS 5.1 and it seemed fine. I shall be doing more testing when I can and I will let you know the results. RESULTS both IIS 6.0 and 5.1 require some tinkering to read URLs with no file extension. The 6.0 process can be found in the comments to this article. The 5.1 version requires the following steps:
- Open the website properties in IIS
- Select the Home tab
- Click the Configuration button
- In new window select the Mappings tab
- Click the Add button
- In new window's executable field Browse to the ASP.NET ISAPI DLL (usually something like C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll)
- In the Extension field, type .*
- Untick the Check that file exists box and click OK
N.B. There is a bug that if your OK button is not active, you need to click the executable field text box, you should see the middle section of the text in the box change from /.../ to the full file path.This should now make the OK button active.
Thanks to Neil Kilbride for that.
Introduction
Like so many people, I started out using SOAP Web requests which do integrate nicely into an IDE but they're not intuitive to the internet generation. A lot of people, Yahoo is one example, want their services to work in a way that any idiot could interpret or predict a likely behaviour. After all, if there's one thing you know you'll find on the internet it's any idiot.
So instead of pitching a great ball of SOAP to a server which then spits its own ball of SOAP back at you, we're going to take a look at REST.
If you really have to know REST stands for: Representational State Transfer.
I bet you feel better knowing that.
No. Maybe not.
I often find there's a dearth of brass tacks answers in Internet tutorials so if you've got this far I'll guess you are either familiar with or amenable to a Web request that is formatted...
www.someserviceprovider.com/myrestservice/3
... and which returns some handy chunk of XML or similar.
Yes. A REST request, when all is said and done, is a service which replaces your SOAP suds with a simple, common or garden URL.
It also just returns some XML or formatted text or whatever instead of a matching SOAP bubble. What could be simpler, put a URL into your address bar and unearth some juicy content.
Well, although it *is* simple from a user perspective, learning how to actually do it is another problem altogether.
What you're looking to do is implement an interface called IHttpHandler
and the easiest way to get a working stub for this is to go to a Web project and add a new item of type generic handler (*.ashx file extension). When you do so, it will return a blank service that looks like this:
<%@ WebHandler Language="C#" Class="example" %>
using System;
using System.Web;
public class exampleRestService : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
Basically you add your code into the "ProcessRequest
" method and it can write something back to the screen, be that (as in this case) some text, or an XML document or whatever using its HttpContext
object. As with any plain Web Request, what you submitted in the URL is a GET
request and the only difference between this and visiting a website is that you are not requesting a file, you are requesting the current state of an object (i.e. a service).
However when you are using a generic handler harness the URL you would use to access it would be along the lines of:
www.someserviceprovider.com/exampleRestService.ashx
And it would do the rest.
So that is blatantly referring to a file, which is not as elegant as what we're wanting to do by implying in the very structure of our URL that there is no actual file to which we are referring.
However, while we have this acceptable yet clunky beast in front of us, let's make it do something when you prod it because it's easier to get your head round at this stage.
<%@ WebHandler Language="C#" Class="example" %>
using System;
using System.Web;
public class exampleRestService : IHttpHandler {
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
string output = "";
if (context.Request["input"] != null)
{
output = context.Request.QueryString["input"].ToString();
context.Response.Write("Your Input Was: " + output);
}
}
public bool IsReusable {
get {
return false;
}
}
}
This basically means that when someone visits:
www.someserviceprovider.com/exampleRestService.ashx?input=hello%20world
Then the response they will get will be:
Your Input Was: hello world
So the effect is all great, but the execution leaves a little something to be desired. How do we grasp that subtly more pleasing URL format we were initially after?
The first thing you're going to have to do is start mucking about with your web.config file. The section you need differs depending on whether your IIS is 6.0 backwards or 7.0. If the latter, you're looking for a section called < handlers >
in the < system.web >
section. If the former, the section is in the same parent location but is now called < httpHandlers >
.
The entry you want to put into the section looks like this:
IIS 6.0 and previous: <add type="MyWebServices.exampleRestService,
MyWebServices" path="exampleRestService/*" verb="*">
IIS 7.0: <add type="MyWebServices.exampleRestService,
MyWebServices" path="exampleRestService/*" verb="*"
name="exampleRestService">
The important attribute of this tag for our purposes is the one marked "type
". Briefly I'll run through the rest: verb=
will this be a handler for just GET
? just POST
? or both (*) so this one is both; path=
what is the domain looking for in rerouting to this service? In this case, the path "exampleRestService/" and then anything from nothing to a single character to the text of your latest hilarious internet memo received via email (because even IIS needs a good laugh occasionally).
But then we get to the type attribute (Oh, IIS 7 people I don't run IIS 7 so what the name attribute does I'm not exactly clear on... names it? For some no doubt a wonderful benefit, I imagine.) What does it do? What magic does it perform? Well the first part before the comma tells it what namespace to look for and what class in that namespace it is trying to find. The part after the comma refers to the assembly it's going to look at.
I can see you all telling me to back up a few places here.
What assembly? I hear you ask. The one you are about to create I answer.
You see to do the URL handling you can't use the *.ashx file, it has to go. Sorry. Instead what you are going to do is start a new C# class library project. And for consistency with this article, you might call it MyWebServices
.
Then in your class library, you might want to call your class exampleRestService.cs.
And it will look like this:
using System;
using System.Web;
namespace MyWebServices
{
public class exampleRestService : IHttpHandler {
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
string output = "";
if (context.Request["input"] != null)
{
output = context.Request.QueryString["input"].ToString();
context.Response.Write("Your Input Was: " + output);
}
}
public bool IsReusable {
get {
return false;
}
}
}
}
Yes, basically the ASHX without the ASP.NET header. Exactly.
Compile this out to a convenient location and then back in your Web project create a reference to the DLL you just compiled.
Now it all becomes clear, yes?
Well, no.
Because you'll still notice that to get the correct output, the URL has to be:
www.someserviceprovider.com/exampleRestService/?input=hello%20world
So close... yet not quite close enough.
We're literally "?input=
" away from our goal. This problem would seem to be intractable. Apparently in .NET 3.5 or whatever the cool kids are calling it these days, there's some way of taking care of this automatically. I don't know, I'm still using 2.0.
All I know is that the probable best way to deal with this is to cheat.
Yes, you heard me. Cheat.
We all know that if you have input for a dynamic Web page, it should come in the form of GET
or POST
variables. That's fine but GET
variables are always behind that dratted "?
" and then you need each one to be tagged with a name like input which then "=
" whatever you want to send.
If you have only one input variable, there's a way around this.
And here it is:
string output = "";
int subPos = 0;
output = context.Request.RawUrl.ToString();
if(output.IndexOf("/exampleRestService/") > -1)
{
subPos = output.IndexOf("/exampleRestService/");
subPos = subPos + 20;
output = output.Substring(subPos);
}
context.Response.Write("Your Input Was: " + output);
Not exactly the most robust of solutions but, trust me, it works. It could be that you tuned out on me way back after I told you to start making class libraries and the like, but maybe you want to go that extra mile. If so, this is a way. I'd be keen to hear any better ideas as I'm not sure how robust this one would be in the long term.
For now though...
www.someserviceprovider.com/exampleRestService/the%20end
... will tell you that:
Your Input Was: the end