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. postman动态数据获取

    1.以获取token(JWT)和uid为例 2.在登录接口的tests中写入代码(因为登录接口报文信息中有返回JWT和uid) 3.在其他接口中需要用到JWT和uid的地方设置变量{{JWT}}和{{ ...

  2. 【Java 基础】谈谈集合.List

    目录 1. ArrayList 1.1 ArrayList的构造 1.2 add方法 1.3 remove方法 1.4 查询方法 1.5 一些其他常用方法 1.6 ArrayList小结 2. Vec ...

  3. 套壳浏览器与Chrome浏览器之间的差别

    之前QQ浏览器一直是我前端调试工具的主力,因为它是一个套壳浏览器,所以它的兼容模式(谷歌Chrome内核)和极速模式(IE浏览器内核)简直是调试兼容性的神器,可以直接切换,不用再反复打开Chrome和 ...

  4. Python开发【第九篇】字典

    字典 字典是一种可变的容器,可以存储任意类型的数据 字典中的每个数据都是用键进行索引,而不像序列容器(str,list,tuole)可以用整数进行索引 字典中的数据没有先后顺序,字典的存储是无序的 字 ...

  5. 【Labview入门】将输入度数转换为3位精度弧度值

    Labview版本2015 程序如下: 可以右键输出控件选择属性来调整输出的小数位数: 运行结果:

  6. 03 Node.js学习笔记之根据http请求路径返回不同数据

    在Nodejs中,当客户端请求的路径不同时,NodeJS处理返回不同的数据 步骤: //1.载入http模块 var http=require('http'); //2.创建一个http服务 var ...

  7. C# 添加、修改、删除Excel图表数据标签

    图表中,图表数据标签以数据化形式表现图表中的特定数据,可增强图表的可读性.我们可以对图表添加数据标签,也可以对已有的数据标签进行修改或者删除,下面将通过C#代码形式来实现. 使用工具:Spire.XL ...

  8. .NET Core 3.0 本地工具

    .NET Core从最早期的版本就开始支持全局工具了.如果仅仅需要在某个项目中或某个文件夹中使用特定的工具,那么.NET Core 3.0就允许您这样做. 使用.NET Core 3.0,您可以在特定 ...

  9. 百万年薪python之路 -- 字典(dict)

    1.字典(dict)-- dict关键字 字典(dict)是python中唯⼀的⼀个映射类型.他是以{ }括起来的键值对组成. ​ 字典中逗号分隔叫作一个元素 ​ 字典是无序的 ​ key必须是不可变 ...

  10. Linux 修改网卡名

    1. 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens32 (“ens32”为当前网卡名) 将NAME.DEVICE项修改为eth0 2.  ...