views:

449

answers:

4

I'm building the standard 3-tier ASP.NET web application but I'm struggling as to where to do certain things - specifically handling exceptions.

I've tried to have a look around on the web for some examples but can't find any which go as far as a whole project showing how everything links together.

In my data-tier I'm connecting to SQL Server and doing some stuff. I know I need to catch exceptions that could be raised as a result but I'm not sure where to do it.

From what I've read I should be doing it in the UI tier but in that case I'm not sure how to ensure that the connection to the database is closed. Is anyone able to clarify how to do this? Also if anyone knows as to where I could find an example 3-tier web application that follows best practices that would be great too.

thanks

A: 

Just a side point that may steer your thinking: if you have any type of volume that may result in concurrency issues (deadlocks) you will want your application to detect that particular SQL error and retry the operation (e.g. transaction). That argues for some exception handling at either the data or business tiers.

-Krip

Krip
cheers for the comment Krip, will keep that in mind for those situations
Nick
+1  A: 

I believe its best practice to handle exceptions at the last responsible moment. This usually means at the UI level (i.e., the Controller in a MVC app or in the codebehind in a traditional asp.net app). Its at this high level that your code "knows" what the user is asking, and what needs to be done if something doesn't work.

Handling exceptions lower down in the call stack usually results in calling code not being able to handle exceptional situations properly.

In your data tier, you would use the standard patterns (e.g., the using statement for IDisposables such as the SqlConnection), document exceptions you know may occur (don't do this for out of memory or other rare cases), and then let them flow up the call stack when they do. At the most you might want to catch those exceptions and wrap them in a single exception type, in situations where MANY exceptions may have to be handled by callers.

If you need to "clean up" stuff before letting exceptions go, you can always use a finally block to clean up. You don't have to catch in order to use a finally block.

I don't know of any examples of small websites which are designed to highlight proper exception handling, sorry.

Will
So its fine to wait for garbage collection to close the connection by using the using statement?
Nick
No, you want to close the connection (a) immediately when done, or (b) in Finally block, or use the Using syntax/pattern so this happens automatically. I'll elaborate on that. . .
Patrick Karcher
@nick check the link in my answer for the `using` statement.
Will
cheers Will, I did have a look earlier but missed the bit where it says it will dispose of the object even if an exception is raised - long day. Looks like this is a must have pattern which I'll be using it from now on.
Nick
+1  A: 

There are no easy answers or patterns that will ensure success. Just like your validation strategy, your exact exception-handling strategy is specific to your exact situation, and is often a trade-off between time and comprehensiveness. There is some good advice we can give though:

  • Don't ever hide the stack-trace; don't ever use "Rethrow" unless for security purposes you want to hide what happened.
  • Don't feel you need error handling everywhere. By default, in your lower tiers, letting the actual error percolate up to the top tier is not bad. The UI/Controller is where you have to really decide how to react to something going wrong.
  • At every point, as yourself what exactly you want to happen if something goes wrong. Often, you won't be able to think of anything better than to just let it go up to the top layer or even to the client machine. (though in production turn of verbose reporting.) If this is the case, just let it go.
  • Make sure you dispose of unmanaged resources (Anything that implements IDisposable.) Your data access is a great example. Either (A) Call .Dispose() for your (especially) connection, command, datareader etc. in the Finally block, or (B) use the Using Syntax/Pattern which makes sure that proper Disposing happens.
  • Look for places where errors are likely and where you can look for certain errors, react (by retrying, waiting a second retrying, trying that action a different way, etc.) and then hopefully succeed. Much of your exception handling is to make success happen, not just to report failures well.
  • In data layer, you must consider what to do if something goes wrong in the middle of a multi-step process. You can let the actual error percolate up, but this layer must handle tidying things up after an error. You'll sometimes want to use transactions.
  • In an asynchronous situation (either (A.) because of multiple threads or (B.) because business logic is processes separately on "task machines" and such and acted upon later), you in particular need to have a plan for logging errors.
  • I'd rather see "error handling code" in 25% of your app than 100%. 100% means you probably wanted it to look and feel like you have error handling. 25% means you spent time handling exceptions where they really needed to be handled.
Patrick Karcher
thanks for the response. What are your thoughts on handling SQLExceptions? For errors raised within sproc's would you catch these at the DAL and throw a custom error here or some alternative?
Nick
In my current DAL (more of a DA *tool*, that I pass into my business objects that are also kindof half-DAL objects) I catch and deal with a few specific errors automatically (like the deadlock victim error #1205. If I see that, I try again once.), but let most errors percolate. Sometimes Business object can doing something intelligent with that error; if not I let it percolate. In my DAL I'm soon going to automatically log the error and the sql command text and parameters automatically. I considered helpfully adding the sql text to the error message, but that's a security concern.
Patrick Karcher
Only trap errors in your sprocs/udf's if you can (**A**) recover / react / maybe succeed, or if (**B**) you can improve the error information that percolates up. So the default is no error handling in sproc/udf's, but then you'll sometimes add error handing to them.
Patrick Karcher
again thanks for the help with this. I was considering raising user-defined erros in my sprocs which I would then catch in the DAL layer and then create and throw a custom exception for them, rather than just the standard sqlexception. Perhaps I should use return codes though in my sprocs to identify whether it was successful or not.
Nick
From what you say, I'm guessing that's the way to go. You don't want to use the trappings of exception handling for logic flow. Whether you check for certain conditions, or assume a particular condition and raise errors otherwise, is a judgment call.
Patrick Karcher
A: 

This is not a specific answer to your question but if you are interested in best practices I would look at Microsoft Patterns and Practices:

Application Architecture Guide

Web Applications Guides

bechbd
thanks for the links, they look really helpful actually.
Nick