Search code examples
c#entity-frameworkerror-handlingservice-layerdata-layer

How to surface the exception details to the user for resolution in a multi layered application


We have the following code in our Service Layer where we add a new user to the database using EF.

    public User AddUser(User user)
    {
        using (var context = DataObjectFactory.CreateContext())
        {
            var userEntity = Mapper.Map(user);

            context.AddObject("UserEntities", userEntity);

            context.SaveChanges();

            return Mapper.Map(userEntity);
        }         
    }

And this is called by the Service Layer via this method:

    public UserResponse GetSubmitUser(UserRequest request)
    {
        var response = new UserResponse(request.RequestId);

        var user = Mapper.FromDataTransferObject(request.User);

        response.User = Mapper.ToDataTransferObject(UserDao.AddUser(user));

        return response;
    }

We do certain client and server validations before we reach the AddUser however I am not sure about how to handle the unhandled exception(s).

For instance if somehow context.SaveChanges() throws an error how do we bubble the error details back to the Presentation layer?

I am thinking of putting try / catch within the AddUser like this:

    public User AddUser(User user)
    {
        try
        {
            using (var context = DataObjectFactory.CreateContext())
            {
                var userEntity = Mapper.Map(user);

                context.AddObject("UserEntities", userEntity);

                context.SaveChanges();

                return Mapper.Map(userEntity);
            }  
        }
        catch (Exception)
        {
            return ??
        }      
    }

but the return type of the AddUser is User and I'm not sure what I should be returning when an exception happens. If I can return the exception details to the UserResponse method I can populate some fields within our response object which are there to hold error details, but now sure what to return from within the catch section.

I am trying to understand how to identify exceptions that should be caught and handled in a multi layered application, one with a data access layer, service layer, presentation layer etc. Mainly trying to figure out how to surface the exception details to the user for resolution.

Thanks


Solution

  • I think "BusinessExceptions" are the thing you are looking for.

    In a n-tier application it's quite common to have trouble getting or creating business rules exceptions from the service layer. I guess that you are trying to validate some business rules with your example, maybe that the username is valid or that it doesn't already exist in the DB.

    For this kind of issues I always work with FaultExceptions and custom objects to send a message "understandable" to the client side. This way I can distinguis between system exceptions, bug exceptions and business rules exceptions (the ones that the user want to know).

    This is how I usually do it. I have a BusinessFault object which contains the data of the business rule broken that will be procesed by the client and showed, if necessary, to the user:

    [DataContract]
    public class BusinessFault
    {
        [DataMember]
        public BusinessFaultType Type { get; set; }
    
        [DataMember]
        public string Details { get; set; }
    
        [DataMember]
        public ErrorCode ErrorCode { get; set; }
    
        public BusinessFault(BusinessFaultType type, ErrorCode errorCode)
        {
            Type = type;
            ErrorCode = errorCode;
        }
    
        public BusinessFault(BusinessFaultType type, string details, ErrorCode errorCode)
            : this(type, errorCode)
        {
            Details = details;
        }
    
    }
    

    So whenever I need to check a rule I use it like this (using your example):

    public void AddUser(User user)
    {
    if(!IsValidUser(user))
     throw new FaultException<BusinessFault>(new BusinessFault(BusinessFaultType.Validation, "Username",ErrorCode.AlreadyExists); 
    
    using (var context = DataObjectFactory.CreateContext())
        {
            var userEntity = Mapper.Map(user);
    
            context.AddObject("UserEntities", userEntity);
    
            context.SaveChanges();
    
            return Mapper.Map(userEntity);
        } 
    }
    

    In my service contract I should specify my BusinessFault as a knowntype. This way this faults will get to the client side:

        [OperationContract]
        [FaultContract(typeof(BusinessFault))]
        void AddUser(User user);                              
    

    So in the client-side all I need is a try-catch specific for the BusinessFault:

    try
    {
        //WebService call
    }
    catch (FaultException<BusinessFault> ex)
    {
    var exception = ex.ToBusinessExeption();  //I use extensions Methods to convert it in a custom Exception with the parameters I sent. 
                throw exception;
    }     
    

    There's a very good article about this: http://www.c-sharpcorner.com/UploadFile/afenster/wcf-error-handling-and-faultexceptions/