ArmorCode
ArmorCode

Reputation: 749

How to rethrow exceptions in a program with multiple layers?

I have an MVC EF5 setup, with classes:

When the DataAccess class tries to do a CRUD operation on my database, if it catches an error, it needs to be handled, my UserInterface class needs to print messages to the user, reporting any errors if neccessary. So, when an error happens, it needs to go through the program class first, then to the UserInterface class, because data layer shouldn't directly communicate to the presentation layer.

It was suggested to me that I don't pass or return the exception to a calling function, but that I should "throw a new simpler exception to the layers above". All this talk about exceptions is confusing to me because My experience with exceptions is limited to this format:

try
{
    // stuff
}
catch (exception ex)
{
    console.writeline(ex.ToString());
}

I've done some of my own research to try and find the answer to this problem, and I've learned a few things but don't know how to put it all together:

I learned:

However, I don't know where to place my try/catch blocks to utilize rethrowing

Is it something like the following code? Or am I missing the point on how this works?

EDITED: (added a little more to make sense of this guesswork to others)

        void MethodA()  
        {
            try
            {
                MethodB();
            }
            catch (MyExceptionType ex)
            {
                // Do stuff appropriate for MyExceptionType
                throw;
            }
        }
        void MethodB()  
        {
            try
            {
                MethodC();
            }
            catch (AnotherExceptionType ex)
            {
                // Do stuff appropriate for AnotherExceptionType
                throw;
            }
        }
        void MethodC()  
        {
            try
            {
                // Do Stuff
            }
            catch (YetAnotherExceptionType ex)
            {
                // Do stuff appropriate for YetAnotherExceptionType
                throw;
            }
        }

Upvotes: 2

Views: 5110

Answers (2)

felipe
felipe

Reputation: 682

What (I think) was suggested you do by throw a new simpler exception is that you translate the exceptions from the lower layers into new, higher level exceptions for consuming in the outer layers. The lower level exceptions are not suitable for consumption at the upper levels of the program.

For example, in LINQ to Entities, the method Single() will throw an InvalidOperationException when the sequence has no elements. However, this exception type is very common, so catching it in the user interface levels is hard to do: how would you differentiate between different possibilities of this exception being thrown (for example, modifying a read-only collection)? The solution is to translate the exception into another (new, user-defined) type that the application can easily handle.

Here is a simple example of the idea:

public class MyUserService {
    public User GetById(int id) {
        try {
            using(var ctx = new ModelContainer()) {
                return ctx.Where(u => u.Id == id).Single();
            }
        }
        catch(InvalidOperationException) {
            // OOPs, there is no user with the given id!
            throw new UserNotFoundException(id);
        }
    }
}

Then the Program layer can catch the UserNotFoundException and know instantly what happened, and thus find the best way to explain the error to the user. The details will depend on the exact structure of your program, but something like this would work in an ASP.NET MVC app:

public class MyUserController : Controller {
    private MyUserService Service = new MyUserService();
    public ActionResult Details(int id) {
        User user;
        try {
            user = Service.GetById(id);
        }
        catch(UserNotFoundException) {
            // Oops, there is no such user. Return a 404 error
            // Note that we do not care about the InvalidOperationException
            // that was thrown inside GetById
            return HttpNotFound("The user does not exist!");
        }
        // If we reach here we have a valid user
        return View(user);
     }
}

Upvotes: 3

humblelistener
humblelistener

Reputation: 1456

There is more than how you use different type of exception handling. Functionally you should define what layers has to do what with a exception.

Like data layer => dont throw anything other than DataException or SQLException. Log them and throw back a generic database exception back to UI.

Business layer => log and rethrow simple bussiness exception UI layer => catch only business exception and alert it in a message inside business exception

Once all this is defined, you can use what you have learned and summarized in question to build this.

Upvotes: 3

Related Questions