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. cocos2d-x 3.2锚点,Point,addchild,getcontensize

    一,锚点 打个比方.在墙挂一幅画时,要钉一个钉子,那个钉子就是锚点. 然后挂图时,钉子(锚点)放在要订的位置(position),订下去.完成(贴图结束). 贴图的基本点,锚点默认为(0.5,0.5) ...

  2. PhpSpreadsheet 导出特定格式 — 广告请款单

    需求说明 最近需要实现一个导出这种格式的Excel表单,之前都有用过导出Excel的功能,但大都是表头+数据的形式,只用于获取数据,没有太多样式要求,不用合并单元格.合并居中等,也不用对每一行数据特异 ...

  3. 让你如“老”绅士般编写 Python 命令行工具的开源项目:docopt

    作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  4. MySQL 插入记录时自动更新时间戳

    将字段设置成timestamp类型,同时默认值设置成 CURRENT_TIMESTAMP.

  5. (22)ASP.NET Core EF创建模型(索引、备用键、继承、支持字段)

    1.索引 索引是跨多个数据存储区的常见概念.尽管它们在数据存储中的实现可能会有所不同,但也可用于基于列(或一组列)更高效地进行查找. 1.1约定 按照约定,将在用作外键的每个属性(或一组属性)中创建索 ...

  6. WPF使用border画框

    以前的界面中使用的框大都是由美工做好的,但是这样就遇到几个问题: 框只是换一个颜色,就需要多做出一张图,资源包中也要多一个图片资源: 文字的数量会改变,用一张固定的图进行拉伸,边角处会变得越来越不尽如 ...

  7. Spring Boot2 系列教程(十六)定时任务的两种实现方式

    在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Qua ...

  8. PHP代码审计基础-高级篇

    高级篇主要讲 1. 熟知各个开源框架历史版本漏洞. 2. 业务逻辑漏洞 3. 多线程引发的漏洞 4. 事务锁引发的漏洞 在高级篇审计中有很多漏洞正常情况下是不存在的只有在特殊情况下才有 PHP常用框架 ...

  9. MS12-042 用户态调度机制特权提升漏洞

    漏洞编号:MS12-042 披露日期: 2012/6/12 受影响的操作系统:Windows 2000;XP;Server 2003;windows 7;Server 2008; 测试系统:windo ...

  10. 从C++到C++/CLI

    本文转载于:https://www.cnblogs.com/feisky/archive/2009/11/22/1607999.html 刘未鹏(pongba) /文 看起来只是在C++后面多写了一个 ...