Click here to Skip to main content
Click here to Skip to main content

REST Web Services in ASP.NET 2.0 (C#)

By , 5 Nov 2008
 

Notes

  1. 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. 
  2. 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 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Leo Stableford
Web Developer
United Kingdom United Kingdom
Member
Leo Stableford has been working in IS for seven years. Initially as a teacher and then for the last five years as in-house developer for an eclectic variety of private sector organisations from law firms to concert ticket agencies.
 
He started out working in Classic ASP and moved to .Net three years ago. His .Net language of choice is C# and he also develops a fair bit of T-SQL for SQL Server 2005 and PHP with MySQL.
 
He lives in Nottingham and has only just started coding for pleasure. He understands this is a slippery slope.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionNice articlememberClivest27 Jun '11 - 12:19 
Got me started on the web service. Thanks. A couple of points:
 
- I'm using IIS 7 in Windows 7 in its 'integrated' mode and I have to enter the web service details in <handlers> inside <system.webServer> in the web.config file.
 
- I ended up using Regular Expressions to match the url. For your example, the following code would get the string at the end of the url:
string path = context.Request.Url.AbsolutePath;
//Matches anything ending in '.../[any characters except /]' or '.../[any characters except /]/'
//and saves the [any characters] section in the group 'display'
Match match = Regex.Match(path, "/(?<display>[^/]+)/?$");
if (match.Success)
{
    string disp = match.Groups["display"].Value;
    //Display the string
}

AnswerRe: Nice articlememberLeo Stableford27 Jun '11 - 21:55 
Good tips. I got the name of the section in IIS7 from some other article, one about handlers probably. I still don't think I've ever used IIS7 (although the days are numbered when that will continue to be the case we're about to move to brand new servers where I am).
 
The Reg Exp thing is very nice. I love RegEx as well despite what mean people saying that if you have a problem and RegEx is the answer you now have two problems...
if (assumption > 0)
{
condescension = true;
}

GeneralGreat helpmemberPratiPhil5 Apr '11 - 4:00 
Hi Stableford,
Thanks.

I was wondering why I was unable to run my Rest in IIS but only in Web devlopper
Thanks for the solution
GeneralVerb type PUT and DELETE on IIS7 anonymous webmembermjehle11 Dec '09 - 4:27 
Hi
 
Have enyone a config example to allow verb PUT and DELETE on IIS7 on a anonymous web?
WebDav is restricted to autenticated users
Is is possible to allow that verb typs on a anonymous web in generally?
 
Any sugestions welcome!
 
All other thisgs are graeat - but on IIS7 a lot of things has changed - security first...
Generalvery helpful articlemember$unil Dhiman28 Oct '09 - 23:32 
Hi Stableford,
Thanks.
its very helpful article to implement URL Rewriting.
 

Regards,
$unil Dhiman
GeneralSOAP 1.2 and web.configmemberDean Hallman21 May '09 - 0:44 
This is something I've been trying to figure out. I've seen several articles suggesting ways to build RESTful webservices on .NET/C#.
 
What is throwing me is what is wrong with this option... Even with ASP2.0, you can add this to your web.config:
 
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
 
And your webservices are instantly RESTful (and still support SOAP 1.2). Even the response is stripped of the enclosing SOAP envelope.
 
So, why do what your article suggests, when this easier option is available? Is there some pros/cons I'm missing? Or some aspect of RESTful webservices that the above web.config setting doesn't give you?
 
Thanks,
Dean
QuestionRe: SOAP 1.2 and web.configmemberLeo Stableford21 May '09 - 1:45 
I think the big answer is that I didn't come across this advice anywhere till now.
 
The bigger question is if they are RESTful after this how are requests to be formatted?
 
I went to a lot of trouble to make my requests format:
 
www.someapi.com/REST/somequery/someinput
 
without extra ?s or =s or other things making the query look untidy or make it hard for a human to parse quite easily.
 
Not saying anything's "better" than anything else the question revolves around something being "fit for purpose".
 
if (assumption > 0)
{
condescension = true;
}

AnswerRe: SOAP 1.2 and web.configmemberDean Hallman21 May '09 - 6:34 
Thanks for your reply Leo. I'm not sure about the extra ?s or =s. What I can do is show you an example call for a webservice built this way...
 
Here is the automatic WSDL generated help file for a webservice in my app:
(NOTE: just the HTTP GET portion of it anyway)
 
----------------
 
/***REQUEST***/
GET /WebServices/DataPoints.asmx/GetDataByDateRange?dataPointId=string&startDate=string&numDays=string HTTP/1.1
Host: xyz.xyz.com
 
/***RESPONSE***/
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
 
<?xml version="1.0" encoding="utf-8"?>
<DataXml xmlns="http://xyz.com/ns/2007/">
<WaveHeight>string</WaveHeight>
<Period>string</Period>
<WaveDirection>string</WaveDirection>
<Spectral>string</Spectral>
<SpectralEnergySum>string</SpectralEnergySum>
<WindSpeed>string</WindSpeed>
<WindDirection>string</WindDirection>
</DataXml>
 
-----------------
 
So, it looks to me like the request is formatted the way you wanted. Do you see anything, umm... un-RESTful here? Smile | :)
 
You'll notice that the response strips out the soap envelop parent. This is a bit surprising to me. Since it is SOAP 1.2, I would have expected the HTTP GET to return a SOAP formatted response. Apparently, the "HttpGet" protocol trick in web.config file decides that if you use HTTP GET, you want REST and formats it accordingly. But that still seems like an odd assumption on .NET's part.
GeneralAlso visit this link for more info: http://softwareprogramminghelp.blogspot.com/memberdesaivishal112 Mar '09 - 14:56 
http://softwareprogramminghelp.blogspot.com/[^]
Questionreading REST requests?memberfetlock23 Oct '08 - 4:21 
Anyone have any links to articles about consuming REST requests?
 
I don't need to create a REST url, I need to read info from them, using both GET and POST, in asp.net 2.0.
GeneralHello again, Leo. Sorry, I may have to revise my rating...memberMMCompton2 Oct '08 - 10:18 
Hi Leo,
After playing around with your example a bit, I may have to reduce the rating I gave your article. There seem to be a few problems with it:
 
1. You ran this under the Visual Studio virtual host, correct? It "works" for me that way, but does not work if I run it from IIS 6 directly. The problem seems to be the same one I have been struggling with in my other attempts at a RESTful Web Service in C#: it's very hard to tell .NET to handle a URL that doesn't have a file extension (.ashx) somewhere. This isn't an issue when running inside of Visual Studio, but outside of VS it's a big, big problem.
 
2. Not to nit-pick, but there are some bugs in the code that prevent it, as listed, from producing the output you show.
 
- output.IndexOf("/exampleRestService/") is always going to return 0, so your "subPos" logic is never going to fire, so your output should be
 
Your Input Was: /exampleRestService/the end
 
(note, too, that The End won't be capitalized as your example shows)
 
If you change the > 0 to > -1, it works, but then the output will be
 
Your Input Was: he end
 
because subPos + 21 should be subPos + 20 (zero-relativity).
 
So, while I was initially very excited to find your article, it looks like I'm back to the drawing board. Thanks, anyway, for taking the time to write it.
 
-MMC
GeneralRe: Hello again, Leo. Sorry, I may have to revise my rating...memberLeo Stableford2 Oct '08 - 21:53 
Hi,
 
You don't think I trust the VS virtual host? No. I ran it on my local IIS, which isn't 6.0 I think it's 5.1. It ran fine under that. How strange that 6.0 should complain... I shall watch out for this when I actually do it as I believe our main web servers are both running 6.0.
 
Thanks for the heads up.
 
And the subpos thing works in my example because I was running the services in a subfolder of the main site, but of course not everyone is. Including me, live. Darn.
 
And as for the last thing that's because my original example was a different folder name and I was too lazy to actually check that my maths was right. By the end of the article other tasks start to press on your time. Hopefully I will soon get some time to revise. Ho hum.
 
if (assumption > 0)
{
condescension = true;
}

GeneralRe: Hello again, Leo. Sorry, I may have to revise my rating...memberMMCompton3 Oct '08 - 8:33 
Hi Leo,
Thanks for the message. You get your rating points back Smile | :)
 
The fix for IIS 6.0 seems to be to add a "WildCard ISAPI Filter" handler for the site in question. That is,
1- open IIS, right click, and select Properties for your site.
2- Click on Home Directory
3- Click on the Configuration button
4- Add a WildCard application map to the bottom window:
a- click Insert
b- browse to windows\microsoft.net\framework\your-version\
c- add aspnet_isapi.dll
d- make sure "Verify that file exists" is UNchecked
5- Save everything
6- RESTART IIS <- important
 
Your handler should now be called.
 
Thanks again for the article, and the response. Good luck!
-MMC
QuestionRe: Hello again, Leo. Sorry, I may have to revise my rating...memberiamstarbuck1 Jun '09 - 22:13 
I have a couple notes in response to MMC's info, and a couple questions:
- I have both IIS 5.1 and 6.0 so I'll check this on both.
- In v5.1 you Add at 4a, not Insert.
- In v5.1 you also need to specify an extension to create a wildcard map, so I used ".*" which generally seems to work.
 
"Generally" you say?
 
As MMC mentioned in his first note to Leo, paths don't work. Example:
http://localhost/virtdir/rest/foo returns with "Your input was foo". But:
http://localhost/virtdir/rest/foo/bar returns a 404.
 
Maybe this is just a v5 thing but does anyone know how to fix this? I have web.config set as follows:
path="rest/*" verb="*"
 
I figured if it found rest and passed 'foo' through, then anything else after rest/* should get processed too. (Buzzer sounds, wrong answer...)
 
Maybe the issue is with the virtual directory while we're setting the wildcard under Home Directory>Config for the web site? My handler is under virtdir/bin. So:
website/virtdir/web.config
website/virtdir/bin/handler.dll
 
As an aside, I didn't get how setting a wildcard to ASP_ISAPI.DLL was going to get my code to execute. Then I realized that the wildcard causes the web.config to get processed. The handler reference is read there, and the handler is then executed from the virtdir/bin. Am I missing something?
 
I don't think this was mentioned, but a developer would probably want to delete their Default.aspx too.
 
I know it's time to get with the rest of the world and move to VS2008, .NET 3.5 (almost 4), and IIS7. But for now I'm milking my environment for as much as I can.
 
Finally, a lot has been written about the differences between ISAPI filters, ISAPI Extensions, and all of the concerns about each. While a REST solution like this is cool and seemingly simple, like any web interface I think deployment in a production environment should only follow proper research.
 
TIA!
AnswerRe: Hello again, Leo. Sorry, I may have to revise my rating...memberLeo Stableford13 Apr '10 - 0:36 
Been a while but I've been having another look at this and you can get paths to work as long as you add a separate handler for each of the major path configurations.
 
e.g. if you have a simple REST service calculatormabob which will do all your basic adding, subtracting, multiplying and dividing using the webservice name: "arithmetic", forward slash,"add"/"subtract"/"multiply"/"divide", forward slash, variables then you would need the rest service to behave thus:
 
Input: localhost/arithmetic/add/2,3
Output: 5
 

Input: localhost/arithmetic/subtract/10,6
Output: 4
 

Input: localhost/arithmetic/multiply/2,3
Output: 6
 

Input: localhost/arithmetic/divide/6,2
Output: 3
 
(N.B. In reading for this reply I discovered, interestingly, that the basis for the REST standard was a particularly practical PhD thesis and the standard is mostly as described in this nugget of theory with some coder folklore thrown in to muddy the waters. So I have taken some suggestions mentioned in this blog post[^] to format the variables at the end of the REST URI)
 
Now obviously the service needs to know that you're accessing the arithmetic class so our:
 
<add type="MyWebServices.arithmetic,MyWebServices" path="arithmetic/*" verb="*"/>
 
Remains untouched; in fact in the test I did I made it so that if no specification of operation is given the service adds inputs together using a default case to the operator switch statement. In order to get it to correctly read the other URIs I also add:
 
<add type="MyWebServices.arithmetic,MyWebServices" path="arithmetic/add/*" verb="*"/>
<add type="MyWebServices.arithmetic,MyWebServices" path="arithmetic/subtract/*" verb="*"/>
<add type="MyWebServices.arithmetic,MyWebServices" path="arithmetic/multiply/*" verb="*"/>
<add type="MyWebServices.arithmetic,MyWebServices" path="arithmetic/divide/*" verb="*"/>
 
Thus we have an architecture for sending all directed requests to the one class library and let that sort out thereafter how to make sense of what we've put in. Some may say that this nest of handler variations could get unwieldy. If so I would argue your service design needs some rethinking, but maybe I'm wrong.
if (assumption > 0)
{
condescension = true;
}

QuestionHi Leo, good article [modified]memberMMCompton2 Oct '08 - 9:12 
.. it addressed exactly what I am trying to do with my REST API, i.e. get rid of the actual "myapi.ashx" reference in the URL.
 
My project compiles, but for some reason the handler isn't kicking in; I'm getting 404's for my URLs. There's no other configuration change I need to make, is there?
 
One question I have about the path= value. My urls are of the form
 
www.myapi.com/api/cars/?id=...
www.myapi.com/api/motorcycles/?id=...
 
etc.
 
Can my path simply be set to "api/", or do I need to register the handler separately for each "REST object", e.g.
 
...path="api/cars/"
...path="api/motorcycles/"
 
etc.
 
Thanks again for a very helpful article!
 
PS: I copy/pasted your article's code directly into a new solution, and get the same result (i.e. page not found). Some sort of environment issue?
 
modified on Thursday, October 2, 2008 3:50 PM

QuestionRe: Hi Leo, good articlememberBarry180324 Nov '09 - 7:46 
Hi MMC,
 
I've got a nearly identical situation as yours. I know it was a good few days ago but did you get a reply to your question or did you figure out a solution? I've been going in circles for a few days on this so any help greatly appreciated.
GeneralNice but...memberJacobs7629 Sep '08 - 21:33 
Nice article but you should check WCF in the framework 3.5, which implements all this out of the box.
 
http://msdn.microsoft.com/en-us/library/bb412169.aspx[^]
 
http://msdn.microsoft.com/en-us/library/aa395208.aspx[^]
 
Cheers
GeneralRe: Nice but...memberLeo Stableford29 Sep '08 - 21:38 
I heard that rumour. I don't have 3.5 at my workplace, won't be getting it anytime soon, and still need to employ this functionality.
 
If I were in my shoes I'd want someone to have written a handy article about how similar results could be achieved in 2.0...
 
Hmmm. Wink | ;)
 
if (signature == null)
{
imagination = 0;
}

GeneralRe: Nice but...memberJacobs7629 Sep '08 - 21:48 
I agree with you and this is why I commented it was a nice article. Though, I think that it is also important to tell the readers that .Net 3.5 does it out of the box. Maybe a word to say so at the beginning will remove the ambiguity. Wink | ;)
GeneralRe: Nice but...memberLeo Stableford29 Sep '08 - 21:57 
Ah, well, I never had it confirmed until now. Maybe I will. I do say it at the bottom of this article but I use the word "apparently" because I didn't know for certain. I will tweak later.
 
if (assumption > 0)
{
condescension = true;
}

JokeRe: Nice but...memberRed Feet1 Oct '08 - 4:51 
How can you tell from the picture above that he author has a nice butt?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 5 Nov 2008
Article Copyright 2008 by Leo Stableford
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid