一、异常拦截思路
新建ExceptionFilter这个异常的拦截器,用于记录工程抛异常并做对应回调处理。
二、实现核心
2.1、异常拦截类
/***
* Title:".NET Core WebApi" 项目
* 主题:异常拦截
* Description:
* 功能:实现拦截异常内容记录到日志
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using log4net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Utils;
namespace Filters
{
public class ExceptionFilter
{
private readonly RequestDelegate _requestDelegate;
private readonly ILog _log = LogManager.GetLogger(WebApi_Learn.Startup.repository.Name, typeof(ExceptionFilter));
/// <summary>
/// 构造函数
/// </summary>
/// <param name="requestDelegate">请求委托</param>
public ExceptionFilter(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
}
/// <summary>
/// 回调
/// </summary>
/// <param name="context">Http内容</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
try
{
await _requestDelegate(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
_log.Error("服务器错误:" + ex.Message);
await ResponseHelper.HandleExceptionAsync(500, "服务器错误:"+ ex.Message);
}
}
}//Class_end
}
2.2、Http响应帮助类
/***
* Title:".NET Core WebApi" 项目
* 主题:响应帮助类
* Description:
* 功能:
* 1、异常处理回调
* 2、统一请求页面实体
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using log4net;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Utils
{
public class ResponseHelper
{
#region 基础参数
//服务接口
public static IServiceProvider serviceProvider;
#endregion
/// <summary>
/// 异常处理回调
/// </summary>
/// <param name="statusCode">html状态码</param>
/// <param name="msg">消息</param>
/// <returns></returns>
public static Task HandleExceptionAsync(int statusCode, string msg)
{
var data = new { code = statusCode, msg = msg };
string text = JsonConvert.SerializeObject(data);
var response = HttpCurrent.Response;
if (string.IsNullOrEmpty(response.ContentType))
{
//跨域的时候注意,不带header没法接收回调
response.Headers.Add("Access-Control-Allow-Origin", "*");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
//因为这个是json
response.ContentType = "application/json;charset=utf-8";
response.StatusCode = 200;
response.ContentLength = text.Length;
return response.WriteAsync(text);
}
else
{
return response.WriteAsync(text);
}
}
/// <summary>
/// 统一请求页面实体
/// </summary>
public static HttpContext HttpCurrent
{
get
{
object factory = serviceProvider.GetService(typeof(IHttpContextAccessor));
HttpContext context = ((IHttpContextAccessor)factory).HttpContext;
return context;
}
}
}//Class_end
}
三、在中间件中调用
3.1、在Startup类中的Configure方法中调用异常过滤
//使用我们的异常拦截中间件
Utils.ResponseHelper.serviceProvider = app.ApplicationServices;
app.UseMiddleware<Filters.ExceptionFilter>();
3.2、调用的接口
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace WebApi_Learn.Controllers
{
[ApiController]
[Route("api/[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
/// <summary>
/// 获取到当前的天气信息
/// </summary>
/// <returns></returns>
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
/// <summary>
/// 获取本地文件
/// </summary>
/// <returns></returns>
[HttpGet]
public Task<IActionResult> GetPath()
{
string filePathAndName = @"C:/test.txt";
if (System.IO.File.Exists(filePathAndName))
{
FileInfo file = new FileInfo(filePathAndName);
return GetFileStreamResult(file);
}
else
{
throw new Exception();
}
}
/// <summary>
/// 获取到文件流结果
/// </summary>
/// <param name="fileInfo">FileInfo文件</param>
/// <returns>返回FileStreamResult</returns>
private async Task<IActionResult> GetFileStreamResult(FileInfo fileInfo)
{
if (fileInfo == null) return NotFound();
var memoryStream = new MemoryStream();
using (var stream = new FileStream(fileInfo.FullName, FileMode.Open))
{
await stream.CopyToAsync(memoryStream);
}
memoryStream.Seek(0, SeekOrigin.Begin);
//文件名必须编码,否则会有特殊字符(如中文)无法在此下载。
string encodeFilename = System.Net.WebUtility.UrlEncode(fileInfo.Name);
Response.Headers.Add("Content-Disposition", "attachment; filename=" + encodeFilename);
return new FileStreamResult(memoryStream, "application/octet-stream");//文件流方式,指定文件流对应的ContenType。
}
}
}
3.3、运行结果和日志
四、AOP介绍
AOP内容来自:AOP 的详细说明以及基本的使用
4.1、AOP简介
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的复用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术。
剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
4.2、AOP作用
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。