none
Best way to create an exceptions class? RRS feed

  • General discussion

  • I want to build an exceptions class for an application but I do not know which route to take. I have a couple of questions,

    1) Can a exceptions class have multiple different, but similar types of exceptions in it or should every exception be handled in its own class. Example:

    [Serializable]
        public class EmployeeCreationException : Exception
        {
            public EmployeeCreationExceptionException() { }
            public EmployeeCreationExceptionException(string message) : base(message) { }
            public EmployeeCreationExceptionException(string message, Exception inner) : base(message, inner) { }
    
            [Serializable]
            public class InvalidEmployeeNameException : Exception
            {
                public InvalidEmployeeNameException() { }
                public InvalidEmployeeNameException(string message) : base(message) { }
                public InvalidEmployeeNameException(string message, Exception inner) : base(message, inner) { }
                
            } 
        }  
    2) Since you can inherit from the Exception class or the ApplicationException class, does it matter which one I inherit from? Because a ApplicationException is just derived from the Exception class.


    Recht

    • Changed type Rechtfertigung Friday, January 11, 2019 5:48 PM This is subjective and everyone might have a different approach to handling this situation.
    Wednesday, January 9, 2019 4:39 PM

All replies

  • The main idea behind different exception class is the usage in code:

    try { /* some code */ }
    catch (EmployeeCreationException e) { /* error handling */ }
    catch (InvalidEmployeeNameException  e) { /* different error handling */ }

    Furthermore, keep an eye on the introduced syntax. In your example: Why do you have a creation exception, but not an update exception? When using multiple classes, the semantic is important. Thus you should have imho the following hierarchy of exceptions:

    public class BaseApplicationException : Exception {} // Your base application exception (marker).
    public class EmployeeException : BaseApplicationException {}
    public class InvalidNameException : EmployeeException {}
    public class InvalidSalaryException : EmployeeException {}

    As you can see, this leads very quickly to a waste amount of exception classes, which is imho unpractical or at least over engineering in most cases.

    Here you need to stop coding and start thinking about the indented use-cases. I would think that especially as we now have filtered exceptions,  that storing the details in text labels is sufficient. In conjunction with the fact that the validation here seems to be part of the data layer, e.g.

    public class BaseApplicationException : Exception {
    	public string ExceptionType { get; private set; }
    	public string MessageText { get; private set; }
    	public string TypeName { get; private set; }
    } 
    
    public class CrudException : BaseApplicationException {}

    At run-time in your scenario ExceptionType = "Validation", MessageText = "Invalid name length." and TypeName="Employee".

    Wednesday, January 9, 2019 5:07 PM
  • Your approach makes the task at hand more efficient and easier. seeing that I just started working with exceptions I did not fully grasp the idea of filtered exceptions so thank you for the reference, and the clarification within your examples.

    Recht

    Wednesday, January 9, 2019 7:25 PM
  • Continuing from what Stefan has shared, your EmployeeCreationException isn't conveying anything useful about what went wrong so that type doesn't make sense. If it were a database error then report the DB error, user doesn't have permission use UnauthorizedAccessException, etc. A general purpose exception type isn't useful beyond the standard Exception type that already exists.

    InvalidEmployeeNameException conveys useful information but there are far too many permutations to be realistic. For example an employee may have a department, boss, etc. Creating separate exceptions for each of them would require effectively a type for each property. In reality it is rare that an app will treat one bad property different from another so a general purpose exception serves better that can say "a property is invalid" and "here it is". We have that in ValidationException. This particular exception is also integrated into most business libraries and UI frameworks (exception Winforms) automatically which means it just works.

    Don't use ApplicationException. This type was deprecated and serves no useful purpose. It was part of the 1.0 release of .NET but MS has since agreed that it doesn't really make sense to distinguish "framework" exceptions from "application" exceptions. So when creating a new exception type either use Exception or another type that is more closely matched to its purpose (e.g. ArgumentException).


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, January 9, 2019 8:24 PM
    Moderator
  • Why are you doing this anyway for business logic validation throwing exceptions? Are you using WCF and trying to get the exception over the WCF boundary, because I see the Serializable attribute being used?

     
    Wednesday, January 9, 2019 8:37 PM
  • "because I see the Serializable attribute being used"

    Exceptions have to be marked serializable in order to get over appdomain boundaries. The guidelines recommend that all exceptions be serializable. However you also need to implement the standard serialization constructor as well which the OP isn't doing.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, January 9, 2019 9:10 PM
    Moderator
  • "because I see the Serializable attribute being used"

    Exceptions have to be marked serializable in order to get over appdomain boundaries. The guidelines recommend that all exceptions be serializable. However you also need to implement the standard serialization constructor as well which the OP isn't doing.


    Michael Taylor http://www.michaeltaylorp3.net


    Throwing exceptions on validation errors is kind of questionable, unless it's being used in some kind of service or unattended processing. If there is a UI involved, I kind of like don't see the point. 
    Wednesday, January 9, 2019 10:56 PM
  • ValidationException is the standard approach to validation today (e.g. WPF, MVC). I agree that using exceptions for "expected error" cases is questionable but there is currently no other mechanism in .NET that allows you to report problems (and require the user do something about them) without using exceptions. Simply relying on IValidatableObject and return codes isn't sufficient because we'll be back to the "C" return code approach which is even worse.

    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, January 9, 2019 11:06 PM
    Moderator
  • ValidationException is the standard approach to validation today (e.g. WPF, MVC). I agree that using exceptions for "expected error" cases is questionable but there is currently no other mechanism in .NET that allows you to report problems (and require the user do something about them) without using exceptions. Simply relying on IValidatableObject and return codes isn't sufficient because we'll be back to the "C" return code approach which is even worse.

    Michael Taylor http://www.michaeltaylorp3.net

    For user validation,  I have seen error collection class be used where the error message for validation is placed in the collection. If the error collection count is > 0,  then the list of error or errors  are shown. That's modern day stuff.

    I can't imagine sitting on a screen that has 10 errors, and each error is shown becuase for each validation error shown an exception is thrown  that forces me to hit the Submit or Save button to discover the next error.

    As opposed to getting all the errors at once on an error page with navigation to the error on a page from the errors page and back to the errors page to navigate to the next error.

    ASP.NET MVC UI design pattern certainly doesn't work by thrown exceptions all over the place on validation errors.  Model.Isvalid  being checked in a controller ActionResult method is looking at the collection of errors against the model or viewmodel in ASP.NET MVC.

    I have seen the error collection concept being used in ASP.NET Web form and Windows form solutions that implement the Model View Presenter UI design pattern.

    I have used the error collection concept in WPF and MVVM too. 


    • Edited by DA924x Thursday, January 10, 2019 4:34 AM
    Thursday, January 10, 2019 4:31 AM
  • I think we're getting off topic here so I'll finish up my point of view with this.

    "For user validation,  I have seen error collection class be used where the error message for validation is placed in the collection."

    Yes that is what Validator.TryValidateObject does. IValidatableObject returns a list of ValidationResult and that represents the "validation" errors. As I mentioned this is implicitly supported in MVC and WPF.

    "becuase for each validation error shown an exception is thrown"

    That's not how it works. Defense in depth. Validation should be occurring in the UI directly long before it gets back to the server (in a web app).

    "As opposed to getting all the errors at once on an error page"

    This approach is just as bad. Why does the user need to do anything other than enter a value before validation occurs? Modern web apps report errors as the user goes along. We aren't calling back to the server each time, we're relying on UI validation. 

    "ASP.NET MVC UI design pattern certainly doesn't work by thrown exceptions all over the place on validation errors.  "

    Of course not, it uses client-side validation so .NET isn't involved. If you're using an MVC template then you may be used to posting back to the server and having ModelState validated (using IValidatableObject) but we don't build apps like that anymore for several reasons. The biggest is that we're using REST APIs to get/set data. The easiest way to report errors back to the HTTP client is through an exception. The exception can contain the validation errors (or anything else) and the UI can build a UI just like you might see in a ModelState-based MVC view. This is really trivial code. 

    "I have used the error collection concept in WPF and MVVM too. 

    That's possible, it's been years since I've bothered with WPF but that isn't how it was originally built. Whenever you're using WPF model binding it is simply setting the underlying property on your model. If your model throws an exception it gets caught and displayed. It doesn't make sense, in most cases, to revalidate the entire model just because one property changed. So per-property validation is appropriate. And the easiest way to do that is through exceptions. Of course I would also expect the model to implement IValidatableObject but data binding wouldn't rely on that.

    In summary, using an "error collection" is useful in active error handling situations (like getting data from the user or an external source) but in all other cases passive error handling via exceptions is most appropriate (for now). Again, defense in depth.


    Michael Taylor http://www.michaeltaylorp3.net


    Thursday, January 10, 2019 2:49 PM
    Moderator
  • This approach is just as bad. Why does the user need to do anything other than enter a value before validation occurs? Modern web apps report errors as the user goes along. We aren't calling back to the server each time, we're relying on UI validation. 

    In some cases, that doesn't apply when a wizard like navigation is being applied and the user doesn't want to be stopped on a page not being able to move previous or next page when navigating  through many pages. Validation doesn't  occur until user press the Submit or Save button where a summery of errors are shown and the user can click  on an error message line that navigates the user back to the page in error.

    In summary, using an "error collection" is useful in active error handling situations (like getting data from the user or an external source) but in all other cases passive error handling via exceptions is most appropriate (for now). Again, defense in depth.

    I just use global exception handling that catches all unhandled exceptions,  logs the exception and show a user friendly error message if needed. There are no try/catch in any code in any layer or tier. 

    Thursday, January 10, 2019 4:24 PM
  •  because I see the Serializable attribute being used?

     

    I was using the Exception snippet that VS offers to create a nested type example to show what my thought process was. However as others have said, having that attribute is recommended in making any exceptions class.

    Also to answer your first question, yes, I am doing this for business logic validation. Well it's not exactly validation it's just reporting what member in the class tripped the exception.

    This is what I came up with after looking at the comments on this thread:

    Code from Manager class that checks all Manager types to make sure there is no violation in name spelling, this method would be called before printing the list of managers to screen or lets say uploading names to a database. If there is a violation, in this case if the name contains digits.. it will create a new instance of EmployeeCreationException using the construtor to feel in the necessary information about the error and then prompt the user that the entry will be removed and what member violated the rules:

    static public void CheckManagerValidation(List<Manager> ManagerList)
            {
                for (int i = 0; i < ManagerList.Count; i++)
                {
                    if (ManagerList[i].FirstName.Any(c => char.IsDigit(c)) | ManagerList[i].LastName.Any(c => char.IsDigit(c)))
                    {
                        EmployeeCreationException ex = new EmployeeCreationException("Name has Invalid Characters! Entry will be deleted", "Error Type: Number in name", ManagerList[i]);
                        ManagerList.Remove(ManagerList[i]);
                        throw ex;
                    }
                }
            }

    The actual Exception class:

    [Serializable]
        public class EmployeeCreationException : Exception
        {
            public string ExceptionType { get; private set; }
            public string MessageText { get; private set; }
            public string TypeName { get; private set; }
    
            public EmployeeCreationException() { }
            public EmployeeCreationException(string message, string Validation, EmployeeModel Emp) : base(message)
            {
                ExceptionType = Validation;
                MessageText = message;
                TypeName = Emp.FirstName + " " + Emp.LastName;
            }
         }

    Finally the try/catch block in the actual application:

     try
                {
                    Manager.CheckManagerValidation(Manager.ManagerRoster);
                }
                catch(EmployeeCreationException ex)
                {
                    Console.WriteLine(ex.ExceptionType);
                    Console.WriteLine(ex.MessageText);
                    Console.WriteLine("Offending Member: {0}",ex.TypeName);
                }

    Recht


    • Edited by Rechtfertigung Friday, January 11, 2019 5:37 PM Answered all parts of the question I was asked.
    Friday, January 11, 2019 5:19 PM
  • I myself, I do throw exceptions when they are needed. But I let the global exception handler handle the exception with no try/catch being done anywhere in the code.


    https://stackify.com/csharp-catch-all-exceptions/

    <copied>

    .NET Framework Exception Events
    The .NET Framework provides a couple events that can be used to catch unhandled exceptions. You only need to register for these events once in your code when your application starts up. For ASP.NET, you would do this in the Startup class or Global.asax. For Windows applications, it could be the first couple lines of code in the Main() method.
    <end>

    You can apply it to Windows desktop solution.

    You know what the validation exception is at  the time of the validation error. So just throw a new exception based on type with the appropriate message and let the global exception handler handle it. 

    Why do you need a try/catch doing anything? Why do you need an exception class?

    An example of global exception handler used in WebAPI, and any exception thrown in the API or a classlib project the API has reference to is caught by the GEH.

    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Configuration;
    using Serilog;
    
    namespace ProgMgmntCore2Api
    {
        public class ErrorHandlingFilter : ExceptionFilterAttribute
        {
            private readonly IConfiguration _configuration;
    
            public ErrorHandlingFilter(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            public override void OnException(ExceptionContext context)
            {
                var log = new LoggerConfiguration().ReadFrom.Configuration(_configuration).CreateLogger();
    
                var exception = context.Exception;
    
                if (exception.InnerException != null)
                {
                    log.Error("Message = " + exception.Message + " Inner.Exception.Message = " +
                              exception.InnerException.Message
                              + " Stack Trace = " + exception.StackTrace);
                }
                else
                {
                    log.Error("Message = " + exception.Message + " Stack Trace = " + exception.StackTrace);
                }
    
                context.ExceptionHandled = false; //optional 
            }
        }
    }
    

    Friday, January 11, 2019 10:08 PM
  • Why do you need a try/catch doing anything? Why do you need an exception class?

    I think there was a miscommunication, I am using a try/catch and making an exceptions class because i am learning about exceptions and how to handle them. I did not know about the way you showed in your example and it does make since and save time.

    But now I am lost, if you can use the global exception handler to catch user errors and display them, then what is the point of having exceptions and handling them. I know they are used to prevent application crashes. You also said that you throw them when they are needed. Can you explain in what situation they might be needed instead of using the global exception handler?


    Recht

    Monday, January 14, 2019 7:43 PM
  • Can you explain in what situation they might be needed instead of using the global exception handler?

    I  throw the exception in conjunction to using the GEH, becuase I have a condition that must terminate program execution letting the GEH catch and log the error and show a user friendly error message to the user.

    GEH/centralized exception handling is a style used when you are using an architectural style such as Layered, N-tier or others, which is the norm in having one spot in the solution where all layers/tiers defer to in letting GEH handle the exception -- no try/catch anywhere in any layer or tier.  

    But GEH can also be used in your scenario too. 

    Personally, I left try/catch behind long ago and  just let GEH handle it. Myself, I have never coded a situation within the last 15 years or so where  I needed a try/catch.  I have never  had a  situation where I needed to interrogate the exception and needed to have the program ignore it and continue processing.

    https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658117(v=pandp.10)

    The code being shown is a situation where the WebAPI code or the Data Access Layer used by the WebAPI had an unhandled exception thrown, control comes back to code in the presentation layer, and I must notify the user that an error has occurred with logging of the error.  The GEH logged the error on the WebAPI side with the GEH code I have previously shown, but the error is coming back to the presentation layer as well where it is logged on the PL side and letting the user know that an error happened.

    I found this nice little article for doing GEH/centralized exception handling for a test Windows form solution that was using a layered style with BLL and DAL being used, I threw an exception in the DAL, and the Windows form project, the presentation layer, caught the exception -- nice very nice.

    https://www.codeproject.com/Articles/43182/Centralised-Exception-Handling-in-C-Windows-Applic

    using System;
    using System.Diagnostics;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using ProgMgmntCore2UserIdentity.Models;
    
    namespace ProgMgmntCore2UserIdentity.Controllers
    {
        public class ErrorController : Controller
        {
            private readonly ILogger _logger;
    
            public ErrorController(ILogger<ErrorController> logger)
            {
                _logger = logger;
            }
    
            public IActionResult Index()
            {
                return View();
            }
    
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
    
                // Get which route the exception occurred at
    
                string routeWhereExceptionOccurred = exceptionFeature.Path;
    
                // Get the exception that occurred
    
                var requestid = "";
    
                requestid = Activity.Current?.Id != null ? Activity.Current?.Id : HttpContext.TraceIdentifier;
                    
                Exception exceptionThatOccurred = exceptionFeature.Error;
    
                _logger.LogError("Request Id: " + requestid + " " + routeWhereExceptionOccurred + " " + exceptionThatOccurred);
    
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }

    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using Entities;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    namespace ProgMgmntCore2UserIdentity.WebApi
    {
        public class WebApi : IWebApi
        {
            #region ProjectApi
            
            public List<DtoProject> GetProjsByUserIdApi(string userid)
            {
                var dtoprojects = new List<DtoProject>();
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/project/GetProjsByUserId?userid=" + userid);
    
                    var response = client.GetAsync(uri).Result;
    
                    if (!response.IsSuccessStatusCode)
                        throw new Exception(response.ToString());
    
                    var responseContent = response.Content;
                    var responseString = responseContent.ReadAsStringAsync().Result;
    
                    dynamic projects = JArray.Parse(responseString) as JArray;
    
                    foreach (var obj in projects)
                    {
                        DtoProject dto = obj.ToObject<DtoProject>();
    
                        dtoprojects.Add(dto);
                    }
                }
    
                return dtoprojects;
            }
    
            public DtoProject GetProjByIdApi(int id)
            {
                DtoProject dto;
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/project/GetProjById?id=" + id);
                    HttpResponseMessage getResponseMessage = client.GetAsync(uri).Result;
    
                    if (!getResponseMessage.IsSuccessStatusCode)
                        throw new Exception(getResponseMessage.ToString());
    
                    var responsemessage = getResponseMessage.Content.ReadAsStringAsync().Result;
    
                    dynamic project = JsonConvert.DeserializeObject(responsemessage);
    
                    dto = project.ToObject<DtoProject>();
                }
    
                return dto;
            }
    
            public void CreateProjectApi(DtoProject dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/CreateProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void UpdateProjectApi(DtoProject dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/UpdateProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void DeleteProjectApi(DtoId dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/DeleteProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    }
    

    Monday, January 14, 2019 11:00 PM