I've implemented a Fire and Forget asynchronous pattern similar to Mike Woodring's sample so that I could execute longer running processes than the typical web request on a spearate thread and return back to the client immediately. These processes connect to a SQL database using windows integrated security.
All was working just dandy on my development XP box running without impersonation. In this scenario the components run under the default ASPNET identity and connect to a local database. (In Windows 2003/IIS6 the default identity is NETWORK SERVICE.) However when I went to deploy it on our testing servers I ran into a problem with the asynchronous thread identities. They were throwing exceptions trying to connect to the database.
Our test rig is set up as two Windows 2003 servers, one app server and one database server on their own little domain. We set the app server's web.config with explicit impersonation of a least priveledged domain account that is windows authenticated to the database. All works fine for client request threads, however the identity of the async threads were that of the application pool, not the explicit domain user and was therefore causing problems connecting to the database. I figured I could change the application pool identity, but I wasn't satisfied with having to remember another configuration setting. I really wanted the Web.config to be the only place for this and I was perplexed as to why the main thread's identity wasn't getting propagated.
I still am not sure as to why this is the case since the documentation for ThreadPool.QueueUserWorkItem makes it seem like this should work in .NET 2.0. I ended up augmenting the AsynHelper class to impersonate a WindowsIdentity. Here's the code and it's usage (comments/suggestions welcome!):
Imports System.Threading Imports System.Security Friend Class AsyncHelper Private Shared wc As New WaitCallback(AddressOf CallMethod) Public Shared Sub FireAndForget(ByVal d As [Delegate], _ ByVal wi As Principal.WindowsIdentity, _ ByVal ParamArray args As Object()) ThreadPool.QueueUserWorkItem(wc, New TargetInfo(d, args, wi)) End Sub Private Shared Sub CallMethod(ByVal o As Object) Dim ti As TargetInfo = DirectCast(o, TargetInfo) 'This is necessary so this thread impersonates the 'calling thread's identity. This is important when 'running under ASP.NET explicit impersonation. ti.Identity.Impersonate() 'Invoke the method, passing the arguments ti.Target.DynamicInvoke(ti.Args) End Sub Private Class TargetInfo Private m_target As [Delegate] Private m_args As Object() Private m_wi As Principal.WindowsIdentity ReadOnly Property Target() As [Delegate] Get Return m_target End Get End Property ReadOnly Property Args() As Object() Get Return m_args End Get End Property ReadOnly Property Identity() As Principal.WindowsIdentity Get Return m_wi End Get End Property Sub New(ByVal d As [Delegate], _ ByVal args As Object(), _ ByVal wi As Principal.WindowsIdentity) m_target = d m_args = args m_wi = wi End Sub End Class End ClassAnd here's a usage example:
Private Delegate Sub ExecuteQueryDelegate(ByVal personID As Integer) Public Sub BeginFetch(ByVal personID As Integer) Dim dlgt As New ExecuteQueryDelegate(AddressOf ExecuteQuery) ' Initiate the asynchronous call. AsyncHelper.FireAndForget(dlgt, Principal.WindowsIdentity.GetCurrent(), personID) End Sub 'This method runs on an asynchronous thread Private Sub ExecuteQuery(ByVal personID As Integer) Dim resultSet As DataTable = Me.LongRunningProcesses() Me.SaveResults(resultSet) End Sub