《Asp.Net Core3 + Vue3入坑教程》 - 6.異常處理與UserFriendlyException

Iannnnnnnnnnnnn發表於2021-03-08

簡介

《Asp.Net Core3 + Vue3入坑教程》 此教程適合新手入門或者前後端分離嘗試者。可以根據圖文一步一步進操作編碼也可以選擇直接檢視原始碼。每一篇文章都有對應的原始碼

目錄

《Asp.Net Core3 + Vue3入坑教程》系列教程目錄

Asp.Net Core後端專案

  1. 後端專案搭建與Swagger配置步驟
  2. 配置CROS策略解決跨域問題
  3. AutoMapper & Restful API & DI
  4. EF Core & Postgresql
  5. .Net Core 3升級成 .Net 5 & JWT
  6. (本文)異常處理與UserFriendlyException

Vue3 前端專案

  1. 使用vue-cli建立vue專案
  2. (暫未發表敬請期待...)使用Ant Design of Vue編寫頁面 & vue-router 初試
  3. (暫未發表敬請期待...)將Antd導航選單與vue-router繫結
  4. (暫未發表敬請期待...) 儲存使用者登入狀態vuex初試

本文簡介

本文為《Asp.Net Core3 + Vue3入坑教程》系列教程的後端第六篇 - 異常處理與UserFriendlyException上文已經為Simple專案升級了SDK並且應用了JWT,本文繼續為Simple專案增加異常處理與使用友好異常(UserFriendlyException)。

為什麼需要使用友好異常的方式進行開發呢?

在很多情況下,我們在一個方法中往往包含著校驗引數返回結果兩個動作,這時候我們的返回結果就需要考慮用物件來包裹校驗結果返回結果。 如果我們使用友好異常,預設方法能順利通過校驗並返回正確的結果,如果校驗出現失敗的情況則將失敗原因通過友好異常的方式返回給呼叫者,可以讓方法的返回內容不需要考慮校驗的結果,程式碼更簡潔明瞭!

使用者友好參照了開源專案ABP專案 https://docs.abp.io/zh-Hans/abp/latest/Exception-Handling

異常處理與UserFriendlyException

第一步先增加測試程式碼,修改SqlCommanderRepo.cs

程式碼調整如下:

using Simple_Asp.Net_Core.Models;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Simple_Asp.Net_Core.Data
{
    public class SqlCommanderRepo : ICommanderRepo
    {
        private readonly CommanderContext _context;

        public SqlCommanderRepo(CommanderContext context)
        {
            _context = context;
        }

        public void CreateCommand(Command cmd)
        {
            if (cmd == null)
            {
                throw new ArgumentNullException(nameof(cmd));
            }

            _context.Commands.Add(cmd);
        }

        public void DeleteCommand(Command cmd)
        {
            if (cmd == null)
            {
                throw new ArgumentNullException(nameof(cmd));
            }
            _context.Commands.Remove(cmd);
        }

        public IEnumerable<Command> GetAllCommands()
        {
            return _context.Commands.ToList();
        }

        public Command GetCommandById(int id)
        {
            if (id == 0)
                throw new Exception("id不能為0!");

            return _context.Commands.First(p => p.Id == id);
        }

        public bool SaveChanges()
        {
            return (_context.SaveChanges() >= 0);
        }

        public void UpdateCommand(Command cmd)
        {
            //Nothing
        }
    }
}

執行專案,呼叫介面api/commands/{id}介面,當請求引數id設定為0時,後端會丟擲異常資訊。

當前的異常資訊將程式內部內容都暴露出來,並且返回資訊也不清晰,呼叫者難以處理。

接著在ServiceProvider資料夾下增加自定義異常類UserFriendlyException.cs

程式碼如下:

using System;

namespace Simple_Asp.Net_Core.ServiceProvider
{
    public class UserFriendlyException : Exception
    {
        public UserFriendlyException(string message) : base(message)
        {
        }

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

在ServiceProvider資料夾下增加類ExceptionHandler.cs,用來處理異常

在捕捉到程式異常的時候需要寫入日誌方便問題追蹤

程式碼如下:

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.Text;
using System.Threading.Tasks;

namespace Simple_Asp.Net_Core.ServiceProvider
{
    public class ExceptionHandler
    {
        public static Task ErrorEvent(HttpContext context)
        {
            var feature = context.Features.Get<IExceptionHandlerFeature>();
            var error = feature?.Error;

            if (error.GetType() == typeof(UserFriendlyException))
            {
                SetResponse(context);
                var content = GetApiResponse(error.Message);

                return context.Response.WriteAsync(JsonConvert.SerializeObject(content), Encoding.UTF8);
            }
            else
            {
                // 寫入日誌
                // error.Message
                // error.StackTrace

                SetResponse(context);
                var content = GetApiResponse("程式發生錯誤,請聯絡客服!");
                return context.Response.WriteAsync(JsonConvert.SerializeObject(content), Encoding.UTF8);
            }
        }

        /// <summary>
        /// 解決異常訊息返回跨域問題
        /// </summary>
        private static void SetResponse(HttpContext context)
        {
            context.Response.Clear();
            context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
            context.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET");
            context.Response.ContentType = "application/json";
        }

        /// <summary>
        /// 響應Response
        /// </summary>
        private static ErrorResponse GetApiResponse(string message)
        {
            return new ErrorResponse() { success = false, message = message };
        }

        private class ErrorResponse
        {
            public bool success { get; set; }
            public bool Success { get { return success; } }
            public string message { get; set; }
            public string Message { get { return message; } }
        }
    }
}

調整Startup.cs,增加異常捕捉

程式碼如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.ServiceProvider;
using System;

namespace Simple_Asp.Net_Core
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddJWT();

            services.AddDbContext<CommanderContext>(options =>
                options.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=123456"));

            services.AddCORS();
            services.AddMvc();
            services.AddSwagger();

            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

            services.AddScoped<ICommanderRepo, SqlCommanderRepo>();

            services.AddControllers().AddNewtonsoftJson(s =>
            {
                s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
                });
            }
            app.UseExceptionHandler(builder => builder.Run(async context => await ExceptionHandler.ErrorEvent(context)));
            app.UseCors("CorsTest");
            app.UseAuthentication();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
        }
    }
}

啟動專案,呼叫api/commands/{id}介面,可以看出後端的介面發生了異常,此時的異常比較清晰。

最後我們將SqlCommanderRepo.cs裡的異常改為友好異常

再次修改SqlCommanderRepo.cs

程式碼調整如下:

using Simple_Asp.Net_Core.Models;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Simple_Asp.Net_Core.Data
{
    public class SqlCommanderRepo : ICommanderRepo
    {
        private readonly CommanderContext _context;

        public SqlCommanderRepo(CommanderContext context)
        {
            _context = context;
        }

        public void CreateCommand(Command cmd)
        {
            if (cmd == null)
            {
                throw new ArgumentNullException(nameof(cmd));
            }

            _context.Commands.Add(cmd);
        }

        public void DeleteCommand(Command cmd)
        {
            if (cmd == null)
            {
                throw new ArgumentNullException(nameof(cmd));
            }
            _context.Commands.Remove(cmd);
        }

        public IEnumerable<Command> GetAllCommands()
        {
            return _context.Commands.ToList();
        }

        public Command GetCommandById(int id)
        {
            if (id == 0)
                throw new Exception("id不能為0!");

            return _context.Commands.First(p => p.Id == id);
        }

        public bool SaveChanges()
        {
            return (_context.SaveChanges() >= 0);
        }

        public void UpdateCommand(Command cmd)
        {
            //Nothing
        }
    }
}

最後啟動專案,呼叫api/commands/{id}介面,這時候我們可以得到友好的提示!

總結

本文為Simple專案增加異常處理與使用友好異常(UserFriendlyException),在捕捉到程式異常的時候需要寫入日誌方便問題追蹤!

目前Simple專案還未使用日誌元件,後續會補上

異常捕捉為了能夠將異常內容進行收集,並且能以統一的方式返回給客戶端,保證伺服器的安全、幫助我們追蹤問題並且客戶端的體驗也能有所保證。

異常捕捉結合友好異常的方式能夠為我們減少程式碼量,並且讓程式碼更直觀明瞭,推薦大家一試

GitHub原始碼

注意:原始碼除錯過程中如果出現xml檔案路徑錯誤,需要參照第一章(後端專案搭建與Swagger配置步驟)Swagger配置“配置XML 文件檔案”步驟,取消勾選然後再選中 ,將XML路徑設定成與你的電腦路徑匹配!

https://github.com/Impartsoft/Simple_Asp.Net_Core/tree/master/Simple_Asp.Net_Core 6.Exception Handling %26 UserFriendlyException

參考資料

ABP開源專案異常處理 https://docs.abp.io/zh-Hans/abp/latest/Exception-Handling

相關文章