CSLA and SQL Cache Dependency.CSLA and SQL Cache Dependency.
Old forum URL: forums.lhotka.net/forums/t/709.aspx
geordiepaul posted on Monday, July 24, 2006
I have been trying to implement some server side SQL Cache Dependencies withing my business objects. Basically within my DataPortal_Fetch I create the dependency and add an event handler to expire the cached data when the data changes within the DB.
The problem I'm having is that the SQLDependency.Start() methods fails when the Csla.ApplicationContext sets the Thread.CurrentPrincipal to the Csla version of the principal/identity. It reports that it can't find my assambly! The application web based so I have changed the User property of the ApplicationContext class to the following....
public
static IPrincipal User
{
get
{
if (HttpContext.Current == null)
return Thread.CurrentPrincipal;
else
return HttpContext.Current.User;
}
set
{
if (HttpContext.Current != null)
HttpContext.Current.User = value;
else // <--- NEW
Thread.CurrentPrincipal = value;
}
}
This works fine but I am a little concerned as to the impact this will have and I can't find any documention covering this area of SQL Cache Depenencies.
Just wondered if anyone had experinced the same problems??
Thanks
RockfordLhotka replied on Monday, July 24, 2006
Are you trying to run your code within IIS or Cassini? CSLA custom security won't work in Cassini - I've blogged about this and mention it several places in the book as well.
Your change is dangerous, because it means that the thread's principal isn't being set and may not match the httpcontext principal. While all CSLA code uses ApplicationContext, and yours probably does too, any code not based on CSLA may be doing the wrong thing and using the thread's principal when in ASP.NET (certainly any .NET 1.x code would do that) - and thus would be using the wrong principal.
david.wendelken replied on Monday, July 24, 2006
FYI, Cassini is the name for the webserver built into Visual Studio.
geordiepaul replied on Tuesday, July 25, 2006
No, I'm using IIS. This is the error I get when I remove the "else" from the above code.
Unable to find assembly 'AssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Runtime.Serialization.SerializationException: Unable to find assembly 'Communicator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Source Error:
Line 35: if (!m_DependancyStarted)
Line 36: {
Line 37: SqlDependency.Start(ConnectionString);
Line 38: m_DependancyStarted = true;
Line 39: }
|
Source File: C:\Documents\Visual Studio 2005\projects\.... Line: 37
geordiepaul replied on Tuesday, July 25, 2006
One thing I should add is that this only fails in ASP.NET applications. If I call the same method in a Console Application then it works as expected even with my "else" added, since...
HttpContext.Current == null
Thread.CurrentPrincipal gets set anyway.
RockfordLhotka replied on Tuesday, July 25, 2006
Sure, that would work fine in a console
app.
But the problem is, that in an ASP.NET app you wouldn't set
the thread principal. Some code (lots actually) rely on the thread principal
only, because (in theory) that's the right thing to do. Thank's to Scott
Hanselman I discovered that in .NET 2.0 they changed some things, so there are
some edge cases where the thread principal isn't safe to use in ASP.NET (which
really sucks), and so this is why ApplicationContext does what it
does.
If you ONLY use ApplicationContext, and you KNOW that you
are only using code that uses it (or an equivilent) then you could get away with
your change. But you really can't, because that means you can't use any 3rd
party controls/components and even the .NET framework itself is not sure to work
(all the security demand attributes use the thread
principal...)
My guess is that SqlDependency is switching threads, and
that it (or ASP.NET) is using dynamic assembly loading. There's a known issue
deep, deep in the .NET runtime that causes problems with finding dynamically
loaded assemblies during deserialization. So deep that they can't/won't fix it -
nor have they officially agreed that it is a bug, but it is very clearly not
desired behavior...
The possible solution is to handle the appdomain event for
failure to resolve the assembly, and to handle it yourself.
I do this in Chapter 4 for the implementation of the
EnterpriseServices data portal host, because this issue exists for any
dynamically loaded assemblies in COM+. You can find the code in Chatper 4 called
"Serialization Workaround". In a web setting you'd want to call the hookup once
in global.asax, as the initial hook should just run once per appdomain. The rest
is automatic.
Whether this fixes your problem or not I don't know -
you'll have to try and see.
Rocky
One thing I should add is that this only fails in
ASP.NET applications. If I call the same method in a Console Application then
it works as expected even with my "else" added, since...
HttpContext.Current == null
Thread.CurrentPrincipal gets set anyway.
xal replied on Tuesday, July 25, 2006
Yes, that is very crazy!! I learned that the hard way. For me, the app was raising exceptions when I closed some forms that where using csla objects. The problem was that it wasn't finding csla's assembly.
I do this when the app starts:
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf ResolveAssembly
And the function does this:
Private Function ResolveAssembly(ByVal sender As Object, _
ByVal e As ResolveEventArgs) As Assembly
For Each ass As Assembly In AppDomain.CurrentDomain.GetAssemblies
If e.Name.Contains(ass.FullName) Then
Return ass
End If
Next
Return Nothing
End Function
(Yeah, I know I didn't pick the best name for the assembly variable, but ins spanish it doesn't mean anything)
Anyway, the reason for this to happen is beyond me. It is a winforms app. I could never find the cause.
Andrés
geordiepaul replied on Wednesday, July 26, 2006
Hi, Thanks for you help and advice.
I have tried to implement the event handler with the Application_Start. The ResolveAssembly method never fires (even though the event is correctly connected through the application_start event) and the problem persists.
The code is global.asax looks like this....
void
Application_Start(object sender, EventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveAssembly);
}
private
System.Reflection.Assembly ResolveAssembly(Object sender, ResolveEventArgs e)
{
foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.FullName == e.Name)
return ass;
}
return null;
}
Could the SqlDependency.Start() method be operating outside the ASP.NET AppDomain? It's not an area I'm particularly knowledgeable?
RockfordLhotka replied on Wednesday, July 26, 2006
Yes, I imagine it could. I haven't looked at this area at
all, so I really don't know... If that is the case, you may be in a difficult
position - but in that case it would be similar to issues faced when doing nunit
tests.
The workaround for nunit tests is to make sure to do a
Logout() before allowing the appdomain transition. So what you might try is
wrapping your call to SqlDependency in a method. Have that method first store
the value of Thread.CurrentPrincipal, then clear it (to GenericPrincipal or
Nothing/null), then call SqlDepdendency, then restore Thread.CurrentPrincipal to
its original value.
Rocky
Could the SqlDependency.Start() method be operating outside the ASP.NET
AppDomain? It's not an area I'm particularly
knowledgeable?
geordiepaul replied on Wednesday, July 26, 2006
That's a great idea and it worked too!
I already had the call to start the dependancy in the method in a database class so it was a simple change and here's is the code for anyone who has a similar problem...
///
<summary>
/// Starts the SQL Cache dependancy if it not already started within the
/// current application
/// </summary>
public static void StartSqlCacheDependancy()
{
if (!m_DependancyStarted)
{
System.Security.Principal.IPrincipal principal;
principal = System.Threading.Thread.CurrentPrincipal;
System.Threading.Thread.CurrentPrincipal = null;
try
{
SqlDependency.Start(ConnectionString);
m_DependancyStarted = true;
}
catch (Exception ex)
{
throw (ex);
}
finally
{
System.Threading.Thread.CurrentPrincipal = principal;
}
}
}
RockfordLhotka:
The workaround for nunit tests is to make sure to do a Logout() before allowing the appdomain transition. So what you might try is wrapping your call to SqlDependency in a method. Have that method first store the value of Thread.CurrentPrincipal, then clear it (to GenericPrincipal or Nothing/null), then call SqlDepdendency, then restore Thread.CurrentPrincipal to its original value.
Rocky
Could the SqlDependency.Start() method be operating outside the ASP.NET AppDomain? It's not an area I'm particularly knowledgeable?
ljcorreia replied on Monday, February 14, 2011
Hi,
I know this post is quite old, but I had this problem for quite a while and never came across this solution, so the SqlCacheDependency I have for Sitemap was never getting noticated in SQL 2005 and therefore I never was able to solve this problem until I bang into this thread.
I just want to say thank you very much. I also solved my problem.
Kind regards,
Leo
Copyright (c) Marimer LLC