views:

446

answers:

3

I'm currently using an Open Session in View pattern in an ASP.NET WebForms application (adapted quite a bit from Billy McCafferty's http://www.codeproject.com/KB/architecture/NHibernateBestPractices.aspx). To oversimply state what goes on here:

  1. Open NHibernate transaction at beginning of request
  2. Commit at end of request (rolling back on any errors)

I usually handle any exceptional database errors by capturing them in Application_Error (where I log, redirect to generic error page, etc.) as follows:

        ...
          if (Context != null && Context.IsCustomErrorEnabled)
        {
            Server.Transfer(ErrorPageLocation, false);
        }
        else
        {
            log.Error("Unhandled Exception trapped in Global.asax", exception);
        }

but with NHibernate, by the time I get to any NHibernate/Database errors, it's too late in the request to do my usual Server.Transfer (obviously no transferring going to happen this late in the request) error redirecting (although logging still takes place). As a very quick fix, I've done the following in my custom HttpModule's HttpApplication Context EndRequest:

        try
        {
            // Commits any open transaction and throws after rolling back any HibernateException and closing session.
            NHibernateSessionManager.Instance.CommitTransaction();
        }
        catch (HibernateException)
        {
            HttpContext.Current.Response.Redirect(ERROR_PAGE_LOCATION);
        }
        finally
        {

            NHibernateSessionManager.Instance.CloseSession();
        }

...but this smells not only because I'm now referencing NHibernate in my Web, but mostly because I'm sure there must be a better way. What would be a better way of dealing with redirecting the user to a generic error page in the event of a database/NHibernate error where, as any exceptions thrown at this point is thrown too late in the process to get handled further back in the request with a Server.Transfer in Global.asax.cs Application_Error? Taking it one step further, with web services it gets even trickier as the above hack obviously has no effect (and no exception gets thrown to be handled on the receiving end - in my case, most typically in client-side ajax calls).

Please tell me I'm missing something obvious (usually the obvious hits me right after I hit submit on questions like these)!

+2  A: 

As I understand it, the open session in view pattern need not necessarily mean a unit of work is open for the entirety of the request. I handle this (in ASP.NET MVC, not webforms but the principle is the same) by explicitly defining a unit of work in my controller methods. Before the controller method returns any database transaction has been committed, so I can handle any data update errors properly at that point. The session stays open though, even after the transaction has been committed. For my purposes, the open session in view is useful mainly to allow lazy loading of related objects in the view stage. There were 2 main reasons I chose to do it this way:

  1. I didn't want to hold any database transactions open while the view is being rendered to the response - it's not necessary, and with database transactions, the shorter the better.
  2. With a 'using' pattern, having an explicit unit of work is not much extra effort

Any NHibernate errors that occur in the view stage are less important for me - the transaction(s) and any updates have already gone through, so I can just handle those with a generic catch-all redirect error handler.

Steve Willcock
Steve - thanks for the response. You've articulated the main reasons I have gone with this pattern, but unfortunately, I'm stuck in WebForms and my unit of work IS the entire request. Repeatedly when I've investigated options, it usually involves MVC, etc. which is not an option. Am I off base?
Ted
I think the issue is not that the options involve MVC as such, but that they involve Dependency Injection or Inversion of Control to get a unit of work into the UI layer without data layer references. If you have an IUnitOfWork interface injected into the UI layer then things suddenely get easier
Steve Willcock
... or at least they did for me! Using DI/IOC with webforms is not as easy as with MVC but still worthwhile I think. I'm using a unit of work pattern in a similar way with DI in an entity framework app on webforms.Sorry there isn't much space in these comments to go into depth...
Steve Willcock
... you could check out this link for the DI bits: http://www.sleeplesscoder.com/blog/2009/03/a-bit-about-using-structuremap-and-webform, and I'm going to do a blog post on this issue in a few days - I'll let you know when it's up!
Steve Willcock
Thanks Steve. I'm looking forward to what you're going to post on this one. DI in WebForms is something I need to investigate further and would love any and all tips gleaned from real experience with it.
Ted
A: 

One thing we have done where the error page is very basic, is to simply intervene in the Page.OnError(via a base page) and emit the html via the Response. Somewhat lame, but gets you the desired result in a centralized location.

Adam Fyles
A: 

Perhaps you could declare a bool in global.asax to indicate that an error occurred in the previous request. You would set it to true in Application_Error, then check its value in Application_BeginRequest and redirect if it's true?

Jamie Ide
This is along the same lines as my hack, but thanks.
Ted