Welcome to Professional ASP.NET - Chris Love's Official Blog Sign in | Join | Help

Chris Love's Official Blog - Professional ASP.NET

Chris Love's Helpful tips, tricks and pragmatic development knowledge for the ASP.NET world.
Add to Technorati Favorites


Join me at CodeStock
Creating a Background Thread to Log IP Information

Trolling the ASP.NET forums this afternoon I came across this thread about creating a custom httpHandler to log IP information to a database. What the requester was trying to do was call a remote site’s (www.ShowMyIP.com) REST API to get information about the visitor’s IP address, such as location and store it in the database. Unfortunately I cannot seem to find documentation about the REST API, but that is not important to this example.

I responded that using an httpHandler in this case would not actually solve the developer’s issue, instead I think it could actually harm the performance of his site. This is because what he is doing is actually calling the handler from the page, like an image file. This actually ties up one of the two threads that browsers open up to retrieve content from a web site upon request. If you go to one of my presentations on front-end performance you will learn more about this. The goal being to reduce the number of objects being requested to build the actual page in the browser.

To actually solve the problem of retrieving the detailed information about the visitor’s IP address is to create a background thread to make the call to the REST API and process the resulting XML. I am not going to get into the details of processing the XML content in this case, nor am I going to store it in a database. Instead I am going to just store the information in a text file corresponding to the IP address, [IPAddress].txt.

The first step is to create a method to log the IP information. In this case I decided to pass in a custom class as its only parameter. This is because it is hard to transport the IP and physical path to use in the logging operation. The method calls the ShowMyIp.com service by creating a new XDocument object via the Load method. The method has to be shared (static in C#) to be able to work in a thread.

Public Shared Sub StoreIPInfo(ByVal args As StoreIpArgs)

    Dim doc As XDocument = _
    XDocument.Load("http://www.showmyip.com/xml/?ip=" & args.UserHost) 
    Dim lPath As String = Path.Combine(args.AppPath, _
        String.Format("{0}.txt", args.UserHost))

    If File.Exists(lPath) Then
        File.Delete(lPath)
    End If

    My.Computer.FileSystem.WriteAllText( _
        lPath, doc.ToString(), True)

End Sub

It then logs the XML content to the file system, in this case in the root of the Web site. Again this is a simple demonstration.

The next step is to create the thread and perform the normal page operations. In this case you create a new thread and pass the address of the method to be executed by the new thread. In this case it is the StoreIPInfo method created in the previous step.

    Protected Sub Page_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.Load

        Dim t As New Threading.Thread(AddressOf StoreIPInfo)
        Dim args As New StoreIpArgs
        args.AppPath = Request.PhysicalApplicationPath
        args.UserHost = Request.UserHostAddress
        t.Start(args)

        ltlIPAddress.Text = Request.UserHostAddress.ToString()

    End Sub

The next thing to do is to create a new instance of a StoreIpArgs class, a custom class I created to hold the value of the user’s IP address and the path to store the log file. This class contains two public properties, UserHost and AppPath.

Public Class StoreIpArgs

    Private _userHost As String
    Public Property UserHost() As String
        Get
            Return _userHost
        End Get
        Set(ByVal Value As String)
            _userHost = Value
        End Set
    End Property

    Private _appPath As String
    Public Property AppPath() As String
        Get
            Return _appPath
        End Get
        Set(ByVal Value As String)
            _appPath = Value
        End Set
    End Property

End Class

Both of these values can be gathered from the Request object, the UserHostAddress and PhysicalApplicationPath properties. The new new StoreIPArgs object is passed to the Start method of the thread object. This will pass the object to the method, StoreIPInfo, designated when the thread was instantiated.

The other pieces of the page simple output the IP address to the page along with the time the page was rendered to the browser. This is done in the PreRender event handler.

    Private Sub Page_PreRender(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.PreRender
        ltlIPAddress.Text = String.Format("{0} - {1}", _
                        ltlIPAddress.Text, TimeString.ToString)
    End Sub

I did this so I could test to make sure the page was actually rendered before the background thread was completed. I found the call to the external web service was relatively quick and was making it hard to see if it was really completing after the page was rendered. If you download the sample project there should be a couple of breakpoints set so you can step through and see when the methods are called. You will see the PreRender event gets called before the IP information is logged to the file.

This example performs the operation in a page. But I really think this will work great in a custom httpModule. I may revisit the topic, but I think you could just modify my Blog on creating a background thread in a custom httpModule, but instead of just doing it in the Init method you would wire up an event handler to the BeginRequest event and repeat the same basic steps there.

The resulting XML is logged and starts off like the following snippet:

<ip_address>
  <lookups>
    <lookup_ip>127.0.0.1</lookup_ip>
    <lookup_ip_long>2130706433</lookup_ip_long>
    <lookup_ip_is_valid>yes</lookup_ip_is_valid>
    <lookup_ip_is_private>yes</lookup_ip_is_private>
    <lookup_is_ipv6>no</lookup_is_ipv6>
    <lookup_host>private</lookup_host>
    <lookup_reverse_host></lookup_reverse_host>
    <lookup_isp>provided to subscribers only</lookup_isp>
    <lookup_org>provided to subscribers only</lookup_org>

So while it might look like a custom httpHandler is the answer to something like this, it really is not. It would actually cause the page to not render efficiently and probably cause it to render much slower because it would hog one of the browser’s download threads. A background thread can make this process more efficient without affecting the rendering of your page in the visitor’s browser.

Source Project

Share this post :
Posted: Monday, August 04, 2008 7:36 PM

by Chris Love

Comments

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# August 4, 2008 7:41 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS