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 ...
随机推荐
- CentOS 7 单机安装Redis Cluster(3主3从)
首先,本篇要基于单实例的安装,你的机器上已经有一个Redishttps://gper.club/articles/7e7e7f7ff7g5egc4g6b 为了节省机器,我们直接把6个Redis实例安装 ...
- 代码审计-DedeCMS-V5.7前台任意用户密码重置
0x01 漏洞影响 该漏洞允许攻击者修改任意前台用户密码. 0x02 漏洞利用条件 1,开启会员模块 2,攻击者拥有一个正常的会员账号 3,目标没有设置安全问题 0x03 漏洞分析 漏洞文件:/mem ...
- 在博客中增加自己的live2d纸片人模型方法
目录 在博客中增加自己的live2d纸片人模型 准备工具 使用步骤 附件 在博客中增加自己的live2d纸片人模型 准备工具 github仓库:存放live2d模型和json文件 如果你的博客支持本地 ...
- Django RESRframework奇淫技巧
Django RESRframework Mixins, ViewSet和router配合使用 Mixins的类共有五种 CreateModelMixin ListModelMixin Retriev ...
- Ubuntu中用户名密码和root密码修改
用户名密码和root密码不是同一个密码 重置(修改)root密码 ubuntu的root初始密码是随机的,每次开机都有一个新的root密码修改方法如下: 1.sudo passwd root 2.此处 ...
- 在Python中,输出格式:%d , %6d , %-6d, %06d , %.6f的一些区分
和C/C++编程语言一样 %d 普通的整数输出 i = 1 sum = 0 while i <= 100: sum += i i += 1 print("1到100的和为:%d&quo ...
- 利用Arthas定位线上问题实例
前言 Arthas是一个类似于Btrace的JVM在线调试分析工具,具体可参考我之前写的一篇博客:利用JVM在线调试工具排查线上问题.本文分享笔者刚遇到的一个问题,虽然不复杂,但是很典型. 问题与分析 ...
- Redis真集群安装
Redis真集群安装 命令文档:http://redisdoc.com/index.html 下载:https://code.google.com/archive/p/redis/downloads ...
- 从零开始把项目发布到maven仓库中心
sonatype准备操作 注册账号 https://issues.sonatype.org 1. 密码符号规范,并且工记住 新建项目 1. group id 如果你有com域名的所有权可以直接使用,如 ...
- 收藏收藏:时隔一年,你关注的打造一个实用的TXT文本操作及日志框架,我们开源了,不再为程序写日志发愁(也支持.net core哦)
记得做这个框架是在2018年刚接触.net core的时候,那个时候为了能够专心的研究我开始不写博客了,但是学有所成并在公司运用了近一年的时间了,决定回来和各位分享我们所掌握的那星星点点的知识,希望可 ...