/* This file implements an ASP.NET HttpHandler which allows services to be accessed as if they were folders, there by allowing more elegant service endpoints. ==Creating ASP.NET HTTP Handlers== HttpHandlers are very powerful as they allow you to exactly what the name suggests: they allow you to handle HTTP requests. Creating an HttpHandler is simply a matter of creating a class with the suffix 'HttpHandler' (that suffix isn't technically required, but it's a good idea to treat guidelines as rules) that implements the System.Web.IHttpHandler interface. This interface requires you to implement a Boolean property named 'IsReusable', which should just about always return true and a method named 'ProcessRequest' that accepts a 'HttpContext' object and returns void. Here is an example of what you would implement: public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { throw new Exception("The method or operation is not implemented."); } To use any HttpHandler that you create, you must register it in the httpHandlers section of the system.web section of your web.config. The element you create will specify, the HTTP verbs you want to handle as well as the path (which could be a specific 'virtual file') you want to handle and the name of the type. Here is an example of an HttpHandler named 'SampleServiceHttpHandler' in the 'Sample.Web.HttpExtensions' namespace that processes all GET and POST HTTP requests under the "services" path of your application that have a file extension of .ajax. As with most concepts in .NET, the best examples of .NET concepts are in .NET itself. HttpHandlers are no exception. Just as you are configuring your HttpHandlers in your web.config file, ASP.NET's "internal" HttpHandlers in its web.config file which you can find at the following location: %sysroot%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config In this file you will find that .aspx files are exist only by the HttpHandler named 'System.Web.UI.PageHandlerFactory'. Technically this is an HttpHandlerFactory, but that's a discussion for another lesson. The point is that what you are doing is basically extending ASP.NET in an appropriate manner, not hacking ASP.NET at all. What you are doing is exactly what it does. Usually when I explain ASP.NET to people for the first time I make the statement "ASP.NET doesn't exist" which leave people either angry or curious. What I mean by this is that ASP.NET is simply something a few DLLs registered to .NET via configuration files coupled with an ISAPI extension for IIS. It's just something built on .NET concepts just like what you would do. Your applications are in assemblies and you use configuration files the same as ASP.NET, so there's no need to be intimidating by the power of ASP.NET. As a side note, the fact that ASP.NET is a .NET application should shut up anyone whining that Microsoft is a hypocritical company that doesn't write any large-scale applications using .NET. This should also silence the hypocrites that refuse to create enterprise applications using .NET, but are willing to create enterprise-class web solutions using ASP.NET. Many people like to make their HttpHandlers process only files that end in .ashx. I find this to be an rather awkward and overly generic. This is basically the same thing as using an .aspx file, except for the fact that if it's an .aspx file, then people would expect it to be a web page. I recommend that when you create your HttpHandler that you choose an extension that will make sense for what your HttpHandler will do. In our examples here, we are making our services accessible via .ajax extension under the "services/" path. Technically this is called URL rewriting as what we are doing is taking a specific URL and internally rewriting it to something else. Usually you do URL rewriting from a logical path to a physical file, but here we are rewriting a logical path to a virtual file. Actually, this isn't really that unusual and comes in incredibly handy when you want certain pieces of information to be accessible by a virtual path. A very popular example of this is with articles and blog entries. Internally, you have have all kinds of articles or blog entries and you could easily create an ASP.NET page to allow people to access these resources in a way tat looks something like this: http://www.tempuri.org/articles/?articleId=92718 Lame. A much better way to do this is to use HttpHandlers to map a virtual path to this file. Using HttpHandlers, you could access the following URL like this: http://www.tempuri.org/articles/92718 or even by article title: http://www.tempuri.org/articles/article-title Taking this idea further, for some things it may be more efficient to actually have the HttpHandler do the output for you and not simply make it redirect. You can use the Response.Write method in an HttpHandler just as you can in an ASP.NET file and in classic ASP. The two Ajax services in this solution are both real .aspx files and this HttpHandler handles does a redirect to the .aspx files to complete the service request. That's enough theory for now; back to the lab. Having an HttpHandler shell that's registered to ASP.NET does little more than allow you to get an exception when you access the path the HttpHandler is registered to. For anything useful to happen you must put some implementation in the 'ProcessRequest' Method in your HttpHandler. The implementation of 'ProcessRequest' simply looks at the URL being accesses and then routes the service call based on the service requested. The URL is accessible via the 'HttpContext' object by the ASP.NET intrinic object 'Request'. This object also gives you the other ASP.NET intrinics as well as access to many other ASP.NET components. public void ProcessRequest(HttpContext context) { String url = context.Request.Url.ToString( ).ToLower( ); if (url.Contains("Services/JsonServiceEndpoint.".ToLower( ))) { Route("SampleJsonService", context); } else if (url.Contains("Services/AsyncSchemaServiceEndpoint.".ToLower( ))) { Route("SampleAsyncSchemaService", context); } } That's not the interesting part though. The fun stuff happens in the 'Route' method. private void Route(String route, HttpContext context) { String target = "~/services/" + route + ".aspx"; IHttpHandler h = PageParser.GetCompiledPageInstance(target, null, context); h.ProcessRequest(context); } This method created the target location of the actual file that will do the service processing, then gets a compiled instance of that page, and finally sends the current context object to it's 'ProcessRequest' object. When I was first creating HttpHandlers I thought this was rather strange. It looks like I'm doing something with a different HttpHandler, but the method I'm calling is called 'GetCompiledPageInstance'. So, is it a page or an HttpHandler? Well, it turns out that an ASP.NET ASPX page actually implements the IHttpHandler interface as well. So, the 'PageParser' class is actually compiling that ASPX page and the result is being stored in an object declared as an 'IHttpHandler'. Calling the 'ProcessRequest' method actually sends the context of the HTTP request we are currently working with to that page thus completing our work here. At least one person reading this will be confused about how you "compile an ASPX page". If you are from the classic ASP world or from the PHP world, then the concept of compiling a webpage seems incredibly foreign to you. Well, remember what I said about ASP.NET not being anything other that another .NET application? Well, that really comes out here. ASP.NET web forms are really nothing more than files that get compiled into a .NET assembly. Put another way, an ASPX page is basically just a C# (or whatever) class that gets compiled into an assembly just like all other .NET resources. Even the .aspx declarative page gets compiles into an assembly. As such, I don't even need to redirect to the .aspx files. The code behind for the .aspx documents is a real .NET instance class and as such I can instantiate it and use it from here. For a more detailed example of using HttpHandlers please see Minima, my ASP.NET blog engine that I use an ASP.NET training tool. In the publicly released version of Minima, I'm managing all blog entry pages, pages that show entries by month/year, and pages that show entries by label all by HttpHandlers... Minima only has one ".aspx" page and that's a the page that received all requests from the HttpHandler. As far as HttpHandler design, it's important to remember that the standard object-oriented principle of high cohesion apply to these as well. Your HttpHandler should do one thing and do it well. Don't create an HttpHandler that does all kinds of things simply for the sake of centralized management. Honestly, this example is bordering. It's still within threshold as it's purpose is to route all services. It's for routing services and for only routing services. It doesn't do anything else with services and it doesn't route anything other than services. If you find yourself wanting have something that routes all kinds of things or want something to centrally manage various HttpHandlers, then you want an 'HttpHandlerFactory', but that's a different topic. For more information on the universal HttpHandlerFactory Technique you can read my blog entry on the subject at http://www.netfxharmonics.com/2007/03/The-Universal-HttpHandlerFactory-Technique.aspx. */ using System; using System.Web; using System.Web.UI; namespace Sample.Web.HttpExtensions { public class ServiceHttpHandler : IHttpHandler { public Boolean IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { String url = context.Request.Url.ToString( ).ToLower( ); if (url.Contains("Services/JsonServiceEndpoint.".ToLower( ))) { Route("SampleJsonService", context); } else if (url.Contains("Services/AsyncSchemaServiceEndpoint.".ToLower( ))) { Route("SampleAsyncSchemaService", context); } } private void Route(String route, HttpContext context) { String target = "~/services/" + route + ".aspx"; IHttpHandler h = PageParser.GetCompiledPageInstance(target, null, context); h.ProcessRequest(context); } } }