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.事务、日志及错误处理的更多相关文章

  1. Asp.net Core 系列之--5.认证、授权与自定义权限的实现

    ChuanGoing 2019-11-24 asp.net core系列已经来到了第五篇,通过之前的基础介绍,我们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,并且简单 ...

  2. 【目录】asp.net core系列篇

    随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...

  3. asp.net core 系列 22 EF(连接字符串,连接复原,DbContext)

    一.连接字符串 在上二篇中,ASP.NET Core 应用程序连接字符串是写死在ConfigureServices代码中,下面介绍通过配置来实现.连接字符串可以存储在 appsettings.json ...

  4. asp.net core 系列 17 通用主机 IHostBuilder

    一.概述 ASP.NET Core 通用主机 (HostBuilder),该主机对于托管不处理 HTTP 请求的应用非常有用.通用主机的目标是将 HTTP 管道从 Web 主机 API 中分离出来,从 ...

  5. asp.net core 系列 16 Web主机 IWebHostBuilder

    一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适 ...

  6. 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 重点: 实现多级子目录的压缩, ...

  7. ASP.NET Core系列(二):创建第一个.Net Core 项目

    前面讲过 .NET Core简介及开发环境安装,本章会讲一讲ASP.NET Core 2.0的项目结构,查看完整的ASP.NET Core系列文章:https://www.cnblogs.com/zh ...

  8. Asp.net Core 系列之--3.领域、仓储、服务简单实现

    ChuanGoing 2019-11-11  距离上篇近两个月时间,一方面时因为其他事情耽搁,另一方面也是之前准备不足,关于领域驱动有几个地方没有想通透,也就没有继续码字.目前网络包括园子里大多领域驱 ...

  9. asp.net core系列 76 Apollo 快速安装模式下填坑和ASP.NetCore结合使用

    前言:由于公司占时没有运维,出于微服务的需要,Apollo只能先装在windows 阿里云上跑起来,由于环境及网络等问题,在安装过程中遇到很多坑,算是一个个坑填完后,最终实现. 一. java jdk ...

随机推荐

  1. icon font在sketch中的下载与安装

    icon font的下载安装: 1.首先打开sketch--插件--管理插件--获取插件--搜索 icon font--点击icon font--clone or download--下载的是一个sk ...

  2. 写在Python学习前

    Python是一门非常有意思的语言,不需要太多的语言基础,就可以实现自己想要的操作.许多非计算机专业的学生都可以利用词云分析.分词.画图等实现自己想要的东西.学习Python和学习其他语言一样,需要耐 ...

  3. 禅道部署(基于Linux)

    部署步骤: 1. 查看Linux服务器是32位还是64位的 #getconf LONG_BIT 2. 禅道开源版安装包下载 下载站点1:#wget    http://sourceforge.net/ ...

  4. 【Medium 万赞好文】ViewModel 和 LIveData:模式 + 反模式

    原文作者: Jose Alcérreca 原文地址: ViewModels and LiveData: Patterns + AntiPatterns 译者:秉心说 View 和 ViewModel ...

  5. windows服务隐藏后门之克隆帐号

    windows服务隐藏后门之克隆帐号 1.CMD命令行下,建立了一个用户名为“test$”,密码为“abc123!”的简单隐藏账户,并且把该隐藏账户提升为了管理员权限. PS:CMD命令行使用&quo ...

  6. Python之random模块和time模块

    1.random()模块的使用   import random x = random.random() y = random.random() print(x,y*10) #random.random ...

  7. Flink 从 0 到 1 学习 —— 如何自定义 Data Source ?

    前言 在 <从0到1学习Flink>-- Data Source 介绍 文章中,我给大家介绍了 Flink Data Source 以及简短的介绍了一下自定义 Data Source,这篇 ...

  8. MS08-067 远程执行代码 漏洞复现

    漏洞编号:MS08-067 披露日期: 2008/10/22 受影响的操作系统:Windows 2000;XP;Server 2003;Server 2008;   目标系统 Microsoft(R) ...

  9. pycharm中debug的使用

    1.未打断点运程序,输出全部结果 2.打断点后,点击debug,代码执行到断点前停止(断点所在行不执行) 3.step over,是在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子 ...

  10. 20.discuz论坛-实现伪静态

    部署discuz论坛 1.直接上配置文件--->>> [root@web01 conf.d]# vim discuz.cheng.com.conf server { listen 8 ...