重新整理 .net core 實踐篇—————異常中介軟體[二十]

不問前世發表於2021-06-15

前言

簡單介紹一下異常中介軟體的使用。

正文

if (env.IsDevelopment())
{
	app.UseDeveloperExceptionPage();
}

這樣寫入中介軟體哈,那麼在env環境下就會去執行UseDeveloperExceptionPage。

public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app)
{
	if (app == null)
	{
		throw new ArgumentNullException(nameof(app));
	}

	return app.UseMiddleware<DeveloperExceptionPageMiddleware>();
}

那麼我們應該去看DeveloperExceptionPageMiddleware中介軟體哈。

那麼這裡介紹它是如何能夠捕獲其他中介軟體的異常的哈。

裡面的invoke:

public async Task Invoke(HttpContext context)
{
	try
	{
		await _next(context);
	}
	catch (Exception ex)
	{
	    // 異常處理
	}
}

其實它的操作是很簡單的,直接在外面套了try catch。

裡面的異常處理怎麼處理的可以直接去看DeveloperExceptionPageMiddleware 中介軟體,裡面的操作也比較簡單處理。

測試:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{
	throw new System.Exception("錯誤");
	return 1;
}

結果:

因為上面說了,這個是dev環境下,那麼生產環境不能直接給使用者看到錯誤資訊。

正式環境:

app.UseExceptionHandler("/error");

將錯誤轉移到/error 處理。具體UseExceptionHandler細節篇裡面介紹,有許多可以借鑑的地方。

[ApiController]
[Route("[controller]")]
public class ErrorController : Controller
{
	public ILogger<ErrorController> _logger;
	public ErrorController(ILogger<ErrorController> logger)
	{
		this._logger = logger;
	}

	public IActionResult Index()
	{
		var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

		var ex = exceptionHandlerPathFeature?.Error;

		var knownException = ex as IKnownException;

		if (knownException == null)
		{
			_logger.LogError(ex, ex.Message);
			knownException = KnownException.Unknow;
		}
		else
		{
			knownException = KnownException.FromKnowException(knowException);
		}

		return View(knownException);
	}
}

檢視:

<html>
<head>

</head>
<body>
<div>
    錯誤碼: @Model.ErrorCode
</div>
<div>
    錯誤資訊: @Model.Message
</div>
</body>
</html>

IKnownException:

public interface IKnownException
{
	public string Message { get; }

	public int ErrorCode { get; }

	public object[] ErrorData { get; }
}

KnownException:

public class KnownException : IKnownException
{
	public string Message
	{
		get; private set;
	}

	public int ErrorCode
	{
		get; private set;
	}

	public object[] ErrorData
	{
		get;
		private set;
	}

	public readonly static IKnownException Unknow = new KnownException { Message = "未知錯誤", ErrorCode = 99 };

	public static IKnownException FromKnowException(IKnownException Exception)
	{
		return new KnownException{Message = Exception.Message, ErrorCode = Exception.ErrorCode, ErrorData = Exception.ErrorData};
	}
}

測試1:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{
	throw new System.Exception("錯誤");
	return 1;
}

這種屬於未知異常,結果:

現在弄一個支付異常:

public class PayErrorException : Exception, IKnownException
{
	public PayErrorException(string message, int errorCode, params object[] errorData): base(message)
	{
		this.ErrorCode = errorCode;
		this.ErrorData = errorData;
	}

	public int ErrorCode { get;private set; }

	public object[] ErrorData { get;private set; }
}

測試2:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{

	throw new PayErrorException("支付錯誤",405,null);
	return 1;
}

將異常處理放入到中介軟體分支中。

app.UseExceptionHandler(errApp =>
{
	errApp.Run(async context =>
	{
		var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
		IKnownException knownException = exceptionHandlerPathFeature.Error as IKnownException;
		if (knownException == null)
		{
			var logger = context.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(exceptionHandlerPathFeature.Error, exceptionHandlerPathFeature.Error.Message);
			knownException = KnownException.Unknown;
			context.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knownException = KnownException.FromKnownException(knownException);
			context.Response.StatusCode = StatusCodes.Status200OK;
		}
		var jsonOptions = context.RequestServices.GetService<IOptions<JsonOptions>>();
		context.Response.ContentType = "application/json; charset=utf-8";
		await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(knownException, jsonOptions.Value.JsonSerializerOptions));
	});
});

效果一樣就不演示了。如果是已知異常錯誤碼應該為200,一個是500異常是系統無法處理,系統錯誤,但是已知錯誤是屬於系統正常處理。另一個是監控系統,認為報500錯誤,是會持續放出系統警告。

還有一種區域性異常,只在mvc中生效,而不是全域性生效:

public class MyExceptionFilter : IExceptionFilter
{
	public void OnException(ExceptionContext context)
	{
		IKnownException knownException = context.Exception as IKnownException;
		if (knownException == null)
		{
			var logger = context.HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(context.Exception, context.Exception.Message);
			knownException = KnownException.Unknown;
			context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knownException = KnownException.FromKnownException(knownException);
			context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
		}
		context.Result = new JsonResult(knownException)
		{
			ContentType = "application/json; charset=utf-8"
		};
	}
}

在mvc 中註冊:

services.AddMvc(mvcOptions =>
{
	mvcOptions.Filters.Add<MyExceptionFilter>();
}).AddJsonOptions(jsonOptions =>
{
	jsonOptions.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
});

最後介紹一種,只作用於某個控制器,或者action:

public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
	public override void OnException(ExceptionContext context)
	{
		IKnownException knownException = context.Exception as IKnownException;
		if (knownException == null)
		{
			var logger = context.HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(context.Exception, context.Exception.Message);
			knownException = KnownException.Unknown;
			context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knownException = KnownException.FromKnownException(knownException);
			context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
		}
		context.Result = new JsonResult(knownException)
		{
			ContentType = "application/json; charset=utf-8"
		};
	}
}

檢視一下ExceptionFilterAttribute頭部:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ExceptionFilterAttribute : Attribute, IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter

上面標誌了可以放於類上也可以放於方法上。所以可以放至在controller上,也可以action上,看需求了。

以上只是個人整理,如有錯誤,望請指點。

下一節,靜態檔案,以前寫過,重新整理一下。

相關文章