Sisusa.ServiceExceptions 1.0.0

dotnet add package Sisusa.ServiceExceptions --version 1.0.0
                    
NuGet\Install-Package Sisusa.ServiceExceptions -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sisusa.ServiceExceptions" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sisusa.ServiceExceptions" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Sisusa.ServiceExceptions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sisusa.ServiceExceptions --version 1.0.0
                    
#r "nuget: Sisusa.ServiceExceptions, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sisusa.ServiceExceptions@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sisusa.ServiceExceptions&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Sisusa.ServiceExceptions&version=1.0.0
                    
Install as a Cake Tool

Service Exceptions Documentation

This document provides comprehensive usage examples and best practices for the Sisusa.ServiceExceptions namespace, which contains specialized exception classes for service-layer operations.

Table of Contents

  1. ServiceException
  2. AccessDeniedException
  3. AuthenticationException
  4. ConcurrencyException
  5. ConfigurationException
  6. DuplicateEntityException
  7. EntityNotFoundException
  8. SecurityException
  9. Advanced Usage Patterns
  10. Best Practices

ServiceException

Base class for all service exceptions. Provides common functionality like error codes.

Basic Usage

throw new ServiceException("Failed to process the request");

With Error Code

throw new ServiceException("Failed to process the request") 
{ 
    ErrorCode = "PROCESSING_ERROR" 
};

Real-world Example

try 
{
    // Service operation
}
catch (Exception ex)
{
    throw new ServiceException("Order processing failed", ex);
}

AuthenticationException

Thrown when authentication fails.

Basic Usage

if (!user.IsAuthenticated)
{
    throw new AuthenticationException("Invalid credentials");
}

Real-world Example

public async Task<User> AuthenticateAsync(string username, string password)
{
    var user = await _userRepository.GetByUsernameAsync(username);
    
    if (user == null || !VerifyPassword(user.PasswordHash, password))
    {
        throw new AuthenticationException("Invalid username or password");
    }
    
    return user;
}

ConcurrencyException

Thrown when a concurrency conflict occurs (e.g., optimistic concurrency).

Basic Usage

throw new ConcurrencyException(entity, "Record was modified by another user");

Real-world Example

public async Task UpdateOrderAsync(Order order)
{
    var existing = await _orderRepository.GetByIdAsync(order.Id);
    
    if (existing.Version != order.Version)
    {
        throw new ConcurrencyException(order, 
            "Order was modified by another process. Please refresh and try again.");
    }
    
    await _orderRepository.UpdateAsync(order);
}

ConfigurationException

Thrown for configuration-related issues.

Basic Usage

var apiKey = Configuration["ApiKey"] 
    ?? throw new ConfigurationException("ApiKey", "API key is required");

Using Builder Pattern

throw ConfigurationExceptionBuilder.For("DatabaseConnection")
    .WithMessage("Failed to establish database connection")
    .WithInnerException(dbEx)
    .WithErrorCode("DB_CONN_FAILURE")
    .Build();

Real-world Example

public string GetRequiredConfig(string key)
{
    try 
    {
        return _configuration[key] 
            ?? throw new ConfigurationException(key, $"Configuration '{key}' is missing");
    }
    catch (Exception ex)
    {
        throw ConfigurationExceptionBuilder.For(key)
            .WithMessage($"Failed to read configuration '{key}'")
            .WithInnerException(ex)
            .Build();
    }
}

DuplicateEntityException

Thrown when a duplicate entity is detected.

Basic Usage

if (await _userRepository.ExistsAsync(email))
{
    throw new DuplicateEntityException($"User with email {email} already exists");
}

Using Helper Methods

var existingUser = await _userRepository.GetByEmailAsync(email);
DuplicateEntityException.ThrowIfExists(existingUser, $"Email {email} is already registered");

Real-world Example

public async Task CreateUserAsync(User newUser)
{
    // Check if email exists
    var existing = await _userRepository.GetByEmailAsync(newUser.Email);
    DuplicateEntityException.ThrowIfExists(existing, 
        $"User with email {newUser.Email} already exists");
    
    // Check if username exists with additional condition
    var usernameExists = await _userRepository.GetByUsernameAsync(newUser.Username);
    DuplicateEntityException.ThrowIfExists(usernameExists, 
        u => !u.IsDeleted, 
        $"Username {newUser.Username} is taken");
    
    await _userRepository.AddAsync(newUser);
}

EntityNotFoundException

Thrown when an expected entity is not found.

Basic Usage

var user = await _userRepository.GetByIdAsync(id) 
    ?? throw new EntityNotFoundException($"User {id} not found");

Using Helper Methods

var order = await _orderRepository.GetByIdAsync(orderId);
EntityNotFoundException.ThrowIfNull(order, $"Order {orderId} not found");

Real-world Example

public async Task<Order> GetOrderDetailsAsync(int orderId, int userId)
{
    var order = await _orderRepository.GetByIdAsync(orderId);
    
    EntityNotFoundException.ThrowIfNull(order, $"Order {orderId} not found");
    
    // Additional validation
    EntityNotFoundException.ThrowIfNull(order, 
        o => o.UserId == userId, 
        $"Order {orderId} not found for user {userId}");
    
    return order;
}

SecurityException

Base class for security-related exceptions.

Basic Usage

if (!IsOperationAllowed(user))
{
    throw new SecurityException("Operation not permitted due to security constraints");
}

Real-world Example

public void ProcessPayment(Payment payment)
{
    if (payment.Amount > _userService.GetMaxPaymentAmount())
    {
        throw new SecurityException(
            $"Payment amount {payment.Amount} exceeds maximum allowed");
    }
    
    // Process payment
}

AccessDeniedException

Thrown when authorization fails(user is logged in but not permitted to do the operation) and the operation has to be halted.

Basic Usage

var requiredPermission = "EDIT_ORDERS";
AccessDeniedException.ThrowIf(u=> !u.HasPermission(requiredPermission), user, requiredPermission);

//or if you prefer to be more verbose:
if (!user.HasPermission("EDIT_ORDERS"))
{
    throw new AccessDeniedException("EDIT_ORDERS");
}

Real-world Example

public void DeleteUser(int userId, User requestingUser)
{
    if (!requestingUser.IsAdmin)
    {
        throw new AccessDeniedException("DELETE_USERS");
    }
    
    _userRepository.Delete(userId);
}

Advanced Usage Patterns

1. Exception Translation Middleware

app.UseExceptionHandler(appError =>
{
    appError.Run(async context =>
    {
        var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
        
        switch (exception)
        {
            case EntityNotFoundException ex:
                context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                await context.Response.WriteAsJsonAsync(new { ex.Message });
                break;
                
            case AccessDeniedException ex:
                context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                await context.Response.WriteAsJsonAsync(new { ex.Message, ex.RequiresPermission });
                break;
                
            // Handle other exception types...
        }
    });
});

2. Domain-Specific Exception Subclasses

public class OrderProcessingException : ServiceException
{
    public int OrderId { get; }
    
    public OrderProcessingException(int orderId, string message) 
        : base(message)
    {
        OrderId = orderId;
        ErrorCode = "ORDER_ERROR";
    }
}

// Usage:
throw new OrderProcessingException(order.Id, "Payment processing failed");

3. Exception Enrichment

try
{
    // Service operation
}
catch (Exception ex) when (ex is not ServiceException)
{
    var enrichedEx = new ServiceException("Operation failed", ex)
    {
        ErrorCode = "OPERATION_FAILED",
        Data = { 
            ["Timestamp"] = DateTime.UtcNow,
            ["UserId"] = currentUserId
        }
    };
    throw enrichedEx;
}

Best Practices

  1. Use Specific Exceptions: Always use the most specific exception type that fits the error condition.

  2. Provide Context: Include relevant information in exception messages:

    // Good
    throw new EntityNotFoundException($"Product with ID {productId} not found");
    
    // Bad
    throw new EntityNotFoundException("Not found");
    
  3. Use Helper Methods: Leverage the static helper methods for common checks:

    EntityNotFoundException.ThrowIfNull(product);
    DuplicateEntityException.ThrowIfExists(existingUser);
    
  4. Preserve Stack Traces: Always include inner exceptions when wrapping exceptions:

    catch (DbException ex)
    {
        throw new ServiceException("Database operation failed", ex);
    }
    
  5. Standardize Error Codes: Define a standard set of error codes for your application.

  6. Log Before Throwing: Consider logging exceptions before throwing them:

    _logger.LogError(ex, "Failed to process order {OrderId}", orderId);
    throw new OrderProcessingException(orderId, "Processing failed", ex);
    
  7. Document Exception Contracts: Document which exceptions your methods can throw.

  8. Use Builder Pattern for Complex Exceptions: For exceptions with many properties (like ConfigurationException), use the builder pattern for cleaner code.

  9. Consider Localization: For user-facing messages, consider adding support for localized messages.

  10. Test Exception Cases: Write unit tests that verify your exceptions are thrown in the right circumstances.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 114 6/7/2025

Initial Release