在这里,我们将给大家分享关于Asp.netCore系列之--4.事务、日志及错误处理的知识,让您更了解简述事务日志文件在sqlserver2005中的作用的本质,同时也会涉及到如何更有效地.netco
在这里,我们将给大家分享关于Asp.net Core 系列之 --4. 事务、日志及错误处理的知识,让您更了解简述事务日志文件在sql server2005中的作用的本质,同时也会涉及到如何更有效地.net core入门-发布及部署_异常(处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2")处理、.net core系列之《对AOP思想的理解及使用AspectCore实现自定义日志拦截》、4.1ASP.NET Core请求过程「深入浅出ASP.NET Core系列」、Alamofire源码解读系列之错误处理(AFError)的内容。
本文目录一览:- Asp.net Core 系列之 --4. 事务、日志及错误处理(简述事务日志文件在sql server2005中的作用)
- .net core入门-发布及部署_异常(处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2")处理
- .net core系列之《对AOP思想的理解及使用AspectCore实现自定义日志拦截》
- 4.1ASP.NET Core请求过程「深入浅出ASP.NET Core系列」
- Alamofire源码解读系列之错误处理(AFError)
Asp.net Core 系列之 --4. 事务、日志及错误处理(简述事务日志文件在sql server2005中的作用)
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;
}
}


public class MessageCodes
{
#region 公用
/// <summary>
/// 成功
/// </summary>
public const int Success = 20101000;
/// <summary>
/// 警告
/// </summary>
public const int Warning = 20102000;
/// <summary>
/// 错误
/// </summary>
public const int Error = 20103000;
/// <summary>
/// 数据验证错误
/// </summary>
public const int DataValidationError = 20104000;
/// <summary>
/// 数据不存在
/// </summary>
public const int DataNotFound = 20105000;
/// <summary>
/// 非法的数据状态
/// </summary>
public const int IllegalState = 20106000;
/// <summary>
/// 参数无效
/// </summary>
public const int InvalidParams = 20107000;
/// <summary>
/// 输入非法
/// </summary>
public const int IllegalInput = 20108000;
/// <summary>
/// 鉴权成功
/// </summary>
public const int AuthSuccess = 20109000;
#endregion
}


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;
}
}
通过 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);
}
}
}
同时,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();
}
利用 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);
}
}
}
因此,我们还需要在 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));
}
}
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 分支可以找到。
原文出处:https://www.cnblogs.com/ChuanGoing/p/11879153.html
.net core入门-发布及部署_异常(处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2")处理
备注:本人使用开发工具:VS2017,.NET Core 2.2,其中VS2017原本自带2.1,我单独从官网下载了2.2的程序集安装包,但是没有下配套的运行环境,运行项目时出了一个问题。
以下是我在发布Core项目后部署到IIS后运行时遇到的一个异常:处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2"
这里备注一下,如果仅仅是在IIS上运行发布好的项目,只需要安装AspNetCoreModuleV2模块就够了。不需要管模块:AspNetCoreModule 。
如果项目还运行不了,报HTTP Error 500.30 - ANCM In-Process Start Failure ,只需要检查应用程序池的配置是否是无托管代码版本。
先摆出微软Core的官方文档对IIS支持的描述吧:https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-3.0
下载中心:https://dotnet.microsoft.com/download/dotnet-core/3.1 选择IIS支持中的IIS runtime support,里面的Hosting Bundle(托管捆绑包)链接下载
或者直接点击这个地址:https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-3.1#install-the-net-core-hosting-bundle
1-发布设置:
在发布过程中,项目一直还原nuget包没有结果?后来我转移了项目的位置,从C盘桌面挪到F盘,重新编译发布项目,居然可以了! 可能是在C盘还原的时候遇到了只读或权限不足的问题吧,贴图如下
发布成功后,在IIS配置网站到push发布目录中,应用程序池:.net framework版本设置成无托管代码(其实设置了v4.0貌似也没有毛病),
在运行项目时报错如下:处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2"
只需要在运行的服务器上下载一个捆绑运行程序包,就OK了:
下载地址:
https://dotnet.microsoft.com/download/dotnet-core/3.1
选择好版本后,点击去,找到Core运行时的支持:IIS runtime support,里面的Hosting Bundle(托管捆绑包)链接下载。
.net core系列之《对AOP思想的理解及使用AspectCore实现自定义日志拦截》
对于AOP这个名词,相信对于搞过MVC开发的人来说,都很熟悉,里面各种各样的Filter简直是将AOP体现到了极致。
那么什么是AOP呢?
AOP(Aspect Oriented Programming,面向切面编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。OOP是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系;AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。AOP是使用切面(aspect)将横切关注点模块化,OOP是使用类将状态和行为模块化。在OOP的世界中,程序都是通过类和接口组织的,使用它们实现程序的核心业务逻辑是十分合适。但是对于实现横切关注点(跨越应用程序多个模块的功能需求)则十分吃力,比如日志记录,权限验证,异常拦截等。
现在我们将在.net core项目中用AspectCore来实现自定义拦截功能
github地址:https://github.com/dotnetcore/AspectCore-Framework
1、在NuGet包中搜索 AspectCore.Extensions.DependencyInjection 并安装
2、然后我们自定一个记录日志的拦截器
public class LoggerInterceptorAttribute : AbstractInterceptorAttribute
{
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
Console.WriteLine("开始记录日志");
await next.Invoke(context);
Console.WriteLine("结束记录日志");
}
}
3、然后将拦截器以特性的方式实现
public interface IUserRepository
{
void GetUser();
}
public class UserRepository : IUserRepository
{
[LoggerInterceptor]
public void GetUser()
{
Console.WriteLine("获取用户信息业务逻辑");
}
}
static void Main(string[] args)
{
var userRepository = new ServiceCollection()
.AddTransient<IUserRepository, UserRepository>()
.BuildAspectInjectorProvider()
.GetService<IUserRepository>();
userRepository.GetUser();
Console.ReadKey();
}
结果如下:
4.1ASP.NET Core请求过程「深入浅出ASP.NET Core系列」
希望给你3-5分钟的碎片化学习,可能是坐地铁、等公交,积少成多,水滴石穿,谢谢关注。
HTTP请求过程
这里展示整体的HTTP请求的过程,这里化繁为简,保留了主干流程:
从浏览器输入域名开始,这里忽略了建立TCP的3次握手,向服务器发起HTTPRequest请求,服务器接受到之后,会触发服务器对网站的动态解析,然后把生成的网页信息通过HTTPResponse返回给用户,内部包含HTML的Body,Head等信息,最后就是浏览器对这些HTML信息进行内部引擎渲染的过程了。
ASP.NET Core请求过程
ASP.NET Core是整个HTTP请求的一个环节,这个环节都做了哪些工作呢?
如图所示,整个请求流程更加细化,特别是ASP.NET Core Application进行了放大,内部包含很重要的两个组建,一个是Kestrel,一个是管道,管道包含多个中间件,而中间件说白了就是一个委托集合,可以无限扩展。
ASP.NET Core Application
ASP.NET Core Applicaton进一步放大,可以了解到,Kestrel其实在这里并没有做真正的核心处理,只是做一层封装为HttpContext,并往下传。真正处理请求的是管道,管道其实就是RequestDelegate,处理完成后封装成HttpContext进行回传,当然,HttpContext内含HttpRequest和HttpResponse。
管道周围的封装,比如WebHost,我们可以在mvc的Progrms.cs看到他的影子:
同样我们在Startup.cs会看到管道的构建器,ApplicationBuilder
以上只是一个初略的描述,但是作为开发来说,有个初步印象就可以了,再细化下去,已经超过碎片化时间,先打住,如果要继续深究,请关注我后面的专题内容。
我是IT人张飞洪,入行10年有余,人不堪其忧,吾不改其乐,谢谢您关注
Alamofire源码解读系列之错误处理(AFError)
1.正常用法
enum Movement {
case Left
case Right
case Top
case Bottom
}
let aMovement = Movement.Left
switch aMovement {
case .Left:
print("left")
default:
print("Unknow")
}
if case .Left = aMovement {
print("Left")
}
if .Left == aMovement {
print("Left")
}
2.声明为整型
enum Season: Int {
case Spring = 0
case Summer = 1
case Autumn = 2
case Winter = 3
}
3.声明为字符串类型
enum House: String {
case ZhangSan = "I am zhangsan"
case LiSi = "I am lisi"
}
let zs = House.ZhangSan
print(zs.rawValue)
enum CompassPoint: String {
case North, South, East, West
}
let n = CompassPoint.North
print(n.rawValue)
let s = CompassPoint(rawValue: "South");
4.声明为浮点类型
enum Constants: Double {
case π = 3.14159
case e = 2.71828
case φ = 1.61803398874
case λ = 1.30357
}
let pai = Constants.π
print(pai.rawValue)
5.其他类型
enum VNodeFlags : UInt32 {
case Delete = 0x00000001
case Write = 0x00000002
case Extended = 0x00000004
case Attrib = 0x00000008
case Link = 0x00000010
case Rename = 0x00000020
case Revoke = 0x00000040
case None = 0x00000080
}
6.enum包含enum
enum Character {
enum Weapon {
case Bow
case Sword
case Lance
case Dagger
}
enum Helmet {
case Wooden
case Iron
case Diamond
}
case Thief
case Warrior
case Knight
}
let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron
7.结构体和枚举
struct Scharacter {
enum CharacterType {
case Thief
case Warrior
case Knight
}
enum Weapon {
case Bow
case Sword
case Lance
case Dagger
}
let type: CharacterType
let weapon: Weapon
}
let sc = Scharacter(type: .Thief, weapon: .Bow)
print(sc.type)
8.值关联
enum Trade {
case Buy(stock: String, amount: Int)
case Sell(stock: String, amount: Int)
}
let trade = Trade.Buy(stock: "Car", amount: 100)
if case let Trade.Buy(stock, amount) = trade {
print("buy \(amount) of \(stock)")
}
enum Trade0 {
case Buy(String, Int)
case Sell(String, Int)
}
let trade0 = Trade0.Buy("Car0", 100)
if case let Trade0.Buy(stock, amount) = trade0 {
print("buy \(amount) of \(stock)")
}
9.枚举中的函数
enum Wearable {
enum Weight: Int {
case Light = 2
}
enum Armor: Int {
case Light = 2
}
case Helmet(weight: Weight, armor: Armor)
func attributes() -> (weight: Int, armor: Int) {
switch self {
case .Helmet(let w, let a):
return (weight: w.rawValue * 2, armor: a.rawValue * 4)
}
}
}
let test = Wearable.Helmet(weight: .Light, armor: .Light).attributes()
print(test)
enum Device {
case iPad, iPhone, AppleTV, AppleWatch
func introduced() -> String {
switch self {
case .AppleTV: return "\(self) was introduced 2006"
case .iPhone: return "\(self) was introduced 2007"
case .iPad: return "\(self) was introduced 2010"
case .AppleWatch: return "\(self) was introduced 2014"
}
}
}
print (Device.iPhone.introduced())
10.枚举中的属性
enum Device1 {
case iPad, iPhone
var year: Int {
switch self {
case .iPad:
return 2010
case .iPhone:
return 2007
}
}
}
let iPhone = Device1.iPhone
print(iPhone.year)
ParameterEncodingFailureReason
通过ParameterEncodingFailureReason
我们能够很清楚的看出来这是一个参数编码的错误原因。大家注意reason
这个词,在命名中,有或者没有这个词,表达的意境完全不同,因此,Alamofire牛逼就体现在这些细节之中。
public enum AFError: Error {
/// The underlying reason the parameter encoding error occurred.
///
/// - missingURL: The URL request did not have a URL to encode.
/// - jsonEncodingFailed: JSON serialization failed with an underlying system error during the
/// encoding process.
/// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during
/// encoding process.
public enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
}
ParameterEncodingFailureReason
本身是一个enum,同时,它又被包含在AFError
之中,这说明枚举之中可以有另一个枚举。那么像这种情况我们怎么使用呢?看下边的代码:
let parameterErrorReason = AFError.ParameterEncodingFailureReason.missingURL
枚举的访问是一级一级进行的。我们再看这行代码:case jsonEncodingFailed(error: Error)
。jsonEncodingFailed(error: Error)
并不是函数,就是枚举的一个普通的子选项。(error: Error)
是它的一个关联值,相对于任何一个子选项,我们都可以关联任何值,它的意义就在于,把这些值与子选项进行绑定,方便在需要的时候调用。我们会在下边讲解如何获取关联值。
参数编码有一下几种方式:
- 把参数编码到URL中
- 把参数编码到httpBody中
Alamofire中是如何进行参数编码的,这方面的内容会在后续的ParameterEncoding.swift
这一篇文章中给出详细的解释。那么编码失败的原因可能为:
missingURL
给定的urlRequest.url为nil的情况抛出错误jsonEncodingFailed(error: Error)
当选择把参数编码成JSON格式的情况下,参数JSON化抛出的错误propertyListEncodingFailed(error: Error)
这个同上
综上所述,ParameterEncodingFailureReason
封装了参数编码的错误,可能出现的错误类型为Error,说明这些所谓一般是调用系统Api产生的错误。
MultipartEncodingFailureReason
public enum MultipartEncodingFailureReason {
case bodyPartURLInvalid(url: URL)
case bodyPartFilenameInvalid(in: URL)
case bodyPartFileNotReachable(at: URL)
case bodyPartFileNotReachableWithError(atURL: URL, error: Error)
case bodyPartFileIsDirectory(at: URL)
case bodyPartFileSizeNotAvailable(at: URL)
case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
case bodyPartInputStreamCreationFailed(for: URL)
case outputStreamCreationFailed(for: URL)
case outputStreamFileAlreadyExists(at: URL)
case outputStreamURLInvalid(url: URL)
case outputStreamWriteFailed(error: Error)
case inputStreamReadFailed(error: Error)
}
多部分编码错误一般发生在上传或下载请求中对数据的处理过程中,这里边最重要的是对上传数据的处理过程,会在后续的MultipartFormData.swift
这一篇文章中给出详细的解释,我们就简单的分析下MultipartEncodingFailureReason
子选项错误出现的原因:
bodyPartURLInvalid(url: URL)
上传数据时,可以通过fileURL的方式,读取本地文件数据,如果fileURL不可用,就会抛出这个错误bodyPartFilenameInvalid(in: URL)
如果使用fileURL的lastPathComponent
或者pathExtension
获取filename为空抛出的错误bodyPartFileNotReachable(at: URL)
通过fileURL不能访问数据,也就是不可达的bodyPartFileNotReachableWithError(atURL: URL, error: Error)
这个不同于bodyPartFileNotReachable(at: URL)
,当尝试检测fileURL是不是可达的情况下抛出的错误bodyPartFileIsDirectory(at: URL)
当fileURL是一个文件夹时抛出错误bodyPartFileSizeNotAvailable(at: URL)
当使用系统Api获取fileURL指定文件的size出现错误bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
查询fileURL指定文件size出现错误bodyPartInputStreamCreationFailed(for: URL)
通过fileURL创建inputStream出现错误outputStreamCreationFailed(for: URL)
当尝试把编码后的数据写入到硬盘时,创建outputStream出现错误outputStreamFileAlreadyExists(at: URL)
数据不能被写入,因为指定的fileURL已经存在outputStreamURLInvalid(url: URL)
fileURL不是一个file URLoutputStreamWriteFailed(error: Error)
数据流写入错误inputStreamReadFailed(error: Error)
数据流读入错误
综上所述,这些错误基本上都跟数据的操作相关,这个在后续会做出很详细的说明。
ResponseValidationFailureReason
public enum ResponseValidationFailureReason {
case dataFileNil
case dataFileReadFailed(at: URL)
case missingContentType(acceptableContentTypes: [String])
case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
case unacceptableStatusCode(code: Int)
}
Alamofire不管请求是否成功,都会返回response。它提供了验证ContentType和StatusCode的功能,关于验证,再后续的文章中会有详细的解答,我们先看看这些原因:
dataFileNil
保存数据的URL不存在,这种情况一般出现在下载任务中,指的是下载代理中的fileURL缺失dataFileReadFailed(at: URL)
保存数据的URL无法读取数据,同上missingContentType(acceptableContentTypes: [String])
服务器返回的response不包含ContentType且提供的acceptableContentTypes不包含通配符(通配符表示可以接受任何类型)unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
ContentTypes不匹配unacceptableStatusCode(code: Int)
StatusCode不匹配
ResponseSerializationFailureReason
public enum ResponseSerializationFailureReason {
case inputDataNil
case inputDataNilOrZeroLength
case inputFileNil
case inputFileReadFailed(at: URL)
case stringSerializationFailed(encoding: String.Encoding)
case jsonSerializationFailed(error: Error)
case propertyListSerializationFailed(error: Error)
}
我们在Alamofire源码解读系列(一)之概述和使用中已经提到,Alamofire支持把服务器的response序列成几种数据格式。
- response 直接返回HTTPResponse,未序列化
- responseData 序列化为Data
- responseJSON 序列化为Json
- responseString 序列化为字符串
- responsePropertyList 序列化为Any
那么在序列化的过程中,很可能会发生下边的错误:
inputDataNil
服务器返回的response没有数据inputDataNilOrZeroLength
服务器返回的response没有数据或者数据的长度是0inputFileNil
指向数据的URL不存在inputFileReadFailed(at: URL)
指向数据的URL无法读取数据stringSerializationFailed(encoding: String.Encoding)
当使用指定的String.Encoding序列化数据为字符串时,抛出的错误jsonSerializationFailed(error: Error)
JSON序列化错误propertyListSerializationFailed(error: Error)
plist序列化错误
AFError
上边内容中介绍的ParameterEncodingFailureReason
MultipartEncodingFailureReason
ResponseValidationFailureReason
和 ResponseSerializationFailureReason
,他们是定义在AFError
中独立的枚举,他们之间是包含和被包含的关系,理解这一点很重要,因为有了这种包含的管理,在使用中就需要通过AFError.ParameterEncodingFailureReason
这种方式进行操作。
那么最重要的问题就是,如何把上边4个独立的枚举进行串联呢?Alamofire巧妙的地方就在这里,有4个独立的枚举,分别代表4大错误。也就是说这个网络框架肯定有这4大错误模块,我们只需要给AFError设计4个子选项,每个子选项关联上上边4个独立枚举的值就ok了。
这个设计真的很巧妙,试想,如果把所有的错误都放到AFError中,就显得非常冗余。那么下边的代码就呼之欲出了,大家好好体会体会在swift下这么设计的妙用:
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
AFError的扩展
也许在开发中,我们完成了上边的代码就认为够用了,但对于一个开源框架而言,远远是不够的。我们一点点进行剖析:
现在给定一条数据:
func findErrorType(error: AFError) {
}
我只需要知道这个error是不是参数编码错误,应该怎么办?因此为AFError提供5个布尔类型的属性,专门用来获取当前的错误是不是某个指定的类型。这个功能的实现比较简单,代码如下:
extension AFError {
/// Returns whether the AFError is an invalid URL error.
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
/// Returns whether the AFError is a parameter encoding error. When `true`, the `underlyingError` property will
/// contain the associated value.
public var isParameterEncodingError: Bool {
if case .parameterEncodingFailed = self { return true }
return false
}
/// Returns whether the AFError is a multipart encoding error. When `true`, the `url` and `underlyingError` properties
/// will contain the associated values.
public var isMultipartEncodingError: Bool {
if case .multipartEncodingFailed = self { return true }
return false
}
/// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
/// Returns whether the `AFError` is a response serialization error. When `true`, the `failedStringEncoding` and
/// `underlyingError` properties will contain the associated values.
public var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
总而言之,这些都是给AFError这个枚举扩展的属性,还包含下边这些属性:
-
urlConvertible: URLConvertible?
获取某个属性,这个属性实现了URLConvertible协议,在AFError中只有case invalidURL(url: URLConvertible)这个选项符合要求/// The `URLConvertible` associated with the error. public var urlConvertible: URLConvertible? { switch self { case .invalidURL(let url): return url default: return nil } }
-
url: URL?
获取AFError中的URL,当然这个URL只跟MultipartEncodingFailureReason这个子选项有关/// The `URL` associated with the error. public var url: URL? { switch self { case .multipartEncodingFailed(let reason): return reason.url default: return nil } }
-
underlyingError: Error?
AFError中封装的所有的可能出现的错误中,并不是每种可能都会返回Error这个错误信息,因此这个属性是可选的/// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`, /// `.multipartEncodingFailed` or `.responseSerializationFailed` error. public var underlyingError: Error? { switch self { case .parameterEncodingFailed(let reason): return reason.underlyingError case .multipartEncodingFailed(let reason): return reason.underlyingError case .responseSerializationFailed(let reason): return reason.underlyingError default: return nil } }
-
acceptableContentTypes: [String]?
可接受的ContentType/// The response `Content-Type` of a `.responseValidationFailed` error. public var responseContentType: String? { switch self { case .responseValidationFailed(let reason): return reason.responseContentType default: return nil } }
-
responseCode: Int?
响应码/// The response code of a `.responseValidationFailed` error. public var responseCode: Int? { switch self { case .responseValidationFailed(let reason): return reason.responseCode default: return nil } }
-
failedStringEncoding: String.Encoding?
错误的字符串编码/// The `String.Encoding` associated with a failed `.stringResponse()` call. public var failedStringEncoding: String.Encoding? { switch self { case .responseSerializationFailed(let reason): return reason.failedStringEncoding default: return nil } }
这里是一个小的分割线,在上边属性的获取中,也是用到了下边代码中的扩展功能:
extension AFError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
return error
default:
return nil
}
}
}
extension AFError.MultipartEncodingFailureReason {
var url: URL? {
switch self {
case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url),
.bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url),
.bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url),
.outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url),
.bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _):
return url
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error),
.outputStreamWriteFailed(let error), .inputStreamReadFailed(let error):
return error
default:
return nil
}
}
}
extension AFError.ResponseValidationFailureReason {
var acceptableContentTypes: [String]? {
switch self {
case .missingContentType(let types), .unacceptableContentType(let types, _):
return types
default:
return nil
}
}
var responseContentType: String? {
switch self {
case .unacceptableContentType(_, let responseType):
return responseType
default:
return nil
}
}
var responseCode: Int? {
switch self {
case .unacceptableStatusCode(let code):
return code
default:
return nil
}
}
}
extension AFError.ResponseSerializationFailureReason {
var failedStringEncoding: String.Encoding? {
switch self {
case .stringSerializationFailed(let encoding):
return encoding
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error):
return error
default:
return nil
}
}
}
错误描述
在开发中,如果程序遇到错误,我们往往会给用户展示更加直观的信息,这就要求我们把错误信息转换成易于理解的内容。因此我们只要实现LocalizedError协议就好了。这里边的内容很简单,在这里就直接把代码写上了,不做分析:
extension AFError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidURL(let url):
return "URL is not valid: \(url)"
case .parameterEncodingFailed(let reason):
return reason.localizedDescription
case .multipartEncodingFailed(let reason):
return reason.localizedDescription
case .responseValidationFailed(let reason):
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
}
}
}
extension AFError.ParameterEncodingFailureReason {
var localizedDescription: String {
switch self {
case .missingURL:
return "URL request to encode was missing a URL"
case .jsonEncodingFailed(let error):
return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
case .propertyListEncodingFailed(let error):
return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
}
}
}
extension AFError.MultipartEncodingFailureReason {
var localizedDescription: String {
switch self {
case .bodyPartURLInvalid(let url):
return "The URL provided is not a file URL: \(url)"
case .bodyPartFilenameInvalid(let url):
return "The URL provided does not have a valid filename: \(url)"
case .bodyPartFileNotReachable(let url):
return "The URL provided is not reachable: \(url)"
case .bodyPartFileNotReachableWithError(let url, let error):
return (
"The system returned an error while checking the provided URL for " +
"reachability.\nURL: \(url)\nError: \(error)"
)
case .bodyPartFileIsDirectory(let url):
return "The URL provided is a directory: \(url)"
case .bodyPartFileSizeNotAvailable(let url):
return "Could not fetch the file size from the provided URL: \(url)"
case .bodyPartFileSizeQueryFailedWithError(let url, let error):
return (
"The system returned an error while attempting to fetch the file size from the " +
"provided URL.\nURL: \(url)\nError: \(error)"
)
case .bodyPartInputStreamCreationFailed(let url):
return "Failed to create an InputStream for the provided URL: \(url)"
case .outputStreamCreationFailed(let url):
return "Failed to create an OutputStream for URL: \(url)"
case .outputStreamFileAlreadyExists(let url):
return "A file already exists at the provided URL: \(url)"
case .outputStreamURLInvalid(let url):
return "The provided OutputStream URL is invalid: \(url)"
case .outputStreamWriteFailed(let error):
return "OutputStream write failed with error: \(error)"
case .inputStreamReadFailed(let error):
return "InputStream read failed with error: \(error)"
}
}
}
extension AFError.ResponseSerializationFailureReason {
var localizedDescription: String {
switch self {
case .inputDataNil:
return "Response could not be serialized, input data was nil."
case .inputDataNilOrZeroLength:
return "Response could not be serialized, input data was nil or zero length."
case .inputFileNil:
return "Response could not be serialized, input file was nil."
case .inputFileReadFailed(let url):
return "Response could not be serialized, input file could not be read: \(url)."
case .stringSerializationFailed(let encoding):
return "String could not be serialized with encoding: \(encoding)."
case .jsonSerializationFailed(let error):
return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
case .propertyListSerializationFailed(let error):
return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)"
}
}
}
extension AFError.ResponseValidationFailureReason {
var localizedDescription: String {
switch self {
case .dataFileNil:
return "Response could not be validated, data file was nil."
case .dataFileReadFailed(let url):
return "Response could not be validated, data file could not be read: \(url)."
case .missingContentType(let types):
return (
"Response Content-Type was missing and acceptable content types " +
"(\(types.joined(separator: ","))) do not match \"*/*\"."
)
case .unacceptableContentType(let acceptableTypes, let responseType):
return (
"Response Content-Type \"\(responseType)\" does not match any acceptable types: " +
"\(acceptableTypes.joined(separator: ","))."
)
case .unacceptableStatusCode(let code):
return "Response status code was unacceptable: \(code)."
}
}
}
作者:老马的春天
链接:https://www.jianshu.com/p/99e6ba32f244
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于Asp.net Core 系列之 --4. 事务、日志及错误处理和简述事务日志文件在sql server2005中的作用的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于.net core入门-发布及部署_异常(处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2")处理、.net core系列之《对AOP思想的理解及使用AspectCore实现自定义日志拦截》、4.1ASP.NET Core请求过程「深入浅出ASP.NET Core系列」、Alamofire源码解读系列之错误处理(AFError)的相关知识,请在本站寻找。
本文标签: