Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Aug 24, 2022

Implementation of Global Exception Handling using .NET Core 6 Web API

Автор:
Jaydeep Patil
Источник:
Просмотров:
1459

In this article, we will learn about global exception handling implementation using .NET Core 6 Web API step-by-step.

Agenda

  • Introduction
  • Implementation of Global Exception Handling

Prerequisites

  • Visual Studio 2022
  • .NET Core 6 SDK
  • Understanding of C# Programming
  • Basic Understanding of Object-Oriented Programming
  • Understanding of .NET Core APIs

Introduction

  • Exception Handling is one of the most important features we need to take care of while designing applications and handling runtime exceptions.
  • If we do not handle exceptions properly, it might impact your application and get terminated automatically.
  • There are many ways to implement exception handling during while software development phase and from which we only used a few like try-catch and something like that to handle exceptions while our application is in running mode.
  • But, when we use this method, in that case, we need to write more redundant code throughout our application and it reduces the performance of the application and increases the time and space complexity unnecessary.
  • Because of this, we used global exception handling through middleware to catch runtime errors efficiently as per our requirement.

Implementation of Global Exception Handling

Step 1)

Create a new .NET Core Web API Project.

Step 2)

Configure your project.

Step 3)

Provide some additional information related to project.

­­Step 4)

Install the following NuGet Packages.

Step 5)

Final Project Structure.

Step 6)

Create a new Product class inside model: 

namespace GlobalExceptionHandling.Model
{
    public class Product
    {
        public int ProductId { get; set; }
        public string ProductName { get; set; }
        public string ProductDescription { get; set; }
        public int ProductPrice { get; set; }
        public int ProductStock { get; set; }
    }
}

Step 7)

Next, create a new DbContextClass inside Data: 

using GlobalExceptionHandling.Model;
using Microsoft.EntityFrameworkCore;

namespace GlobalExceptionHandling.Data
{
    public class DbContextClass : DbContext
    {
        protected readonly IConfiguration Configuration;

        public DbContextClass(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        }

        public DbSet<Product> Products { get; set; }
    }
}

Step 8)

After that, create an IProductService interface inside the services: 

using GlobalExceptionHandling.Model;

namespace GlobalExceptionHandling.Services
{
    public interface IProductService
    {
        public Task<IEnumerable<Product>> GetProductList();
        public Task<Product> GetProductById(int id);
        public Task<Product> AddProduct(Product product);
        public Task<Product> UpdateProduct(Product product);
        public Task<bool> DeleteProduct(int id);
    }
}

Step 9)

Next, create ProductService with interface implementation inside services: 

using GlobalExceptionHandling.Data;
using GlobalExceptionHandling.Model;
using Microsoft.EntityFrameworkCore;

namespace GlobalExceptionHandling.Services
{
    public class ProductService : IProductService
    {
        private readonly DbContextClass _dbContext;

        public ProductService(DbContextClass dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IEnumerable<Product>> GetProductList()
        {
            return await _dbContext.Products.ToListAsync();
        }

        public async Task<Product> GetProductById(int id)
        {
            return await _dbContext.Products.Where(x => x.ProductId == id).FirstOrDefaultAsync();
        }

        public async Task<Product> AddProduct(Product product)
        {
            var result = _dbContext.Products.Add(product);
            await _dbContext.SaveChangesAsync();
            return result.Entity;
        }

        public async Task<Product> UpdateProduct(Product product)
        {
            var result = _dbContext.Products.Update(product);
            await _dbContext.SaveChangesAsync();
            return result.Entity;
        }

        public async Task<bool> DeleteProduct(int Id)
        {
            var filteredData = _dbContext.Products.Where(x => x.ProductId == Id).FirstOrDefault();
            var result = _dbContext.Remove(filteredData);
            await _dbContext.SaveChangesAsync();
            return result != null ? true : false;
        }
    }
}

Step 10)

Register a few services inside the Program class related to product and database service: 

using GlobalExceptionHandling.Data;
using GlobalExceptionHandling.Services;
using GlobalExceptionHandling.Utility;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddDbContext<DbContextClass>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 11)

Add a database connection string inside the appsetting.json file: 

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP;Initial Catalog=GlobalExceptionHandling;User Id=sa;Password=*****@1;"
  }
}

Step 12)

Create Product Controller.

  • Here you can see we throw an exception inside get product by id and filter method at lines no 35 and 63, you can use any exception that you want to handle. 
using GlobalExceptionHandling.Exceptions;
using GlobalExceptionHandling.Model;
using GlobalExceptionHandling.Services;
using Microsoft.AspNetCore.Mvc;
using NotImplementedException = GlobalExceptionHandling.Exceptions.NotImplementedException;

namespace GlobalExceptionHandling.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private readonly IProductService productService;
        private ILogger<ProductController> _logger;
       
        public ProductController(IProductService _productService, ILogger<ProductController> logger)
        {
            productService = _productService;
            _logger = logger;
        }

        [HttpGet("productlist")]
        public Task<IEnumerable<Product>> ProductList()
        {
            var productList = productService.GetProductList();
            return productList;
        }

        [HttpGet("getproductbyid")]
        public Task<Product> GetProductById(int Id)
        {
            _logger.LogInformation($"Fetch Product with ID: {Id} from the database");
            var product = productService.GetProductById(Id);
            if (product.Result == null)
            {
                throw new NotFoundException($"Product ID {Id} not found.");
            }
            _logger.LogInformation($"Returning product with ID: {product.Result.ProductId}.");
            return product;
        }

        [HttpPost("addproduct")]
        public Task<Product> AddProduct(Product product)
        {
            return productService.AddProduct(product);
        }

        [HttpPut("updateproduct")]
        public Task<Product> UpdateProduct(Product product)
        {
            return productService.UpdateProduct(product);
        }

        [HttpDelete("deleteproduct")]
        public Task<bool> DeleteProduct(int Id)
        {
            return productService.DeleteProduct(Id);
        }

        [HttpGet("filterproduct")]
        public Task<List<Product>> FilterProduct(int Id)
        {
            throw new NotImplementedException("Not Implemented Exception!");
        }
    }
}

Step 13)

Execute database migration scripts inside the package manager console.

add-migration “Initial”

update-database

Step 14)

Create multiple exception classes inside Exception.

  • BadRequestException

    namespace GlobalExceptionHandling.Exceptions
    {
        public class BadRequestException : Exception
        {
            public BadRequestException(string message) : base(message)
            { }
        }
    }

     
  • KeyNotFoundException

    namespace GlobalExceptionHandling.Exceptions
    {
        public class KeyNotFoundException : Exception
        {
            public KeyNotFoundException(string message) : base(message)
            { }
        }
    }

     
  • NotFoundException

    namespace GlobalExceptionHandling.Exceptions
    {
        public class NotFoundException : Exception
        {
            public NotFoundException(string message) : base(message)
            { }
        }
    }

     
  • NotImplementedException

    namespace GlobalExceptionHandling.Exceptions
    {
        public class NotImplementedException : Exception
        {
            public NotImplementedException(string message) : base(message)
            { }
        }
    }

     
  • UnauthorizedAccessException

    namespace GlobalExceptionHandling.Exceptions
    {
        public class UnauthorizedAccessException : Exception
        {
            public UnauthorizedAccessException(string message) : base(message)
            { }
        }
    }

Step 15)

Next, create a GlobalErrorHandlingMiddleware inside utility to handle exceptions: 

using GlobalExceptionHandling.Exceptions;
using System.Net;
using System.Text.Json;
using KeyNotFoundException = GlobalExceptionHandling.Exceptions.KeyNotFoundException;
using NotImplementedException = GlobalExceptionHandling.Exceptions.NotImplementedException;
using UnauthorizedAccessException = GlobalExceptionHandling.Exceptions.UnauthorizedAccessException;

namespace GlobalExceptionHandling.Utility
{
    public class GlobalErrorHandlingMiddleware
    {
        private readonly RequestDelegate _next;

        public GlobalErrorHandlingMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private static Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            HttpStatusCode status;
            var stackTrace = String.Empty;
            string message;

            var exceptionType = exception.GetType();
            if (exceptionType == typeof(BadRequestException))
            {
                message = exception.Message;
                status = HttpStatusCode.BadRequest;
                stackTrace = exception.StackTrace;
            }
            else if (exceptionType == typeof(NotFoundException))
            {
                message = exception.Message;
                status = HttpStatusCode.NotFound;
                stackTrace = exception.StackTrace;
            }
            else if (exceptionType == typeof(NotImplementedException))
            {
                status = HttpStatusCode.NotImplemented;
                message = exception.Message;
                stackTrace = exception.StackTrace;
            }
            else if (exceptionType == typeof(UnauthorizedAccessException))
            {
                status = HttpStatusCode.Unauthorized;
                message = exception.Message;
                stackTrace = exception.StackTrace;
            }
            else if (exceptionType == typeof(KeyNotFoundException))
            {
                status = HttpStatusCode.Unauthorized;
                message = exception.Message;
                stackTrace = exception.StackTrace;
            }
            else
            {
                status = HttpStatusCode.InternalServerError;
                message = exception.Message;
                stackTrace = exception.StackTrace;
            }

            var exceptionResult = JsonSerializer.Serialize(new { error = message, stackTrace });
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)status;
            return context.Response.WriteAsync(exceptionResult);
        }
    }
}
  • The use of global error handling middleware is to handle the exception and it is a type of custom middleware. So, it provides more flexibility to handle the request and response pipeline which is provided by .NET Core.
  • Here in the above class, you can see we used RequestDelegate through dependency injection inside constructor and _next parameter which is used to process the incoming HTTP request and below that there is Invoke method is a process and execute next middleware and process the request.
  • If the request is not processed properly and got some issue then control goes to catch and call or HandleExceptionAsync method to handle the exception and configure stack trace.

Step 16)

Configure your middleware inside the program class: 

using GlobalExceptionHandling.Data;
using GlobalExceptionHandling.Services;
using GlobalExceptionHandling.Utility;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddDbContext<DbContextClass>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

//configure exception middleware
app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware));

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 17)

Finally, run your application.

Step 18)

Here in the below images, you see exceptions when passing invalid product id or sometimes call unimplemented method. I took only these two examples, but there are many scenarios in which you use and handle the exception as per your need.

Conclusion

In this article, we discussed the introduction of exception handling and practical implementation of that using custom middleware step-by-step.

Happy Coding!

Похожее
Oct 20, 2022
Author: Vishal Yelve
ASP.NET Core MVC is a web development framework, widely used by developers around the word, to develop web applications. These web applications have proven to be vulnerable to attacks from different sources, though, and it is our responsibility to safeguard...
Feb 29
Author: Sharmila Subbiah
IntroductionWith the release of .NET 8, Microsoft takes a significant stride forward, introducing the native Ahead-of-Time (AOT) compilation for ASP.NET Core. This advancement not only enhances application performance but also simplifies the development process, marking a new era in the...
Jun 25, 2022
Author: Philipp Bauknecht
Sometimes your web app needs to do work in the background periodically e.g. to sync data. This article provides a walkthrough how to implement such a background task and how to enabled/disable a background task during runtime using a RESTful...
2 дня назад
Author: Paul Balan
Pagination is in front of us everyday yet we take it for granted kind of like we do with most things. It’s what chunks huge lists of data (blog posts, articles, products) into pages so we can navigate through data.This...
Написать сообщение
Почта
Имя
*Сообщение


© 1999–2024 WebDynamics
1980–... Sergey Drozdov
Area of interests: .NET Framework | .NET Core | C# | ASP.NET | Windows Forms | WPF | HTML5 | CSS3 | jQuery | AJAX | Angular | React | MS SQL Server | Transact-SQL | ADO.NET | Entity Framework | IIS | OOP | OOA | OOD | WCF | WPF | MSMQ | MVC | MVP | MVVM | Design Patterns | Enterprise Architecture | Scrum | Kanban