Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What exception should I throw when expecting a null value?

Is there a standard Exception to invoke when excepting null instead?

Commonly, exceptions are thrown for null references and arguments such as NullReferenceException and ArgumentNullException exceptions.

  • The NullReferenceException is thrown when there is an attempt to dereference a null object reference.

  • The ArgumentNullException is thrown when a null reference is passed to a method that does not accept it as a valid argument.

But what about in the event we expect the result to be null? For example, I am trying to validate if an entity exists in the database, if it does then an exception should be thrown and then I can log the error. The purpose of this validation is simply to ensure a duplicate entity is not created based on Clean Architecture in ASP.NET Core.

The code is as follows:

private async Task ValidateEntityIfExist(EntityModel entityModel, CancellationToken cancellationToken)
{
    Entity entity = await this._repository.GetEntityByIdAsync(entityModel.Id, cancellationToken);
    if (entity != null)
    {
        throw new NotNullReferenceException($"{entityModel} with this id already exists.");
    }
}

I have tried to create a custom NotNullReferenceException class that inherits from Exception class which I use for logging the event:

The code is as follows:

public class NotNullReferenceException : Exception
{
    internal NotNullReferenceException(string message)
        : base(message)
    {
    }
    internal NotNullReferenceException(string message, Exception exception)
        : base(message, exception)
    {
    }
}

Is there a standard Exception to invoke when excepting null instead? If not, what would be the best solution or workaround to such a scenario?


Update

Please note that this is based on Clean Architecture principles. For more information, please visit this link.

like image 671
Stephen Murumba Avatar asked May 10 '26 22:05

Stephen Murumba


2 Answers

No one wants to be informed that something is not null and therefore exceptional. Someone wants to be informed if the entity with the given id already exists. So either throw a custom exception like EntityExistsException or don't use exceptions at all. Your method validates an entity, so why it does not return a bool?

like image 179
Tim Schmelter Avatar answered May 12 '26 12:05

Tim Schmelter


In Clean Architecture, exceptions can be handled in a centralized spot to return consistent responses.

Once the business-level entities are defined, common exceptions and business-specific exceptions should be defined so that they can be shared among projects like API, Function App, Console App, etc..

Once the exceptions are defined, we need a centralized spot to handle all the pre-defined exceptions as well as the unknown exceptions (most likely a 500 error), log the errors, and then return a consistent API response so that any client that calls the API knows what to expect.

For example, in your case, we define an EntityAlreadyExistsException at the Core Layer as follows:

using System;
using System.Collections.Generic;
using System.Text;

namespace MyApp.Core.Exceptions
{
    public class EntityAlreadyExistsException : Exception
    {
        public EntityAlreadyExistsException()
        { }

        public EntityAlreadyExistsException(string message) : base(message)
        { }

        public EntityAlreadyExistsException(string message, Exception inner) : base(message, inner)
        { }
    }
}

In order to catch all the exceptions, we can define a filter that runs asynchronously after an action has thrown an exception. For example:

using MyApp.Core.Exceptions;
using MyApp.WebAPI.Infrastructure.ApiExceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Serilog;
using System;
using System.Collections.Generic;

namespace MyApp.WebAPI.Infrastructure.Filters
{
    public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
    {
        private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;

        public ApiExceptionFilterAttribute()
        {
            // Register known exception types and handlers.
            _exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
            {
                { typeof(EntityAlreadyExistsException), HandleAlreadyExistsException }
            };
        }

        public override void OnException(ExceptionContext context)
        {
            HandleException(context);
            base.OnException(context);
        }

        private void HandleException(ExceptionContext context)
        {
            Log.Error(context.Exception, "Handling exception:");
            Type type = context.Exception.GetType();
            if (_exceptionHandlers.ContainsKey(type))
            {
                _exceptionHandlers[type].Invoke(context);
                return;
            }
            if (!context.ModelState.IsValid)
            {
                HandleInvalidModelStateException(context);
                return;
            }
            HandleUnknownException(context);
        }

        private void HandleAlreadyExistsException(ExceptionContext context)
        {
            EntityAlreadyExistsException exception = context.Exception as EntityAlreadyExistsException;
            ProblemDetails details = new ProblemDetails()
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
                Title = "The specified resource already exists.",
                Detail = exception.Message
            };
            context.Result = new NotFoundObjectResult(details);
            context.ExceptionHandled = true;
        }
    }
}

Then register the Filter:

services.AddControllers(options =>
  // handle exceptions thrown by an action
  options.Filters.Add(new ApiExceptionFilterAttribute())
);

You can find a Template to setup centralized exception handling and logging below:

https://github.com/ShawnShiSS/clean-architecture-azure-cosmos-db

like image 44
Paul Nakitare Avatar answered May 12 '26 12:05

Paul Nakitare