Thursday, July 4, 2013

ASP.Net HTTPHandler by example

Due to the size and scope of HTTPHandlers, I am going to be creating a mini-part series on them.  It would be easy to say that you could literally write books on Handlers – many have.  If you decided to put money towards these periodicals what you will find is some heavy technical termed books that will require you to have a geek dictionary available just for the purpose of making the author sound intelligent and/or knowledgeable (you pick).  For those of us that are redneck geeks, I like to keep things simple (I have thought once or twice about writing technical books for redneck geeks), If I wanted to speak big terms that no one understands – I would have become a doctor.

What are HTTPHandlers?

In my mind, a HTTPHandler is a GPS and translator all in one.  It is the traffic cop that tells you how to get somewhere and then an instruction manual on how to (do something) when you get there.  So let’s take an example of a forest.  There are thousands of different size boxes populating this forest.  There are some that are made of cardboard, wood, metal ..etc..
So a handler will tell you where a (specific) wooden box is and what you need to do in order to open it to get to its contents (as opposed to a cardboard or metal box).  Once opened, there are many boxes inside of it that are made of wood, metal, cardboard etc.. And if it is made of a different type of material there is another handler to assist you with this.
Now there is a global type handler for IIS that takes care of the most common types of objects (I.E. Images, aspx Pages, Text Files …etc…) that is in the ISAPI (Internet Server API) assembly.  They are mapped inside IIS.  The most common type objects are automatically mapped in the Handler Mappings section of the site.  You can add your own through IIS or in the web.config of the website. 
image
So, in theory, we should be able to create ANY kind of extension we want as long as we have a handler to tell IIS how to present the object.  Let’s put this into practice by taking a common extension (RSS) and creating our own extension (supercalifragilisticexpealodotious) -
image
Notice in my URL that the extension is .supercalifragilisticexpealodotious.  No, there is not a file in the project named chad.supercalifragilisticexpealodotious.  Why? This is due to the fact that objects can be virtual.  There is not even a .rss file -
image
So what is really going on here?  Well I wrote a HTTPHandler that said when it got a request for supercalifragilisticexpealodotious objects to go out and get data from a database and then format it in a rss file virtually and then present that to the requestor (the browser).  Think this is complicated?  Well you won’t once we are done with the article.  For reference, you might want to look at my RSS articles -
 http://chadcarter30101.blogspot.com/2012/08/return-of-cheese-syndicatecreating-rss.html
and
http://chadcarter30101.blogspot.com/2012/05/secrets-of-cheese-syndicate-consuming.html
There is also something else to remember, the URL of a browser only defines an endpoint, an address.  The way it works is that you put a URL address in a browser to a page on the internet.  So your browser issues a GET command from the web server.  The web server goes and gets the page (through the Handler).  Then behind the scenes, the Handler sends back the text (usually in the form of HTML) back to the browser along with a list of objects that exist in the Page (for instance all the images).  Your browser then sends follow-up GET commands for each and every image (which on the server the request goes through the Image Handler – in this instance).  So in fact, when you visit a webpage your browser makes several to hundreds of trips to the server to get all the content of a single web page.  The server relies on the browser then to take those objects and construct the page correctly on the client side so that it is readable and makes sense to humans.  For every object that makes up a web page there is a GET command issued from the browser.  If you have ever looked at detailed web server logs, you can see this in action.  So in concept, you don’t ever look at a “live” version of the web page.  You are looking at cached version of the web site that is required so that the browser can build whatever page you are looking at.  Look at the cache folder for your browser to see this in action.  AJAX and other technologies has shaken this up a little bit, but the principle remains the same.  This is a very simplistic explanation of the very complex interaction between Client/Server in the HTTP protocol.

Let’s Get Started -

Start up VS2010 and create an empty ASP.Net Web Site (This article assumes you know how to create an ASP.Net Web Project).  Once there, you need to add some folder framework to house our Handler.  Right Click your solution name | Add ASP.Net folder | App_Code.  Now we need to add two folders to the App_Code folder.  Right Click App_Code | New Folder | Name it BLL.  Repeat the previous step and name the second folder DAL.
You know where we are going next.  The Database.  We don’t need to do anything else in VS2010 until we know what data we are going to be getting.  In this case we will be creating a Blog database.  Which I named….. Blogger.  Fire up SSMS (Sql Server Management Studio) and Create the database (This article assumes you know how to create a database in SSMS).
In the Blogger database, add a table named blogs with the following columns -
image
You can use the following script -
USE [Blogger]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[blogs](
    [blog_id] [int] IDENTITY(1,1) NOT NULL,
    [blog_title] [varchar](50) NULL,
    [blog] [varchar](max) NULL,
    [blog_dt] [datetime] NULL,
    [blog_creator] [varchar](50) NULL
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO


With that made, we need to create a SP (Stored Procedure) that will retrieve all our blogs.  I named this dbo.usp_blogs_select_all -




USE [Blogger]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO-- =============================================
-- Author:        Chad Carter
-- Create date: 11/x/2012
-- Description:    Selects all blogs 
-- =============================================ALTER PROCEDURE [dbo].[usp_blogs_select_all] 
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
            blog_id
        ,    blog_title
        ,    blog
        ,    blog_dt
        ,    blog_creator
    From
        dbo.blogs
END


Now populate a couple entries by right clicking your table and choosing Edit Top 200 rows.  Now highlight the SP Name and then press execute -

image





And you should see the entries you just created -

image


We are now done with SSMS.  Lets go back to VS2010.


First thing we need to do is add our connectionString to the web.config (This article assumes you know how to add a connectionString to your web application). 
So now what we need to do is work our way backwords from the datasource to the presentation layer (in this case it will be virtual).  Now that we have SSMS ready to handle our RSS requests with the building of our tables and SPs, we need to fetch this data.  Right Click the App_Code/Handler/DAL folder | Add | Class.  Name this class RSSHandler.vb.


Like all the other examples in my blog, we start with the declarations -




Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration.WebConfigurationManager


Now we need to create our Namespace for the DAL -



Namespace Handler.DAL
    Public Class RSSHandler    End Class
End Namespace


Our first region is the “Methods” region and is the method that returns all our blogs from the datasource -



#Region "Methods"
   Public Function getBlogRSSFeed() As List(Of Handler.BLL.RSSHandler)
     Dim cn As New SqlConnection(ConnectionStrings("blogger").ToString())
     Dim cmd As New SqlCommand("dbo.usp_blogs_select_all", cn)
     cmd.CommandType = System.Data.CommandType.StoredProcedure
     cn.Open()
     Dim reader As SqlDataReader = cmd.ExecuteReader()
     Dim RssFeed As List(Of Handler.BLL.RSSHandler) = ConvertReader(reader)
     cn.Close()
     Return RssFeed
   End Function
#End Region


If you have ever read any of my blogs in the past, you can guess what the next region will be – A “Helper Methods” region.  These are regions that serve to support the major Methods in the class.  If you notice, call a ConvertReader method when Dimensioning the RssFeed variable.  This is that method -



#Region "Helper Methods" Public Function ConvertReader(ByVal reader As SqlDataReader) As _
                                            List(Of Handler.BLL.RSSHandler)
  Dim RssFeed As List(Of Handler.BLL.RSSHandler) = New _
                                           List(Of Handler.BLL.RSSHandler)
  If (reader.HasRows()) Then
   While reader.Read()
  RssFeed.Add(New Handler.BLL.RSSHandler(reader("blog_title").ToString(), _
                                                       reader("blog_dt"), _
                                               reader("blog").ToString(), _
                                                 CInt(reader("blog_id")), _
                                                                      "", _
                                           getURL(CInt(reader("blog_id")))))
                End While
            End If
            Return RssFeed
        End Function


You will once again notice that we are calling another method called getURL, this is also a helper method -



Public Function getURL(ByVal cd As Integer) As String
 Dim context As System.Web.HttpContext = System.Web.HttpContext.Current()
 Dim baseURI = context.Request.Url.Scheme & "://" + _
                                           context.Request.Url.Authority & _
                          context.Request.ApplicationPath.TrimEnd("/") + "/"
 Return baseURI.ToString() & cd.ToString() & "/Blog"End Function
#End Region
    End Class
End Namespace


So here we have the entire Data Access Layer for our Rss Handler -




Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration.WebConfigurationManager
Namespace Handler.DAL
    Public Class RSSHandler#Region "Methods"Public Function getBlogRSSFeed() As List(Of Handler.BLL.RSSHandler)
 Dim cn As New SqlConnection(ConnectionStrings("blogger").ToString())
 Dim cmd As New SqlCommand("dbo.usp_blogs_select_all", cn)
 cmd.CommandType = System.Data.CommandType.StoredProcedure
 cn.Open()
 Dim reader As SqlDataReader = cmd.ExecuteReader()
 Dim RssFeed As List(Of Handler.BLL.RSSHandler) = ConvertReader(reader)
 cn.Close()
 Return RssFeed
End Function
#End Region
#Region "Helper Methods"Public Function ConvertReader(ByVal reader As SqlDataReader) As _
                                           List(Of Handler.BLL.RSSHandler)
 Dim RssFeed As List(Of Handler.BLL.RSSHandler) = New _
                                           List(Of Handler.BLL.RSSHandler)
 If (reader.HasRows()) Then
  While reader.Read()
RssFeed.Add(New Handler.BLL.RSSHandler(reader("blog_title").ToString(), _
                                                     reader("blog_dt"), _
                                             reader("blog").ToString(), _
                                               CInt(reader("blog_id")), _
                                                                    "", _
                                         getURL(CInt(reader("blog_id")))))
   End While
  End If
  Return RssFeed
 End Function
 Public Function getURL(ByVal cd As Integer) As String
  Dim context As System.Web.HttpContext = System.Web.HttpContext.Current()
  Dim baseURI = context.Request.Url.Scheme & "://" + _
                                         context.Request.Url.Authority & _
                        context.Request.ApplicationPath.TrimEnd("/") + "/"
  Return baseURI.ToString() & cd.ToString() & "/Blog"
 End Function
#End Region
    End Class
End Namespace


So we have been creating a list of an object that we have not even created yet.  Lets go create


that object.

Right click on your App_Code/Handler/BLL folder | Add New Item… | Class.  Name this class (again) RSSHandler.vb

First things first, we have no declarations so we need to get our namespace in place so that the class name does not become ambiguous -




Namespace Handler.BLL
    Public Class RSSHandler    End Class
End Namespace

Now we need to create our properties region that will hold the values for our list.  Obviously, these properties will need to be made Public -




#Region "Properties"
        Public Property Title As String
        Public Property PubDate As DateTime
        Public Property Summary As String
        Public Property CD As Integer
        Public Property Author As String
        Public Property URL As String
#End Region




With our properties created, we can hold the values of the list be we have no way to populate them.  This is done with the constructors region -



#Region "Constructors"
        Public Sub New()
        End Sub
        Public Sub New(ByVal title As String, _
                       ByVal pubdate As DateTime, _
                       ByVal summary As String, _
                       ByVal cd As Integer, _
                       ByVal author As String, _
                       ByVal url As String)
            Me.Title = title
            Me.PubDate = pubdate
            Me.Summary = summary
            Me.CD = cd
            Me.Author = author
            Me.URL = url
        End Sub
#End Region




Now we can physically create a list in the object that can hold values.  Think of a List (or in code it is called by List Of(T)) as a flat table or an Excel spreadsheet without all that formula gobbly-gook.  It literally is a list of items.  Finally, we need a method to call the DAL to populate our list -



#Region "Methods"
        Public Function getBlogRSSFeed() As List(Of Handler.BLL.RSSHandler)
            Dim handler_DAL As New Handler.DAL.RSSHandler
            Return handler_DAL.getBlogRSSFeed()
        End Function
#End Region




Fairly simple and straight forward method.  The entire listing of the BLL -



Namespace Handler.BLL
    Public Class RSSHandler#Region "Properties"
        Public Property Title As String
        Public Property PubDate As DateTime
        Public Property Summary As String
        Public Property CD As Integer
        Public Property Author As String
        Public Property URL As String
#End Region
#Region "Constructors"
        Public Sub New()
        End Sub
        Public Sub New(ByVal title As String, _
                       ByVal pubdate As DateTime, _
                       ByVal summary As String, _
                       ByVal cd As Integer, _
                       ByVal author As String, _
                       ByVal url As String)
            Me.Title = title
            Me.PubDate = pubdate
            Me.Summary = summary
            Me.CD = cd
            Me.Author = author
            Me.URL = url
        End Sub
#End Region
#Region "Methods"
        Public Function getBlogRSSFeed() As List(Of Handler.BLL.RSSHandler)
            Dim handler_DAL As New Handler.DAL.RSSHandler
            Return handler_DAL.getBlogRSSFeed()
        End Function
#End Region
    End Class
End Namespace




So now we have hit the meat and potatoes of a Handler.  In order to code a Handler, it must implement the IHTTPHandler interface.  I would feel a little regret if I didn’t dive into Interfaces a little bit.  An Interface is a contract of sorts between two objects.  By definition, an interface is a method of allowing two dissimilar entities to communicate.  For instance, your keyboard would be an interface between you and your computer.  A television remote would be an interface between you and the television.  A modem is an interface between two computers… etc…

This being said, there is a contract between all these objects.  For instance, in the case of the remote and television, you accept that the method to change the channel is by using the remote.  The television also accepts that when it receives the request from the remote, it will change the channel.  Of course this gets more complicated in the programming world, but you get the basic idea. 
Now conceptually, let’s apply this to programming (remember- conceptually)… So you have a website that uses User Controls in a side panel.  You have a calendar User Control or optionally, if the user wants to use a less visual manner, you offer a date control User Control that just has a drop down date control in it.  It doesn’t matter which User Control the user has selected, both of them change which appointments display in the appointment side panel section.  Well if you look at the controls themselves, a calendar control uses a .selecteddate property to get the date that user has selected and a drop down date control uses a .value property.  We could do a lot of programming to cover both instances of where to get the value from depending on the selected control to populate the appointments control – OR – we could use an interface.  When you use an interface it MUST implement the properties and methods of that interface (on both ends). 
It’s kind of like saying when they built the television you just bought, they didn’t build it expecting you to change the channel using your microwave.  They added the functionality to use the remote.  Thus, the general public has accepted the contract between you and the television that you will use the remote to change the channel and not the microwave.


How do you tell the compiler that you are using an Interface?  With the Implements keyword.  If you implement an interface, you MUST implement the entire interface (unlike the case of an abstract class).  Thus, under your class definition you will have an Implements keyword along with any method, property or variable that are in the interface.  Lets get started.  The first thing we need to do is.. You guessed it, declarations -



Imports System.ServiceModel.Syndication




This Syndication namespace is needed to provide help with creating the RSS.  I t has nothing to do with the interface.  Next? Namespacing and class declaration (which includes our interface) -



Namespace Handler
    Public Class RSSHandler
        Implements IHttpHandler

    End Class
End Namespace




If done correctly, the Implements IHttpHandler declaration should produce an error saying you haven’t implemented the IsReusable and ProcessRequest.  There is a property in the Interface called IsReusable that MUST be implemented in order to implement the interface.  How do you think we are going to accomplish this?  Again, with the implements keyword -



Public ReadOnly Property IsReusable As Boolean Implements _
                                          System.Web.IHttpHandler.IsReusable
            Get
                Return False
            End Get
        End Property




Ok, this can get a little complicated, but you do not need to name the property the same name that it is implementing from the Interface.  I could have simply called the property IsR or ChadsExtremelyLongPropertyName…. AS LONG AS I HAVE THE IMPLEMENTS KEYWORD.  Notice in this case, it points to the IsReusable definition in the Interface.  This is actually what fulfills the “contract” and points to the property in the Interface.

You are still getting that annoying error about not implementing ProcessRequest.  Again, you can name the method anything you want, as long as you implement System.Web.IHttpHandler.ProcessRequest -




Public Sub ProcessRequest(context As System.Web.HttpContext) Implements _
                                      System.Web.IHttpHandler.ProcessRequest
 Dim rssURI As New Uri("http://chadcarter30101.blogspot.com")
 Dim rssName As New String("Chad Carter's ASP.Net Blog")
 Dim rssDescription As New String("My blog about ASP.Net with code samples")
 Dim rssCopyright As New String("Copyright (c) 2013")
 Dim rssContributor As New SyndicationPerson("chadcarter30101@gmail.com", _
                       "Chad Carter", "http://chadcarter30101.blogspot.com")
 Dim rssLink As New SyndicationLink(New _                     System.Uri("http://www.Microsoft.com"), "alternate", _
                                             "Microsoft", "text/html", 1000)
 Dim rssObject As New Handler.BLL.RSSHandler
 Dim rssFeeds As List(Of Handler.BLL.RSSHandler) = rssObject.getBlogRSSFeed()
 Dim rssItems As New List(Of SyndicationItem)
 For Each rssFeed As Handler.BLL.RSSHandler In rssFeeds
   Dim rssItem As New SyndicationItem
   rssItem.Title = New TextSyndicationContent(rssFeed.Title)
   rssItem.Summary = New TextSyndicationContent(rssFeed.Summary)
   rssItem.PublishDate = New System.DateTimeOffset(rssFeed.PubDate)
   rssItem.BaseUri = New System.Uri(rssFeed.URL)
   ' ..Add Additional Items here..
   rssItems.Add(rssItem)
 Next
 Dim feed As New SyndicationFeed(rssName, rssDescription, rssURI)
 feed.Copyright = New TextSyndicationContent(rssCopyright)
 feed.Authors.Add(rssContributor)
 feed.Generator = "RSS HTTPHandler"
 feed.Links.Add(rssLink)
 'feed.ImageUrl = New System.Uri("http://someURLHere.com")
 feed.LastUpdatedTime = DateTime.Now()
 feed.Language = "en-us"
 feed.Items = rssItems

 context.Response.Clear()
 context.Response.ContentType = "application/rss+xml"
 Dim format As Rss20FeedFormatter = New Rss20FeedFormatter(feed)
 Dim RSSWriter As System.Xml.XmlWriter = _
               System.Xml.XmlWriter.Create(context.Response.Output, Nothing)
 format.WriteTo(RSSWriter)
 RSSWriter.Flush()
 context.Response.End()
End Sub




This is a fairly simple, straight-forward RSS feed.  If you have trouble understanding what is going on, you can refer to my previous post -

http://chadcarter30101.blogspot.com/2012/08/return-of-cheese-syndicatecreating-rss.html


Now that we have completed our obligation on the “contract” for the interface, we should no longer see the error. 
The system still has no idea what you want it to handle, we take care of that in the web.config.  So open it up and in the system.web you need to add a httpHandlers section like so -




<system.web>
    <compilation debug="true" strict="false" 
                 explicit="true" targetFramework="4.0"/>
    <httpHandlers>
      <add path="*.supercalifragilisticexpealodotious" 
                 type="Handler.RSSHandler" verb="GET"/>
    </httpHandlers>
  </system.web>




Type is the namespace where the system can the handler.  Additionally, you created this outside of a website, say in an assembly then you would list the namespace, assembly like so -

…. type=”Handler.RssHandler, ChadsAssembly”  ……


Ofcourse, you would need to reference it into your solution. 


Now our example only works if you want to use the supercalifragilisticexpealodotious extension.  If you want to make it use the RSS extension -



<add path="*.rss" type="Handler.RSSHandler" verb="GET"/>




Or if you only want it for a specific rss, in the instance you have more than one blog on the site -



<add path="specific.rss" type="Handler.RSSHandler" verb="GET"/>




Finally, what if you want it for more than one extension type?  You comma deliminate them -



<add path="*.rss, *.chad, chad.superhero" type="Handler.RSSHandler" 
                                          verb="GET"/>




Fricken awesome!


Happy .Netting…

3 comments:

  1. more about httphandlers and moudles and performance

    whats about
    http://mrojas.ghost.io/tracking-session-size-in-asp-net-applications/
    https://github.com/orellabac/OnlineSessionModule

    source code in blog not view right, IMHO. difficult view

    ReplyDelete

  2. http://www.endurasoft.com/blog/post/implementing-an-asynchronous-httpmodule.aspx Async HttpModule has more scabality not more

    performance per se, isn't?

    ReplyDelete

  3. Real world async httpHandler

    ErrorLogDownloadModule is frankly the module that would benefit the most from being asynchronous as it can take a while to run as it serves to download. To that extent, it is already implemented in an asynchronous manner by virtue of implementing IHttpAsyncHandler . It doesn't used tasks/TPL but it's asynchronous alright.
    https://github.com/elmah/Elmah/blob/1x/src/ErrorLogDownloadHandler.cs#L39

    However, that alone does not guarantee asynchronous execution semantics. The ErrorLog implementation also needs to support (non-blocking) asynchronous retrieval and the only one that does is SqlErrorLog .
    https://github.com/elmah/Elmah/blob/1x/src/SqlErrorLog.cs

    It's not about performance per se. Instead, it's about returning threads to the pool while waiting for an I/O so that those thread are more available to service other requests meanwhile. It will affect your scalability.

    ReplyDelete