.Net Core Web API MiddleWare’de Exception Handler

Merhaba,

Bugün ki makalemizde .Net Core Web API MiddleWare‘de Custom exception handler yapacağız.

Kullanıcılarımıza sunduğumuz bir servis’te işlem esnasında herhangi bir hata oluştuğunda kullanıcıya eğer bir mesaj döndermessek exception’ın tamamını servis çağırıldığında exception detayına basacaktır. Ama bu durumun böyle olmasını istemeyiz.

Örnek olarak çağırdığımız servisin başına bir throw yerleştirdim. Sistem böyle bir hata aldığında kullanıcıya aşağıdaki bir çıktı vermek. Kullanıcı için bu dönen sonuç anlamsız olacaktır.

Böyle bir sonuç doğurmaması için kullanıcıya onunda anlayabileceği bir sonuç dönmeliyiz.

Bunun önüne geçmek için Middleware‘de bir Exception Handler yapısı oluşturacağım.

İlk olarak Visual Studio ortamında bir .Net Core Web API projesi oluşturuyorum.

Oluşturduğum projenin katmanlarını basit şekilde aşağıdaki gibi oluşturuyorum.

Models katmanının içerisine servis sonuçlarımızı dönebileceğimiz bir “OperationResult” adında bir sınıf ekliyorum.

    public class OperationResult
    {
        public OperationResult()
        {
            IsSuccessful = true;
            ReturnMessage = "İşlem Başarılı";
            ReturnCode = 100;
        }
        public bool IsSuccessful { get; set; }
        public int ReturnCode { get; set; }
        public string ReturnMessage { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }

Ve bu oluşturduğumuz sınıfı base bir sınıfımıza field olarak ekliyorum. Bu sayede tüm işlemlerde kullanabilme imkanı bulabileceğim.

Exception sınıfından türeyen bir custom bir exception sınıfı oluşturuyoruz.

    public class CustomException : Exception
    {
        public string ReturnMessage { get; set; }
        public override string Message => ReturnMessage;
        public CustomException()
        {

        }


        public string Code { get; }


        public CustomException(string code)
        {
            Code = code;
        }
        public CustomException(string code, string message)
        {
            Code = code;
            ReturnMessage = message;
        }

        public CustomException(string message, params object[] args)
            : this(string.Empty, message, args)
        {
        }
        public CustomException(string code, string message, params object[] args)
            : this(null, code, message, args)
        {
            ReturnMessage = String.Format(message, args);
            Code = code;
        }

        public CustomException(Exception innerException, string message, params object[] args)
            : this(innerException, string.Empty, message, args)
        {
        }

        public CustomException(Exception innerException, string code, string message, params object[] args)
            : base(string.Format(message, args), innerException)
        {
            Code = code;
        }

    }

Artık exception işlemlerimiz için yukarıda oluşturduğumuz CustomException sınıfını kullanacağız.

Business katmanına BusinessBase adında bir sınıf ekliyoruz ve bu sınıf içerisine business işlemlerinin execute olduğunda kullanacağımız exception handler opeartion methodlarını ekliyoruz. Diğer business sınıflarımız bu sınıftan türeyeceği için bu methodları kullanabilir hale gelecektir.

protected T ExecuteWithExceptionHandledOperation<T>(Func<T> func)
        {
            try
            {
                var result = func.Invoke();

                return result;
            }
            catch (CustomException ex)
            {
                throw new CustomException(ex.Code, ex.ReturnMessage);
            }
            catch (Exception ex)
            {
                throw new CustomException("500", ex.Message);
            }
        }

Yukarıda business işlemlerini execute etmekte kullanacağımız method try blokları içerisinde herhangi bir hata olmazsa invoke olacak ve işlemlermize başarılı bir şekilde devam edecek. Eğer cache bloğuna düşerse hata yakalama işlemini sağlayacaktır. Burada ilk olarak kendi CustomException kısmını kontrol ediyoruz. Aslında burda bu şekilde bir kontrol eklememin sebebi kendi iş kesici işlemlerimizide kullanıcıya throw atarak göstereceğim.

Şuana kadar yaptığımız işlemlerde hatayı kendi CustomException sınıfından handle edilmesini sağladık. Şimdi gerekli konfigürasyonlarımızı yaparak yakalanan hataya nasıl işlem yapmamız gerektiğini belirleyeceğiz.

API katmanına CustomExceptionExtension adında bir sınıf oluşturuyorum.

    public static class     public static class CustomExceptionExtension

    {
        public static void ConfigureExceptionHandler(this IApplicationBuilder app)
        {
            app.UseExceptionHandler(appError =>
            {
                appError.Run(async context =>
                {

                    context.Response.ContentType = "application/json";

                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                    CustomException exception = (CustomException)contextFeature.Error;
                    if (exception.Code == "500")
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    }
                    else
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.OK;
                    }
                    if (contextFeature != null)
                    {
                        
                        await context.Response.WriteAsync(new OperationResult()
                        {
                            IsSuccessful = false,
                            ReturnCode = Convert.ToInt32(exception.Code),
                            ReturnMessage = contextFeature.Error.Message

                        }.ToString());
                    }
                });
            });
        }
    }

Yukarıdaki CustomExceptionExtension sınıfında business katmanında yakalanan hatayı kendi CustomException sınıfına cast edilmesini sağlıyoruz. Burada eğer hata kodu “500” dışında bir değer gelerek throw fırlatılırsa kendi iş kesicilerimiz olduğunu anlıyoruz. Response’nin döneceği modeli yani OperationResult modeline gerekli atamaları yaparak. Response edilmesini sağlıyoruz.

Business katmanına UserBusiness adında bir sınıf ekliyorum. Ve içerisine basit bir Login methodu yazıyorum.

    public class UserBusiness :  BusinessBase,IUserBusiness
    {
        public UserLoginOutput Login(UserLoginInput input)
        {
            return base.ExecuteWithExceptionHandledOperation(() =>
            {
                throw new ArgumentNullException();
                if (input.Email == "[email protected]" && input.Password == "123")
                {
                    return new UserLoginOutput()
                    {
                        Id = "1",
                        OperationResult = new OperationResult()
                    };
                }
                else
                {
                    throw new CustomException("200", "Kullanıcı adı veya şifre yanlış");
                }

            });

            }
    }

Yukarıda işlemde görüldüğü gibi kendi business katmanımızdaki işlemler içinde oluşturduğumuz CustomException sınıfını kullandım. Burada BusinessBase sınıfında oluşturduğumuz ExceptionHandler method sayesinde işlem Invoke olacak veya da catch bloğuna düşecektir.

WebApi katmanındaki Startup.cs sınıfına dependency injection’ı sağlamak için aşağıdaki konfigürasyonu ekliyoruz. Vede oluşturduğumuz exception handler konfigürasyonunu ekliyoruz.

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddScoped<IUserBusiness, UserBusiness>();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            ...
            app.ConfigureExceptionHandler();
            app.UseRouting();
            ...
        }

Şimdi WebApi katmanına bir UserController adında bir controller ekliyorum. Ve içerisine Login methodu ekliyorum.

[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
    private readonly IUserBusiness _business;
    public UserController(IUserBusiness business)
    {
        _business = business;
    }
    [HttpPost]
    public UserLoginOutput Login(UserLoginInput userLoginInput)
    {
        return _business.Login(userLoginInput);
    }
 }

İşlemler esnasında bizim oluşturduğumuz hata durumundaki oluşan response ve işlem başarılı olma durumunda oluşan response aşağıdaki gibidir.

Şimdi de işlem esnasında hata oluşturmak için kodun başına throw fırlatarak nullexception atmasını sağladım. Şimdi de onun çıktısını görelim.

Görüldüğü gibi kendi oluşturduğumuz işlem hatasında status “200” yani başarılı gelmişti. Bizim oluşturduğumuz işlem dışındaki hatalarda status “500” yani internal server error şeklinde verdi.

Yaptığımız bu işlemler sayesinde hem başarılı olma durumunu, iş kesici hatalarımızı ve bizim dışımızdan kaynaklanan hataları kullanıcıya aynı formattaki response’nin dönmesini sağladık.

Umarım yararı olmuştur. Makalenin kaynak kodlarına burdan erişebilirsiniz.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *