Multiple Threads in ASP.NET (VB)

I’ve been using ASP.NET for years and never thought about the usage of threads within the .NET site. It’s not that I thought it was impossible, it’s just that I hadn’t even considered it.

Earlier this year I did a few experiments with threads and more recently I built upon this and put a generic task manager system in an eCommerce platform I am building, after seeing how nopCommerce implemented one. The first task I built upon this was a thread which loops every 10 minutes and cleans out any inactive customer account sessions from the database.

The easiest way to do this would have been a simple SQL Server Stored Procedure and use SQL Agent to fire it every 10 minutes. The first problem with this is that I don’t necessary have access to full SQL Server and the express version doesn’t have SQL Agent. The second problem is down to management and deployment - I wanted a self contained application that doesn’t require the setting up of SQL Agent tasks and the maintainable of these outside of the main application.

So my dream…

  • I wanted a generic task system so multiple types of task thread can be programmed and controlled by the same system. 
  • I wanted an area in the administration side of website that could see the state of the tasks, and a log of the task activities.
  • I wanted the ability to start and stop these tasks in the administration area.

The main layout of my solution is based on the nopCommerce implementation with some tweaks:

  • Create a TaskManager class using the Singleton pattern
  • Create a TaskThread class that will be inherited from with the different types of task threads required
  • On Application_Start create an instance of this class
  • With the initialization of this class it create instances of the task threads (inheriting from TaskThread) and adds them to a field in this single instance so they can be tracked and given start/stop commands
  • Application_Start then calls a Start function in the TaskManager which loops through each TaskThread and starts them
  • Application_End calls a Stop function in the TaskManager before calling dispose

One problem I had to get past was that the .NET platform might be ran on an IIS Web Farm scenario with multiple worker threads. This would in turn lead to double the amount of threads. To get around this I used a Mutex so that only one work thread can have the tasks running at a time. Here is the main code:

The TaskManager Class

Imports System.Threading

Namespace Tasks

    Public Class TaskManager

        Private Shared ReadOnly _TaskManager As New TaskManager()

        Private ReadOnly _TaskThreads As New List(Of TaskThread)

        Private Sub New()
        End Sub

        Public Sub Initialize()

            If Configuration.getBooleanSetting("Tasks.CleanOldSessions.Enabled", True) Then
                _TaskThreads.Add(New TaskCleanOldSessions())
            End If

        End Sub

        Public Sub StartTaskManager()

            ' Use a Mutex so only one instance of the task manager can be run at a time '
            Dim createdNewMutex As Boolean = True
            Using mut As New Mutex(True, "makitTaskManager", createdNewMutex)

                If createdNewMutex Then

                    ' Start each thread created during initialize in turn '
                    For Each taskThread As TaskThread In _TaskThreads
                        taskThread.InitTimer()
                    Next

                    ' Stop the garbage collecting the mutex, which would be bad '
                    GC.KeepAlive(mut)

                End If

            End Using

        End Sub


        Public Sub StopTaskManager()

            Using mut As New Mutex(True, "makitTaskManager")

                For Each taskThread As TaskThread In _TaskThreads
                    taskThread.Dispose()
                Next

                ' The mutex can now be released, due to it not being used any more '
                mut.ReleaseMutex()

            End Using

        End Sub


        Public Shared ReadOnly Property Instance() As TaskManager
            Get
                Return _TaskManager
            End Get
        End Property

    End Class

End Namespace

The TaskThread Class

Imports System.Threading

Namespace Tasks

    Public MustInherit Class TaskThread
        Implements IDisposable

        Protected _timer As Timer
        Protected _disposed As Boolean
        Protected _started As DateTime
        Protected _isRunning As Boolean

        Protected Sub New()
        End Sub

        Protected Overridable Sub Run()
            _started = DateTime.Now
            _isRunning = True

            ' Overriden class does the actual task here before setting isRunning to false '

        End Sub


        Public Sub InitTimer()

            ' Create a timer which executes the timer handler sub between intervals '
            If _timer Is Nothing Then
                _timer = New Timer(New TimerCallback(AddressOf TimerHandler), Nothing, Interval, Interval)
            End If

        End Sub


        Private Sub TimerHandler(ByVal state As Object)

            Try

                ' Stop the timer whilst the task runs, otherwise if the task is slow then will get an overlap '
                _timer.Change(-1, -1)

                ' Run the actual task '
                Run()

                ' Now reset the timer back to the interval again, so it will execute again in 10 minutes '
                _timer.Change(Interval, Interval)

            Catch ex As Exception

                Logging.logAsError("Exception during execution of task. Caught error and task will be stopped until problem solved. Info: " & ex.ToString)

                _timer.Dispose()
                _timer = Nothing
                _disposed = True

            End Try

        End Sub

        Public ReadOnly Property Started() As DateTime
            Get
                Return _started
            End Get
        End Property

        Public ReadOnly Property IsRunning() As Boolean
            Get
                Return _isRunning
            End Get
        End Property

        Public ReadOnly Property Interval() As Integer
            Get
                Return 600000 '10 Minutes - could be configurable '
            End Get
        End Property

        Public Sub Dispose() Implements IDisposable.Dispose
              If (_timer IsNot Nothing) AndAlso Not _disposed Then
                 SyncLock Me
                     _timer.Dispose()
                     _timer = Nothing
                     _disposed = True
                 End SyncLock
             End If
         End Sub
      End Class
  End Namespace

The actual example task thread class

Namespace Tasks

    Public Class TaskCleanOldSessions
        Inherits TaskThread

        Protected Overrides Sub Run()
            MyBase.Run()

            Execute()

            _isRunning = False

        End Sub

        Private Sub Execute()

            ' Get the number of hours in the past to delete sessions older than this '
            Dim hoursOldToDelete As Single = Configuration.getSingleSetting("Tasks.CleanOldSessions.HoursOld", 48)

            ' Pass in the current date with the number of hours in the past removed so the sessions are deleted from the DB '
            ' This returns a list of the session IDs that have been logged out for reporting purposes. '
            Dim loggedOutSessions As List(Of String) = AccountRepository.clearOldAuths(Now().AddHours(-hoursOldToDelete))

            ' If some people were logged out then log the fact '
            If loggedOutSessions IsNot Nothing Then

                Logging.logAsInfo("Logged out " & loggedOutSessions.Count & " sessions.")

                For Each curHUID As String In loggedOutSessions
                    Logging.logAsInfo("Logged out session " & curHUID)
                Next

            End If

            ' Make sure the memory is released '
            If loggedOutSessions IsNot Nothing Then
                loggedOutSessions.Clear()
            End If
            loggedOutSessions = Nothing
            hoursOldToDelete = Nothing

            GC.Collect()

        End Sub

    End Class

End Namespace

That’s the basic premise, it has so far worked well. With the only major problem being a memory leak that was cured by implementing IDisposable in the threads and making sure all variables were cleared up before garbage collecting.

The threads are currently logging to text files, but they will be extended to link in with the admin area of the site so the history and status can be seen easily.

MVC 4 and Mobile Support

I have been doing a lot of work on my ASP.NET MVC web platform, as well as doing work on nopCommerce v2. The more I use ASP.NET MVC then the more I love it.

Within the next few months I will be moving over some high traffic ecommerce stores onto my platform, and some of these have mobile versions. My plan has been to setup a seperate copy of the web project but with very lightweight views. This would mean they could sit on the same data layer and I have no need to do much in terms of programming.

This was until I read the release notes for ASP.NET MVC 4:

http://www.asp.net/learn/whitepapers/mvc4-release-notes

Definitely worth a read, but the point I am concentrating on here is having mobile support built in. This would make deployment and management of the websites much easier when it’s all handled in the same application. I would just need to utilise the display modes and add the views into the same project.

I’ve not had time to actually experiment with MVC 4 yet, but it’s looking promising.

makitCommerce

I have recently been doing some work on my own MVC eCommerce platform:

http://makitcommerce.codeplex.com/

I have created several basic platforms and this will be my first open source, generic one. I am trying to build in a lot of flexibility with dynamic DLL loading so the idea of the platform is that the person setting it up would choose the modules they need on install and only have what is required with great flexibility.

The platform is currently in very early stages, and I don’t have a lot of time to work on it recently but it’s progressing.

.NET MVC SEO/Slug Only URLs for eCommerce

As part of my ongoing journey creating an eCommerce site I had to implement into the .NET MVC 3 website I’m creating a way of having URLs such as

website.com/pretty-pink-boots

Instead of:

website.com/product/view/100/pretty-pink-boots

The theory being that the URL is shorter and more SEO friendly, as it contains just the important information and the full URL is shown in Google. The keen eyed amongst you will realize that this means we have no “unique ID” or controller specified in the URL - so how do we know whether it’s a product and which product to route to? Possible solutions could be:

  • Have a route at the bottom of the list of routes of simply {id} and route it to the ProductController.
    The problem here is that the slug is not the unique identifier for a product so I would have to change the product ID in the website to ‘pretty-pink-boots’ or have two unique IDs. The other problem is that we could only have the slug only URL to products, what about categories and content pages?
  • Have a route at the bottom of the list of routes of simply {id} again but this time route towards an SEOController. This controller would then do a RedirectToAction to the correct controller (Product or Category).
    Here we now have the ability to have slug only URLs for different areas of the site but the redirect causes a HTTP redirect and we definitely don’t want that from an SEO point of view. We still have the unique ID problem.

So my solution was as follows:

  1. Create a controller factory that inherits from the DefaultControllerFactory
  2. Override the GetControllerInstance function and put in some logic that if the controller requested is of type SEOController we will change the route data to (for example) “/Product/View/100” and return that controller instance.
  3. Tell MVC to use the new controller
  4. Build in a URLManager that takes in the slug and returns whether it’s a valid URL, and if so, the controller type and unique ID
  5. If no slug exists for the requested URL then a 404 must be returned
  6. Finally a route needs setting up for the SEOController, but the route must be as strict as possible and not “take over” from other URLs.

So here’s the code. First the Controller Factory

Imports makit.WebUI.Controllers
Imports System.Globalization
Imports makit.Core.URLs

Namespace Infrastructure

    Public Class MakitControllerFactory
        Inherits DefaultControllerFactory

        Protected Overrides Function GetControllerInstance(ByVal requestContext As RequestContext, ByVal controllerType As Type) As IController

            If controllerType Is Nothing Then
                Throw New HttpException(404, String.Format("The controller for path '{0}' was not found or does not implement IController", requestContext.HttpContext.Request.Path))
            End If

            If Not GetType(IController).IsAssignableFrom(controllerType) Then
                Throw New ArgumentException(String.Format("The controller type '{0}' must implement IController.", controllerType), "controllerType")
            End If

            ' This code is the custom bit to handle slug only URLs'
            If controllerType Is GetType(SEOController) Then

                ' Check if it is an actual slug and get the product/category if it is'
                Dim slug As URLManager.SlugRewrite = URLManager.getURLSlugRewrite(requestContext.RouteData.Values("id").ToString)

                If slug.exists Then

                    ' Rewrite the routedata to point to the actual controller and id'
                    requestContext.RouteData.Values("controller") = slug.pageType
                    requestContext.RouteData.Values("action") = "Display"
                    requestContext.RouteData.Values("id") = slug.pageID

                    Select Case slug.pageType
                        Case "Category"
                            controllerType = GetType(CategoryController)
                        Case "ContentPage"
                            controllerType = GetType(ContentPageController)
                        Case "Product"
                            controllerType = GetType(ProductController)
                    End Select

                    Return MyBase.GetControllerInstance(requestContext, controllerType)

                Else

                    ' A normal 404 because not a slug url'
                    Throw New HttpException(404, String.Format("No slug exists for '{0}'", requestContext.HttpContext.Request.Path))

                End If

            Else

                ' Not slug only so default to the base method'
                Return MyBase.GetControllerInstance(requestContext, controllerType)

            End If

        End Function

    End Class

End Namespace

The SEOController just needs to exist:

Namespace Controllers

    Public Class SEOController
        Inherits System.Web.Mvc.Controller

    End Class

End Namespace

Global.asax changes:

Imports makit.Core.Infrastructure
Imports makit.WebUI.Infrastructure

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
        filters.Add(New HandleErrorAttribute())
    End Sub

    Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")

        routes.MapRoute("Default",
            "{controller}/{action}/{id}",
            New With {.action = "Index",
                      .id = UrlParameter.Optional}
        )

        ' Below now handles all that didnt match as a clean SEO URL'
        routes.MapRoute("SEO Slug URLs",
            "{id}",
            New With {.controller = "SEO", .action = "Index"},
            New With {.id = "^[A-Za-z0-9\-]+$"}
        )

        ' Below now handles all that didnt match as a clean SEO URL'
        routes.MapRoute("Home Page",
            "",
            New With {.controller = "Home", .action = "Index"}
        )

    End Sub

    Sub Application_Start()

        AreaRegistration.RegisterAllAreas()
        RegisterGlobalFilters(GlobalFilters.Filters)

        RegisterRoutes(RouteTable.Routes)

        ' Custom factory which handles slug only URLs'
        ControllerBuilder.Current.SetControllerFactory(New MakitControllerFactory())

        'TODO: Below is temp for testing'
        'RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes)'

    End Sub

End Class

Finally the URLManager, here is just an example stub, this will most likely be programmed with a Dictionary object in memory containing all the slugs as keys and the SlugRewrite structures as the values

Namespace URLs

    Public Class URLManager

        Public Structure SlugRewrite
            Dim exists As Boolean
            Dim pageType As String
            Dim pageID As String
        End Structure

        Public Shared Function getURLSlugRewrite(ByVal slug As String) As SlugRewrite

            Dim slugReturn As New SlugRewrite

            Select Case slug
                Case "pencils"
                    slugReturn.exists = True
                    slugReturn.pageType = "Category"
                    slugReturn.pageID = "35"
                Case "pretty-pink-boots"
                    slugReturn.exists = True
                    slugReturn.pageType = "Product"
                    slugReturn.pageID = "2936"
            End Select

            Return slugReturn

        End Function

    End Class

End Namespace

Several things to note:

  • When building the site you will also need a reverse lookup to the slugs, in my head I will be having a slug table in the DB like the following:

    Slug - “pencils”
    Type - “Category”
    ID “35” 

    But also have the slug in the product table as a column so we have a lookup from both ways.

  • The links in the site will then need to link through to the page using the slug if one exists, which can now be gotten from the product table. If no slug exists it can then default to the Product/View/100 style URL.

  • Finally, the routing I’ve done will only route with URLs not matching any other route and a URL containing only alpha numeric characters and a dash - even so the URLManager could get a lot of hits if spiders are trying lots of types of URLs so the slug checking needs to be cached and high performance - no straight to DB calls. 

Well it’s been launched last week, and now just waiting for traffic to build

.NET Dependency Injection

Introduction

Recently I’ve been started a new MVC 3 app which is to have lot’s of components so I decided to go down the Dependency Injection route. This, for those who are hazy, is a OO design pattern for decoupling components.

I did some research (read, Google) and thought I’d go with StructureMap, but due to my limitations at work I must use VB.NET and I couldn’t find many examples, even converting from C# using my brain and auto tools, so I had some issues getting it working exactly how I wanted it to. Therefore I decided to try Ninject. The differences aren’t much and both do the DI I wanted to do but Ninject I got working in less than 10 minutes.

The app I’m working on is a MVC3 e-Commerce app with several distinct areas, such as categories, products, content pages, etc. Using the repository pattern I have repositories for returning the models but the actual concrete implementation of this can be done however I want and Ninject takes care of returning the correct concrete implementation. This means the application can be built with simple “fake/dummy” implementations and the whole site can be designed without ever needing to do the actual behind the scene complexities.

I will also use Ninject for returning concrete implementations of logging functionality, accounts, baskets, etc.

Simple “How I Did It”

The thing that frustrated me was trying to find a good example that was simple and easy to reimplement for myself so hopefully this will help for those with similar software architecture:

  1. Download Ninject - http://ninject.org/
  2. Add a reference to the MVC Web Project for Ninject.dll
  3. Create a class inheriting from the default MVC controller factory
    Imports System.Configuration
    Imports System.Web.Mvc
    Imports System.Web.Routing
    Imports Ninject
    Imports Ninject.Modules
    Imports makit.Core.Repositories
    
    Namespace Infrastructure
    
        Public Class DIControllerFactory
            Inherits DefaultControllerFactory
    
            Private kernel As IKernel = New StandardKernel(New DIServices())
    
            ' MVC calls this to get the controller for each request'
            Protected Overrides Function GetControllerInstance(ByVal context As RequestContext, ByVal controllerType As Type) As IController
                If controllerType Is Nothing Then
                    Return Nothing
                End If
                Return DirectCast(kernel.[Get](controllerType), IController)
            End Function
    
            ' Configures how abstract service types are mapped to concrete implementations'
            Private Class DIServices
                Inherits NinjectModule
    
                Public Overrides Sub Load()
    
                    Bind(Of Abstract.IProductRepository)().[To](Of Concrete.XMLProductRepository)()
                    Bind(Of Abstract.ICategoryRepository)().[To](Of Concrete.XMLCategoryRepository)()
    
                End Sub
    
            End Class
    
        End Class
    
    End Namespace
  4. As can be seen in the example above I am specifying to use the concrete XML repository class where ever the repository interfaces are referenced. An example of this use is:
    Namespace Controllers
        Public Class ProductController
            Inherits System.Web.Mvc.Controller
    
            Private m_ProductsRespository As IProductRepository
    
            Public Sub New(ByVal prodRepos As IProductRepository)
                m_ProductsRespository = prodRepos
            End Sub
    
            Function Index() As ActionResult
                Return View()
            End Function
    
        End Class
    End Namespace
  5. Now we need to add in the code that will get the dependency to fire, this is done with a line in the Application_Start function (in Global.asax):
    Sub Application_Start()
    
            AreaRegistration.RegisterAllAreas()
            RegisterGlobalFilters(GlobalFilters.Filters)
            RegisterRoutes(RouteTable.Routes)
    
            ' The Important line below'
            ControllerBuilder.Current.SetControllerFactory(New DIControllerFactory())
    
        End Sub
  6. This now means when the application starts in IIS Ninject will sort out the dependencies based on the code in DIServices, such as in the case of the ProductController example above. The main example used here is so that the products & categories for an eCommerce site can come from any type of repository - XML, SQL Server, MYSQL, Magic, etc.

Hopefully this will help you setup dependency injection in your MVC app. If you wish to see how I’ve got the repositories setup then here are my base class/intertface setups:

Namespace Repositories.Abstract

    Public Interface IProductRepository

        Function getProduct(ByVal id As String) As Models.Product

    End Interface

End Namespace
Imports System.IO

Namespace Repositories.Concrete.XML

    Public Class XMLProductRepository
        Implements Abstract.IProductRepository

        Public Function getProduct(ByVal id As String) As Models.Product Implements Abstract.IProductRepository.getProduct

            Dim prodMdl As Models.Product = Nothing

            Dim x As New System.Xml.Serialization.XmlSerializer(GetType(Models.Product))
            Dim fileName As String = Path.Combine(System.Web.HttpContext.Current.Server.MapPath("~/App_Data/Products/"), id & ".xml")

            If File.Exists(fileName) Then
                Using oStmR As New StreamReader(fileName)
                    prodMdl = CType(x.Deserialize(oStmR), Models.Product)
                    oStmR.Close()
                End Using
            End If

            Return prodMdl

        End Function

    End Class

End Namespace
Avoiding “Validation of viewstate MAC failed”

This is usually down to servers hosting multiple websites/shared hosting, or I tend to get it when developing and keep on compiling. To stop this then simply add a machine key to the config so it’s always the same, to do this easily use this generator:

http://aspnetresources.com/tools/machineKey

Simply press the button and copy the machinekey element in your web.config within system.web.

ASP.NET MVC 3

This is a message to anybody considering using MVC. Especially 3 with Razor. Do it.

You won’t regret it, it’s fast, beautiful and razor makes it very easy to manage the design side of things.

When I have several e-commerce sites running on it then I’ll know more, so I’ll blog along the way.

nop

The code for the latest nopCommerce is completely rewritten compared to 1.9 due to the MVC architecture. I’m intrigued to see the performance differences.

I’ve just spend a few hours tracing and looking for performance issues in the product listing page on nop 1.8 - turned out to be down to the discount pricing code (was adding up to 2 seconds to the page load). Now I’ve got it down from 5 seconds to 0.3 - still slow for my tastes sometimes but will do for now!

Soon I may have a launch-able site.