Asp.net Core 系列之--4.事务、日志及错误处理
ChuanGoing 2019-11-17
这篇原本想把事务处理、日志处理、错误处理、授权与鉴权一并介绍完的,授权和鉴权我想结合自定义权限来介绍,全部放到这里篇幅可能太长,因此权限部分将会在下篇来介绍。先说下我接下来的打算把,下篇将介绍权限控制,结合Oauth2.0和OpenId(OIDC)以及自定义权限来介绍;完了后会结合之前所介绍的基础来实现一个简单的电商网站,当然是利用领域驱动设计来实现。我的这个系列的主题就是领域驱动设计,实现简单电商网站时将会深入的讲解下领域的划分原则及领域服务的场景,中间可能会尝试部分业务实现事件驱动。
本篇学习曲线:
1.日志记录
2.错误处理
3.事务处理
日志记录
NLog是一个记录日志组件,和log4net一样被广泛使用,它可以将日志保存到文本文件、CSV、控制台、VS调试窗口、数据库等。在之前例子中的WebApi项目中添加NLog.Web.AspNetCore的Nuget包,并添加如下配置:

简单介绍下配置信息,“targets”配置每个输出配置,我这里有3个输出:database、allfile、ownfile,分别表示输出到数据库和对应路径的日志文件下。
"rules"规则配置了4条:
1.将Debug以上级别(含)信息输出到allfile
2.忽略Microsoft.*开头的信息(对应的输出没有配置到任何文件),此配置一般忽略即可
3.将Debug以上级别(含)信息输出到ownfile(注意这里配置和allfile一样,一般配置级别高点的日志信息)
4.将Warn以上级别(含)信息输出到数据库
完了后,在Program.cs Main方法里面注册NLog:
var logger = NLogBuilder.ConfigureNLog($"Nlog.config").GetCurrentClassLogger();
try
{
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
logger.Error(ex, "Stopped program because of exception");
throw ex;
}
finally
{
NLog.LogManager.Shutdown();
}
注意不要忘了启用NLog组件使之生效

在OrderController的Add方法中加入以下代码:

用postman简单测试下,我们可以看到执行目录中多出来了日志信息

错误处理
这里一般我们关心的错误大概有两类:
1.内部错误,即通过框架(Mvc)管道准确的传入到内部系统中并发生错误的此类信息
2.框架(Mvc)执行管道的某些中间件时发生的错误或被中间件禁止继续访问的请求
因此,定义如下3个类:
public class InnerException : Exception
{
/// <summary>
/// 内部错误代码
/// </summary>
public int? ErrorCode { get; } public InnerException(int errorCode) : base()
{
ErrorCode = errorCode;
} public InnerException(int errorCode, string message) : base(message)
{
ErrorCode = errorCode;
} public InnerException(int code, string message, Exception exception) : base(message, exception)
{
ErrorCode = code;
}
}
InnerException
public class MessageCodes
{
#region 公用 /// <summary>
/// 成功
/// </summary>
public const int Success = ;
/// <summary>
/// 警告
/// </summary>
public const int Warning = ;
/// <summary>
/// 错误
/// </summary>
public const int Error = ;
/// <summary>
/// 数据验证错误
/// </summary>
public const int DataValidationError = ;
/// <summary>
/// 数据不存在
/// </summary>
public const int DataNotFound = ;
/// <summary>
/// 非法的数据状态
/// </summary>
public const int IllegalState = ;
/// <summary>
/// 参数无效
/// </summary>
public const int InvalidParams = ;
/// <summary>
/// 输入非法
/// </summary>
public const int IllegalInput = ;
/// <summary>
/// 鉴权成功
/// </summary>
public const int AuthSuccess = ; #endregion }
MessageCodes
public class WebException: InnerException
{
public HttpStatusCode HttpStatus { get; set; } public HttpRequest Request { get; private set; } public WebException(HttpStatusCode httpStatus, int errorCode, string message)
: base(errorCode, message)
{
HttpStatus = httpStatus;
} public WebException(HttpStatusCode httpStatus, int errorCode, string message, HttpRequest request)
: this(httpStatus, errorCode, message)
{
Request = request;
} public WebException(int errorCode, string message)
: base(errorCode, message)
{
HttpStatus = HttpStatusCode.BadRequest;
}
}
WebException
通过Aop,很方便就可以实现错误信息的处理:
public class ExceptionFilter : IExceptionFilter
{
private readonly ILogger<ExceptionFilter> _logger; public ExceptionFilter(ILogger<ExceptionFilter> logger)
{
_logger = logger;
} public void OnException(ExceptionContext context)
{
_logger.LogError(context.Exception, context.Exception.Message); #region Ioc/automapper等中间件对错误信息进行了包装,需要解包 //web错误:验证/鉴权等
var webException = GetException<Base.Exceptions.WebException>(context.Exception);
if (webException != null)
{
context.Result = new JsonResult(new
{
ErrorCode = webException.ErrorCode ?? MessageCodes.Error,
webException.Message
})
{
StatusCode = (int)webException.HttpStatus
};
return;
}
//内部错误
var exception = GetException<InnerException>(context.Exception);
if (exception != null)
{
context.Result = new JsonResult(new
{
ErrorCode = exception.ErrorCode ?? MessageCodes.Error,
exception.Message
})
{
StatusCode = (int)HttpStatusCode.InternalServerError
};
return;
} #endregion
} private TException GetException<TException>(Exception exception)
where TException : Exception
{
if (exception == null)
{
return null;
}
if (exception is TException tException)
{
return tException;
}
else
{
return GetException<TException>(exception.InnerException);
}
}
}
ExceptionFilter
同时,Startup.cs的ConfigureServices中注册一下:
services.AddMvc(mvcOptions =>
{
mvcOptions.Filters.Add<ExceptionFilter>();
})
即完成了错误信息并且错误信息会写入相应配置的输出中。
事务处理
UnitOfWork又称工作单元,为了保证数据操作完整性,我们将处理数据的的操作统一放在一个事务中,我们这里利用UnitOfWork来实现事务处理。
首先定义IUnitOfWork及UnitOfWork实现:
public interface IUnitOfWork
{
void Begin(IsolationLevel level = IsolationLevel.Unspecified);
void SaveChanges();
void Failed();
}
public class UnitOfWork : IUnitOfWork
{
private ITransactionRepository _repository; public UnitOfWork(ITransactionRepository repository)
{
_repository = repository;
} public virtual void Begin(IsolationLevel level = IsolationLevel.Unspecified)
{
_repository.BeginTransaction(level);
} public virtual void SaveChanges()
{
_repository.Commit();
} public virtual void Failed()
{
_repository.Rollback();
}
}
其中,UnitOfWork依赖于ITransactionRepository的实现:
public interface ITransactionRepository
{
/// <summary>
/// 打开事务
/// </summary>
/// <param name="level"></param>
void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified);
/// <summary>
/// 提交事务
/// </summary>
void Commit();
/// <summary>
/// 事务回滚
/// </summary>
void Rollback();
}
ITransactionRepository
利用DapperRepository继承ITransactionRepository并实现:
public virtual void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified)
{
DbContext.BeginTransaction(level);
} public virtual void Commit()
{
DbContext.Commit();
} public virtual void Rollback()
{
DbContext.RollBack();
}
基本功能实现后,如何使用呢?这里还是需要利用Aop:
public class UnitOfWorkAttribute : AbstractInterceptorAttribute
{
public override Task Invoke(AspectContext context, AspectDelegate next)
{
if (context.Implementation is IApplicationService applicationService)
{
var uow = applicationService.UnitOfWork;
uow.Begin();
var aspectDelegate = next(context);
if (aspectDelegate.Exception != null)
{
uow.Failed();
throw aspectDelegate.Exception;
}
else
{
uow.SaveChanges();
return aspectDelegate;
}
}
else
{
return next(context);
}
}
}
UnitOfWorkAttribute
因此,我们还需要在Application项目中添加如下代码:
public class ServiceBase<TEntity, TPrimaryKey> : IApplicationService
where TEntity : class, IEntity<TPrimaryKey>
{
protected IMapper Mapper { get; private set; }
public virtual IUnitOfWork UnitOfWork { get; private set; } public ServiceBase(IComponentContext container, ICommandRepository<TEntity, TPrimaryKey> repository)
{
Mapper = container.Resolve<IMapper>();
UnitOfWork = container.Resolve<IUnitOfWork>(new TypedParameter(typeof(ITransactionRepository), repository));
}
}
ServiceBase
Application中的每个服务去继承上面的ServiceBase,因此每个Application服务都具有了事务处理能力
public interface IOrderService : IScopeInstance
{
[UnitOfWork]
void Add(OrderViewModel order);
OrderViewResult Get(string sn);
}

程序运行时,Add方法前后形成切面,如下图所示,next(context)这里执行的就是Add方法,执行前开启事务,执行后提交

利用Aop特性切面实现事务的无感注入(Asp.net Core 系列之--1.事件驱动初探:简单事件总线实现(SimpleEventBus)Ioc/DI小节中引入了AspectCore动态代理),底层还是依赖IDbConnection的事务相关接口,完整的事务处理大概就是这样了。
详细代码在Github的https://github.com/ChuanGoing/Start.git 的Domain分支可以找到。
Asp.net Core 系列之--4.事务、日志及错误处理的更多相关文章
- Asp.net Core 系列之--5.认证、授权与自定义权限的实现
		
ChuanGoing 2019-11-24 asp.net core系列已经来到了第五篇,通过之前的基础介绍,我们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,并且简单 ...
 - 【目录】asp.net core系列篇
		
随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...
 - asp.net core 系列 22 EF(连接字符串,连接复原,DbContext)
		
一.连接字符串 在上二篇中,ASP.NET Core 应用程序连接字符串是写死在ConfigureServices代码中,下面介绍通过配置来实现.连接字符串可以存储在 appsettings.json ...
 - asp.net core 系列 17 通用主机 IHostBuilder
		
一.概述 ASP.NET Core 通用主机 (HostBuilder),该主机对于托管不处理 HTTP 请求的应用非常有用.通用主机的目标是将 HTTP 管道从 Web 主机 API 中分离出来,从 ...
 - asp.net core 系列 16 Web主机 IWebHostBuilder
		
一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适 ...
 - C#实现多级子目录Zip压缩解压实例  NET4.6下的UTC时间转换  [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了  asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程  asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案  .NET Core开发日志
		
C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...
 - ASP.NET Core系列(二):创建第一个.Net Core 项目
		
前面讲过 .NET Core简介及开发环境安装,本章会讲一讲ASP.NET Core 2.0的项目结构,查看完整的ASP.NET Core系列文章:https://www.cnblogs.com/zh ...
 - Asp.net Core 系列之--3.领域、仓储、服务简单实现
		
ChuanGoing 2019-11-11 距离上篇近两个月时间,一方面时因为其他事情耽搁,另一方面也是之前准备不足,关于领域驱动有几个地方没有想通透,也就没有继续码字.目前网络包括园子里大多领域驱 ...
 - asp.net core系列 76  Apollo 快速安装模式下填坑和ASP.NetCore结合使用
		
前言:由于公司占时没有运维,出于微服务的需要,Apollo只能先装在windows 阿里云上跑起来,由于环境及网络等问题,在安装过程中遇到很多坑,算是一个个坑填完后,最终实现. 一. java jdk ...
 
随机推荐
- springboot redis-cache 自动刷新缓存
			
这篇文章是对上一篇 spring-data-redis-cache 的使用 的一个补充,上文说到 spring-data-redis-cache 虽然比较强悍,但还是有些不足的,它是一个通用的解决方案 ...
 - 实验吧之【简单的sql注入 1、2、3】
			
实验吧的三道sql注入(感觉实验吧大部分web都是注入) 简单的SQL注入 地址:http://ctf5.shiyanbar.com/423/web/ 这道题也是sql注入,输入1,页面显示正常,输出 ...
 - PHP array_filter
			
1.函数的作用:过滤数组中的值: 2.函数的参数: @params array $array @params callback $callback @params int $flag [ARRAY ...
 - [BZOJ3449] [Usaco2014 Feb]Secret Code
			
Description Farmer John has secret message that he wants to hide from his cows; the message is a str ...
 - css涂鸦这样玩
			
前言 上一次深扒CSS的时候,还说CSS和H5绘制复杂图形很麻烦,看了大神的操作后,感觉茅塞顿开了,哈哈. 就算可能我暂时没有用到的机会,学习一下开发者的设计思路也是受益匪浅呀. 嗯,今天要介绍的是一 ...
 - libevent::事件
			
/***************************************************************** 函数功能: 创建事件集 ********************* ...
 - vue   MD5  加密
			
确保vue项目中有MD5的依赖,当然没有的可以安装crypto模块. npm安装: npm install --save crypto 在main.js文件中将md5引入,可以全局使用的 import ...
 - 第三方应用  flashfxp,filezilla提权
			
filezilla 提权 filezilla 开源的ftp服务器 默认监听14147端口 默认安装目录下有个敏感文件 filezillaserver.xml(包含用户信息) filezillaserv ...
 - Spring Cloud Alibaba(三)Sentinel之熔断降级
			
本项目演示如何使用 Sentinel 完成 Spring Cloud 应用的熔断降级调用. Sentinel 是阿里巴巴开源的分布式系统的流量防卫组件,Sentinel 把流量作为切入点,从流量控制, ...
 - 搭建 webpack + react 框架爬坑之路
			
由于工程实践需要搭一个 webpack + react 框架,本人刚开始学,就照b站上的react黑马视频做,爬过无数个坑...希望读者能引以为戒.我的是macos系统 https://www.bil ...