we use a StateServer for handling Session for the known benefits (web farm, IIS recycling).
However I am trying to figure out how to make this fault tolerant. Nothing we store in the Session is critical, it is just used for performance. So if the StateServer is not available we are happy to reload from disk.
However there appears to be no way of detecting if the StateServer is online or not, so the following code all runs fine even if the StateServer is down
try
{
//It is not NULL as it has been configured
if (HttpContext.Current.Session != null)
Session["Test"] = "value";
}
// No exception is thrown
catch (Exception)
{
throw new Exception();
}
Now it makes sense to me that no exception is thrown. The Session handling would not be very performant if it had to check the status on every write. So I am guessing what happens is that it writes all the Session vaiables when the Response is written.
There lies the problem, when it tries to write the Session it fails with a 500 error and I do not know anyway to intercept this error and handle it.
Unable to make the session state request to the session state server.
Please ensure that the ASP.NET State service is started and that the
client and server ports are the same.
What I would like to happen is that the write just fails silently (or logs an error) and clients are not impacted. As it is write now the entire site goes down due to this single point of failure.
Any ideas - am I missing something obvious?
well, it can be hard. Asp.net uses session tightly, so if session storage fails, it asp.net will also fails during initialization of session module. You can write own session state provider, that will wrap existing one, and in case of fail it will return empty session items, but it can be hard to use it, because session behavior can be unpredictable.
You can look into built in SQL session state provider, that has failover in case if your SQL server has replication.
UPDATE1
Here is example of wrapper for default session providers
public class SessionProviderWrapper : SessionStateStoreProviderBase
{
private readonly SessionStateStoreProviderBase _provider;
private static Func<SessionStateStoreProviderBase> _createProvider;
static SessionProvider()
{
_createProvider = InitializerProvider();
}
private static Func<SessionStateStoreProviderBase> InitializerProvider()
{
if (_createProvider != null)
return _createProvider;
var sessionType = "stateserver"; // you can switch to another session provider
Type type;
switch (sessionType)
{
case "inproc":
type = Type.GetType("System.Web.SessionState.InProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
case "sql":
type = Type.GetType("System.Web.SessionState.SqlSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
case "stateserver":
type = Type.GetType("System.Web.SessionState.OutOfProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
default:
throw new ConfigurationErrorsException("Unknow session type: " + sessionType);
}
if (type == null)
{
throw new InvalidOperationException("Failed to find session provider for " + sessionType);
}
_createProvider = GenerateConstructorCall(type);
return _createProvider;
}
private static Func<SessionStateStoreProviderBase> GenerateConstructorCall(Type type)
{
// we are searching for public constructor
var constructor = type.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
if (constructor == null)
{
// otherwise for internal. SQL session provider has internal constructor, but we don't care
constructor = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Length == 0);
}
var node = Expression.New(constructor);
var lambda = Expression.Lambda<Func<SessionStateStoreProviderBase>>(node, null);
var func = lambda.Compile();
return func;
}
public SessionProvider()
{
var createProvider = InitializerProvider();
_provider = createProvider();
}
public override void Initialize(string name, NameValueCollection config)
{
_provider.Initialize(name, config);
}
public override string Name
{
get { return _provider.Name; }
}
public override string Description
{
get { return _provider.Description; }
}
public override void Dispose()
{
_provider.Dispose();
}
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return _provider.SetItemExpireCallback(expireCallback);
}
public override void InitializeRequest(HttpContext context)
{
_provider.InitializeRequest(context);
}
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
out SessionStateActions actions)
{
try
{
return _provider.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
}
catch (Exception ex)
{
locked = false;
lockAge = TimeSpan.Zero;
lockId = null;
actions = SessionStateActions.None;
// log ex
return new SessionStateStoreData(new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 10);
}
}
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
out SessionStateActions actions)
{
return _provider.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
}
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
_provider.ReleaseItemExclusive(context, id, lockId);
}
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
_provider.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
}
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
_provider.RemoveItem(context, id, lockId, item);
}
public override void ResetItemTimeout(HttpContext context, string id)
{
_provider.ResetItemTimeout(context, id);
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
return _provider.CreateNewStoreData(context, timeout);
}
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
_provider.CreateUninitializedItem(context, id, timeout);
}
public override void EndRequest(HttpContext context)
{
_provider.EndRequest(context);
}
}
Basically you can make try\catch on each method like in GetItem method, and in case of error, you can return empty session object. If it fails in try\catch application still will be alive. But performance will be decreased as for each request it will throw a couple of exceptions on Get\Release, that will be handled in catch section. But anyway these exceptions will decrease performance a bit
I would like to accept tgolisch answer as a solution that works for me.
In Global.asax we will look for the missing StateServer error in the Application_Error event
If we find it we will use Server.ClearError() and log the error
We will also use this to log the error and possibly send out an alert
Thanks all!
Related
I've created a custom activity which contains as a Body another Activity.
[Browsable(false)]
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
}
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
throw new Exception(propagatedException.Message);
}
When an exception is thrown during the execution of the Body, ma handler for the OnFaulted is hit.
My execution starts with a call to static method Run of the WorkflowApplication class. My WorkflowApplication instance has a handler associated for the OnUnhandledException event.
instance.OnUnhandledException +=
delegate(WorkflowApplicationUnhandledExceptionEventArgs args)
{
Console.WriteLine(args.ExceptionSource);
waitEvent.Set();
return UnhandledExceptionAction.Cancel;
};
But regardless of what happens when the Activity hosted in the Body is executed, i never reach the handler defined above. I thought that if i throw an exception from the OnFaulted, i will be able to redirect the flow to the OnUnhandledException but i was wrong. Any ideas ?
I need this in order to centralize my errors, check them and display messages accordingly. Also i need a way to stop the execution and so on and i don't want to define handlers all over the application. Is there any way to accomplish this ?
As Will suggested, i will post what i did to handle my scenario.
Basically, in my custom activity i have hosted an Assign :
[Browsable(false)]
public Activity Body { get; set; }
Activity System.Activities.Presentation.IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
{
return new Assignment()
{
Body = new Assign() { DisplayName = "" }
};
}
I've added this code to my Execute method :
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
I was trying to run this Assignment by giving an array a negative value as index and and an exception was thrown. This, somehow ended my execution but no handler for the events of my WorkflowApplication instance were hit.
Here is the method given as a callback when executing the body ( in our case the Assign activity ) :
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
faultContext.HandleFault();
CommunicationExtension ce = faultContext.GetExtension<CommunicationExtension>();
ITextExpression toTextExpression = (propagatedFrom.Activity as Assign).To.Expression as ITextExpression;
string valueTextExpression = string.Empty;
if ((propagatedFrom.Activity as Assign).Value != null)
{
if ((propagatedFrom.Activity as Assign).Value.Expression != null)
valueTextExpression = (propagatedFrom.Activity as Assign).Value.Expression.ToString();
}
if (ce != null)
{
ce.AddData(string.Format("{0} found on Assignment definition [{1} = {2}]", propagatedException.Message, toTextExpression.ExpressionText, valueTextExpression));
}
}
The trick was to call :
faultContext.HandleFault();
and use CommunicationExtension to allow me to to display the erros in the GUI.
The code for this class is trivial :
public class CommunicationExtension
{
public List<string> Messages { get; set; }
public CommunicationExtension()
{
Messages = new List<string>();
}
public void AddData(string message)
{
if (string.IsNullOrEmpty(message))
return;
Messages.Add(message);
}
}
Use this to add the extension:
CommunicationExtension ce = new CommunicationExtension();
instance.Extensions.Add(ce);
where instance is my WorkflowApplication instance.
I understood that for each instance of the workflow application we have one instance of its extension class. So i can send messages like this from all my custom activities in order to display their status.
I hope this scenario can help other people too.
I want to be able to load a user from a cloud database on each request and have that available on the request in a controller using asp.net mvc. The problem is the current framework does not support doing async operations from action filters. So OnActionExecuting, OnAuthorization methods do not allow me to do this.. for example I have the following code which DOES NOT work (so don't try it).. You get an exception : "An asynchronous module or handler completed while an asynchronous operation was still pending."
protected async override void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
HandleUnauthorizedRequest(filterContext);
return;
}
using (var session = MvcApplication.DocumentStore.OpenAsyncSession())
{
User currentUser = await session.LoadAsync<User>(user.Identity.Name);
if (currentUser == null)
{
HandleUnauthorizedRequest(filterContext);
return;
}
filterContext.HttpContext.Items["User"] = currentUser;
}
}
So is there any other way of being able to do this? I notice there is a begin execute method in the base Controller:
protected override IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
{
return base.BeginExecute(requestContext, callback, state);
}
Could I do it there possibly?
The question is three months old so I guess you've managed to work around this. Anyway, I'll add my solution here, as I had to do something similar.
I used the ToAsync method from the ParallelExtensionsExtras library. This is my class:
public class AsyncControllerBase : Controller
{
protected override IAsyncResult BeginExecute(System.Web.Routing.RequestContext requestContext, AsyncCallback callback, object state)
{
return ExecuteCoreAsync(requestContext, state).ToAsync(callback, state);
}
protected override void EndExecute(IAsyncResult asyncResult)
{
IAsyncResult baseAsyncResult = ((Task<IAsyncResult>)asyncResult).Result;
base.EndExecute(baseAsyncResult);
}
protected virtual async Task<IAsyncResult> ExecuteCoreAsync(System.Web.Routing.RequestContext requestContext, object state)
{
await DoStuffHereOrInDerivedClassAsync();
var baseBeginExecuteCompletion = new TaskCompletionSource<IAsyncResult>();
AsyncCallback callback = ar =>
{
baseBeginExecuteCompletion.SetResult(ar);
};
// OnActionExecuting will be called at this point
var baseAsyncResult = base.BeginExecute(requestContext, callback, state);
await baseBeginExecuteCompletion.Task;
return baseAsyncResult;
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
See also this documentation from Microsoft on converting between Task and IAsyncResult.
Solved:I found the solution to this. Not sure why it happens but Switching the application pool type to 'classic' from 'integrated' solves the problem. Now the 'Requests Executing' keeps going up, the actual application pool process thread remains low (~31 threads) and the app is very responsive (as it should be).
I'm using .Net 2.0 so maybe there is an issue there - tried to google it but no luck.
See Joe Enzminger's reply for an explanation
Thank you all again.
ps. the code is used for playing pool (billiards) online - windows (free) version here for anyone curious and brave enough to try :)
Hello,
I've implemented an IHttpAsyncHandler that client applications "poll" to wait for server notifications. Notifications are generated by other "activities" on the server and the Async Handler does no work at all.
The execution steps are:
IHttpAsyncHandler.BeginProcessRequest
Create AsyncResult instance and add it to a "registered clients" collection
return the AsyncResult
...other server activity will generate notifications to be sent to registered clients...
AsyncResult.CompleteCall called as a result of the generated notification(s).
IHttpAsyncHandler.EndProcessRequest is called
The notification(s) attached to the AsyncResult are written to the response stream.
The problem:
I've tested this on IIS7 on a VM with Windows Server 2008 SP2 and 1 cpu core. After 12 clients register for notifications (using an HTTP GET on the Async.ashx) the performance is degraded to the point that subsequent clients cannot connect.
When I check the ASP.NET performance counters the "Requests Executing" counter goes up with each client registration and stays at 12 (which appears to be its maximum value - probably a thread pool size per CPU).
I find this very confusing. I though the whole point of async handlers is to free up threads for other connections. It appears that this is not the case so I must be doing something wrong!
Why is ASP.NET consuming a thread while waiting for my AsyncResult to complete? Is this a config issue? Do I need to do something specific to indicate that this is an Async Handler?
Thank you,
Nikos.
Edit: Added code below:
public class AsyncResult : IAsyncResult
{
private AsyncCallback _cb;
private object _state;
private ManualResetEvent _event;
private bool _completed;
private bool _completedsynchronously;
private HttpContext _context;
private byte[] _data;
private int _datalength;
private object _lock = new object();
public AsyncWaitResult(AsyncCallback cb, object state, HttpContext context)
{
_context = context;
_cb = cb;
_state = state;
}
public void Close()
{
if (_event != null)
{
_event.Close();
_event = null;
}
}
public HttpContext Context { get { return _context; } }
public Object AsyncState { get { return _state; } }
public bool CompletedSynchronously { get { return _completedsynchronously; } }
public bool IsCompleted { get { return _completed; } }
public byte[] Data { get { return _data; } }
public int DataLength { get { return _datalength; } }
public WaitHandle AsyncWaitHandle
{
get
{
lock (_lock)
{
if (_event == null)
_event = new ManualResetEvent(_completed);
return _event;
}
}
}
public void CompleteCall(byte[] data, int length, bool completedsynchronously)
{
_data = data;
_datalength = length;
_completedsynchronously = completedsynchronously;
lock (_lock)
{
_completed = true;
if (_event != null)
_event.Set();
}
if (_cb != null)
_cb(this);
}
}
public class Outbound : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object state)
{
AsyncResult asyncresult = new AsyncResult(cb, state, context);
RegisteredClients.Instance.Add(asyncresult);
return asyncresult;
}
public void EndProcessRequest(IAsyncResult ar)
{
AsyncResult result = (AsyncResult)ar;
if (result != null)
{
result.Context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
result.Context.Response.ContentType = "application/octet-stream";
result.Context.Response.AddHeader("Connection", "keep-alive");
if (result.Data != null)
result.Context.Response.OutputStream.Write(result.Data, 0, result.DataLength);
result.Close();
}
}
public void ProcessRequest(HttpContext context){}
public bool IsReusable { get { return true; } }
}
Here is a blog post that explains what you are seeing:
http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx
and companion post
http://blogs.msdn.com/b/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
In integrated pipeline mode, using the default configuration, IIS7 places a limit of 12 concurrent REQUESTS (not threads) per CPU. You can change this by modifying the configuration.
I couldn't let it go. I'm pretty sure this is what you're seeing. Deep diving into the article, I don't really like the change they made because it clearly causes problems like this, but who am I to judge!
Another thing to check. If your client is not an actual browser but rather another application that is making multiple concurrent requests to your server this could cause your issue.
Concurrent Requests and Session State
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the # Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.
I have a library assembly that outputs trace information, and a client winforms application that adds a trace listener via app.config. If I were to use the library in ASP.NET, not configured for System.Diagnostics tracing, how could I 'catch' the trace output?
Bonus question: Can I do something with Elmah to catch and log this info? Our ASP.NET app currently uses Elmah for error logging, but that's all I know on that side of things.
I think that, as long as the library outputs trace information, this information could be captured with any specialized tool (like DebugView) or even a house grown tool, regardless of ASP.NET configuration.
I see this is old and answered, but.. I have just had the same issue and I came up with
Exception class for tracelistener
namespace TrueNorth.Elmah.TraceListener
{
internal class TraceInformation : Exception
{
internal TraceInformation(string message) : base(message){}
}
internal class TraceError: Exception
{
internal TraceError(string message) : base(message) { }
}
internal class TraceWarning : Exception
{
internal TraceWarning(string message) : base(message) { }
}
internal class TraceWrite : Exception
{
internal TraceWrite(string message) : base(message) { }
}
}
The listener
namespace TrueNorth.Elmah.TraceListener
{
internal class ElmahListener : System.Diagnostics.TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) //
{
Exception exception;
switch (eventType)
{
case TraceEventType.Information:
exception = new TraceInformation(message);
break;
case TraceEventType.Error:
exception = new TraceError(message);
break;
case TraceEventType.Warning:
exception = new TraceWarning(message);
break;
default:
exception = new TraceWrite(message);
break;
}
if (HttpContext.Current.Session == null)
{
ErrorLog.GetDefault(null).Log(new Error(exception));
}
else
{
ErrorSignal.FromCurrentContext().Raise(exception );
}
}
public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId)
{
base.TraceTransfer(eventCache, source, id, message, relatedActivityId);
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
}
And, the GO code ( you can use your web.config)
Tracer.Register();
blogged at http://truenorthit.co.uk/2015/04/17/trace-listener-for-elmah-asp-mvc-exception-logger/
write code for attaching a trace file listener programtically and enable tracing in web.config - you will be done after that
I'm using the [System.Web.Script.Services.ScriptService] tag to use web services callable from client side javascript. What I need is a way of globally logging any unhandled exceptions in those methods. On the client side, I get the error callback and can proceed from there, but I need a server-side catch to log the exception.
The guy at this url:
http://ayende.com/Blog/archive/2008/01/06/ASP.Net-Ajax-Error-Handling-and-WTF.aspx
suggests that this can't be done.
Is that accurate? Do I seriously have to go to every single webmethod in the entire system and try/catch the method as a whole.
You can use an HTTP module to capture the exception message, stack trace and exception type that is thrown by the web service method.
First some background...
If a web service method throws an exception the HTTP response has a status code of 500.
If custom errors are off then the web
service will return the exception
message and stack trace to the client
as JSON. For example:{"Message":"Exception
message","StackTrace":" at
WebApplication.HelloService.HelloWorld()
in C:\Projects\Stackoverflow
Examples\WebApplication\WebApplication\HelloService.asmx.cs:line
22","ExceptionType":"System.ApplicationException"}
When custom errors are on then the
web service returns a default message
to the client and removes the stack
trace and exception type:{"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""}
So what we need to do is set custom errors off for the web service and plug in an HTTP module that:
Checks if the request is for a web service method
Checks if an exception was thrown - that is, a status code of 500 is being returned
If 1) and 2) are true then get the original JSON that would be sent to the client and replace it with the default JSON
The code below is an example of an HTTP module that does this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
public class ErrorHandlerModule : IHttpModule {
public void Init(HttpApplication context) {
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
context.EndRequest += OnEndRequest;
}
static void OnPostRequestHandlerExecute(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
// TODO: Update with the correct check for your application
if (context.Request.Path.StartsWith("/HelloService.asmx")
&& context.Response.StatusCode == 500) {
context.Response.Filter =
new ErrorHandlerFilter(context.Response.Filter);
context.EndRequest += OnEndRequest;
}
}
static void OnEndRequest(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
ErrorHandlerFilter errorHandlerFilter =
context.Response.Filter as ErrorHandlerFilter;
if (errorHandlerFilter == null) {
return;
}
string originalContent =
Encoding.UTF8.GetString(
errorHandlerFilter.OriginalBytesWritten.ToArray());
// If customErrors are Off then originalContent will contain JSON with
// the original exception message, stack trace and exception type.
// TODO: log the exception
}
public void Dispose() { }
}
This module uses the following filter to override the content sent to the client and to store the original bytes (which contain the exception message, stack trace and exception type):
public class ErrorHandlerFilter : Stream {
private readonly Stream _responseFilter;
public List OriginalBytesWritten { get; private set; }
private const string Content =
"{\"Message\":\"There was an error processing the request.\"" +
",\"StackTrace\":\"\",\"ExceptionType\":\"\"}";
public ErrorHandlerFilter(Stream responseFilter) {
_responseFilter = responseFilter;
OriginalBytesWritten = new List();
}
public override void Flush() {
byte[] bytes = Encoding.UTF8.GetBytes(Content);
_responseFilter.Write(bytes, 0, bytes.Length);
_responseFilter.Flush();
}
public override long Seek(long offset, SeekOrigin origin) {
return _responseFilter.Seek(offset, origin);
}
public override void SetLength(long value) {
_responseFilter.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count) {
return _responseFilter.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
for (int i = offset; i < offset + count; i++) {
OriginalBytesWritten.Add(buffer[i]);
}
}
public override bool CanRead {
get { return _responseFilter.CanRead; }
}
public override bool CanSeek {
get { return _responseFilter.CanSeek; }
}
public override bool CanWrite {
get { return _responseFilter.CanWrite; }
}
public override long Length {
get { return _responseFilter.Length; }
}
public override long Position {
get { return _responseFilter.Position; }
set { _responseFilter.Position = value; }
}
}
This method requires custom errors to be switched off for the web services. You would probably want to keep custom errors on for the rest of the application so the web services should be placed in a sub directory. Custom errors can be switched off in that directory only using a web.config that overrides the parent setting.
You run the Stored Procedure in the backend. Then, for a single variable, it returns more than 1 value. Because of that, a conflicts occurs, and, this error is thrown.
I know this doesn't answer the question per-say, but I went on my own quest a while back to find this out and would up empty handed. Ended up wrapping each web service call in a try/catch, and the catch calls our error logger. Sucks, but it works.
In ASP.Net it is possible to catch all run handled exceptions using a global error handler although the blog post suggest this would not work but you could experiment with this approach trying to rethrow the error in some way?
Another idea would be to look at the open source elmah (Error Logging Modules and Handlers) for ASP.Net that might help or someone in that community may have an idea.