关于ASP.NET WEB API(OWIN WEBAPI)的几个编码最佳实践总结
近期工作比较忙,确实没太多精力与时间写博文,博文写得少,但并不代表没有研究与总结,也不会停止我继续分享的节奏,最多有可能发博文间隔时间稍长一点。废话不多说,直接上干货,虽不是主流的ASP.NET CORE但ASP.NET WEB API仍然有很多地方在用【而且核心思路与.NET CORE其实都是一样的】,而且如下也适用于OWIN WEBAPI方式。
- 全局异常捕获处理几种方式
具体可参见: ASP.NET Web API 中的错误处理
自定义异常过滤器(ExceptionFilterAttribute) :当控制器方法引发不是带有 httpresponseexception异常的任何未经处理的异常时,将执行异常筛选器,仅处理与特定操作或控制器相关的子集未处理异常的最简单解决方案
/// <summary>
/// 统一全局异常过滤处理
/// author:zuowenjun
/// </summary>
public class GlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var actionContext = actionExecutedContext.ActionContext;
string actionInfo = $"{actionContext.ActionDescriptor.ControllerDescriptor.ControllerName}.{actionContext.ActionDescriptor.ActionName}";
logger.Error(context.Exception, $"url:{actionContext.Request.RequestUri}({actionInfo}),request body:{actionContext.Request.Content.ReadAsStringAsync().Result},error:{context.Exception.Message}."); //为便于服务之间追踪分析,尝试把入参的TraceId继续传递给响应出参
string traceId = null;
if (actionContext.ActionArguments?.Count > 0)
{
var traceObj = actionContext.ActionArguments?.Values.Where(a => a is ITraceable).FirstOrDefault() as ITraceable;
traceId = traceObj?.TraceId;
} var apiResponse = new ApiResponse() { Code = 400, Msg = $"{actionInfo} Error:{context.Exception.Message}", TraceId = traceId }; actionExecutedContext.Response = actionExecutedContext.ActionContext.Request.CreateResponse(HttpStatusCode.OK, apiResponse, "application/json"); }
}
自定义异常处理器(ExceptionHandler):自定义 Web API 捕获的未处理异常的所有可能响应的解决方案
/// <summary>
/// 全局异常处理器
/// author:zuowenjun
/// </summary>
public class GlobalExceptionHandler : ExceptionHandler
{
private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); public override void Handle(ExceptionHandlerContext context)
{
var actionContext = context.ExceptionContext.ActionContext;
string actionInfo = $"{actionContext.ActionDescriptor.ControllerDescriptor.ControllerName}.{actionContext.ActionDescriptor.ActionName}";
logger.Error(context.Exception, $"url:{actionContext.Request.RequestUri}({actionInfo}),request body:{actionContext.Request.Content.ReadAsStringAsync().Result},error:{context.Exception.Message}."); //为便于服务之间追踪分析,尝试把入参的TraceId继续传递给响应出参
string traceId = null;
if (actionContext.ActionArguments?.Count > 0)
{
var traceObj = actionContext.ActionArguments?.Values.Where(a => a is ITraceable).FirstOrDefault() as ITraceable;
traceId = traceObj?.TraceId;
}
var apiResponse = new ApiResponse() { Code = 400, Msg = $"{actionInfo} Error:{context.Exception.Message}", TraceId = traceId };
context.Result = new ResponseMessageResult(context.ExceptionContext.Request.CreateResponse(HttpStatusCode.OK, apiResponse));
}
}
自定义委托处理程序中间件(DelegatingHandler):在请求响应管道中进行拦截判断,用于捕获并处理HttpError类异常。【如果想记录完整的异常信息,可以自定义多个继承自ExceptionLogger】
/// <summary>
/// 全局异常处理管道中间件(将除GlobalExceptionHandler未能捕获的HttpError异常进行额外封装统一返回)
/// author:zuowenjun
/// </summary>
public class GlobalExceptionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(respTask =>
{
HttpResponseMessage response = respTask.Result;
HttpError httpError;
if (response.TryGetContentValue(out httpError) && httpError != null)
{
var apiResponse = new ApiResponse<HttpError>()
{
Code = (int)response.StatusCode,
Msg = $"{response.RequestMessage.RequestUri} Error:{httpError.Message}",
Data = httpError,
TraceId = GetTraceId(response.RequestMessage.Content)
};
response.StatusCode = HttpStatusCode.OK;
response.Content = response.RequestMessage.CreateResponse(HttpStatusCode.OK, apiResponse).Content;
} return response; });
} private string GetTraceId(HttpContent httpContent)
{
string traceId = null;
if (httpContent is ObjectContent)
{
var contentValue = (httpContent as ObjectContent).Value;
if (contentValue != null && contentValue is ITraceable)
{
traceId = (contentValue as ITraceable).TraceId;
}
}
else
{
string contentStr = httpContent.ReadAsStringAsync().Result;
if (contentStr.IndexOf("traceId", StringComparison.OrdinalIgnoreCase) >= 0)
{
var traceObj = httpContent.ReadAsAsync<ITraceable>().Result;
traceId = traceObj.TraceId;
}
} return traceId;
}
}
Web Api Config入口方法(OWIN 是startup Configuration方法)注册上述相关的自定义的各类中间件:
【注:建议一般只需要同时注册GlobalExceptionDelegatingHandler、GlobalExceptionHandler即可】
config.MessageHandlers.Insert(0, new GlobalExceptionDelegatingHandler());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
config.Filters.Add(new GlobalExceptionFilterAttribute());
如下是自定义的一个可追踪接口,用于需要API入参透传TraceId(Model类实现该接口即代表可传TraceId),我这里是为了便于外部接口传TraceId方便,才放到报文中,其实也可以放在请求头中
public interface ITraceable
{
string TraceId { get; set; }
}
ACTION方法入参验证的几种方式
在Model类对应的属性上增加添加相关验证特性
一般是指System.ComponentModel.DataAnnotations命名空间下的相关验证特性,如: PhoneAttribute、EmailAddressAttribute 、EnumDataTypeAttribute 、FileExtensionsAttribute 、MaxLengthAttribute 、MinLengthAttribute 、RangeAttribute 、RegularExpressionAttribute 、RequiredAttribute 、StringLengthAttribute 、UrlAttribute 、CompareAttribute 、CustomValidationAttribute【这个支持指定自定义类名及验证方法,采取约定验证方法签名,第一个参数为具体验证的对象类型(不限定,依实际情况指定),第二个则为ValidationContext类型】
让Model类实现IValidatableObject接口并实现Validate方法,同时需注册自定义的ValidatableObjectAdapter对象,以便实现对IValidatableObject接口的支持,这种方式的好处是可以实现集中验证(当然也可以使用上述方法1中的CustomValidationAttribute,并标注到要验证的Model类上,且指定当前Model类的某个符合验证方法即可)
public class CustomModelValidator : ModelValidator
{
public CustomModelValidator(IEnumerable<ModelValidatorProvider> modelValidatorProviders) : base(modelValidatorProviders)
{ } public override IEnumerable<ModelValidationResult> Validate(ModelMetadata metadata, object container)
{
if (metadata.IsComplexType && metadata.Model == null)
{
return new List<ModelValidationResult> { new ModelValidationResult { MemberName = metadata.GetDisplayName(), Message = "请求参数对象不能为空。" } };
} if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))
{
var validationResult = (metadata.Model as IValidatableObject).Validate(new ValidationContext(metadata.Model));
if (validationResult != null)
{
var modelValidationResults = new List<ModelValidationResult>();
foreach (var result in validationResult)
{
modelValidationResults.Add(new ModelValidationResult
{
MemberName = string.Join(",", result.MemberNames),
Message = result.ErrorMessage
});
} return modelValidationResults;
}
return null;
} return GetModelValidator(ValidatorProviders).Validate(metadata, container);
}
}
除了定义上述的CustomModelValidator以便支持对IValidatableObject的验证外,还必需注册加入到验证上下文集合中才能生效,仍然是在WEB API的config入口方法(OWIN 是startup Configuration方法),类似如下:
RegisterDefaultValidatableObjectAdapter(config); private void RegisterDefaultValidatableObjectAdapter(HttpConfiguration config)
{
IEnumerable<ModelValidatorProvider> modelValidatorProviders = config.Services.GetModelValidatorProviders(); DataAnnotationsModelValidatorProvider provider = (DataAnnotationsModelValidatorProvider)
modelValidatorProviders.Single(x => x is DataAnnotationsModelValidatorProvider); provider.RegisterDefaultValidatableObjectAdapter(typeof(CustomModelValidator));
}
出入参JSON格式及日期处理
大小写及强制仅支持JSON交互方式(以便符合目前通行的JSON规范,若不加则是大写)
config.Formatters.XmlFormatter.SupportedMediaTypes.Clear(); config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
日期格式化(如果不设置,则默认序列化后为全球通用时,不便于阅读)
config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
config.Formatters.JsonFormatter.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
自定义类型转换(通过自定义实现JsonConverter抽象类,可灵活定义序列化与反序化的行为),例如:
//在入口方法加入自定义的转换器类
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new BoolenJsonConverter()); public class BoolenJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(bool).IsAssignableFrom(objectType);
} public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Boolean)
{
return Convert.ToBoolean(reader.Value);
} throw new JsonSerializationException($"{reader.Path} value: {reader.Value} can not convert to Boolean");
} public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
if (value is bool)
{
writer.WriteValue(value.ToString().ToLower());
return;
} throw new JsonSerializationException($"Expected Boolean object value: {value}");
}
}
后面计划将分享关于OAuth2.0原生实现及借助相关框架实现(JAVA与C#语言),敬请期待!
温馨提示:我的分享不局限于某一种编程语言,更多的是分享经验、技巧及原理,让更多的人受益,少走弯路!
关于ASP.NET WEB API(OWIN WEBAPI)的几个编码最佳实践总结的更多相关文章
- 使用 OWIN Self-Host ASP.NET Web API 2
Open Web Interface for .NET (OWIN)在Web服务器和Web应用程序之间建立一个抽象层.OWIN将网页应用程序从网页服务器分离出来,然后将应用程序托管于OWIN的程序而离 ...
- 使用 OWIN 作为 ASP.NET Web API 的宿主
使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...
- Use OWIN to Self-Host ASP.NET Web API 2
Open Web Interface for .NET (OWIN)在Web服务器和Web应用程序之间建立一个抽象层.OWIN将网页应用程序从网页服务器分离出来,然后将应用程序托管于OWIN的程序 ...
- [转] JSON Web Token in ASP.NET Web API 2 using Owin
本文转自:http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/ ...
- JSON Web Token in ASP.NET Web API 2 using Owin
In the previous post Decouple OWIN Authorization Server from Resource Server we saw how we can separ ...
- 在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)
在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Ap ...
- 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证
基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...
- ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API
在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...
- Exception Handling in ASP.NET Web API webapi异常处理
原文:http://www.asp.net/web-api/overview/error-handling/exception-handling This article describes erro ...
- (转)【ASP.NET Web API】Authentication with OWIN
概述 本文说明了如何使用 OWIN 来实现 ASP.NET Web API 的验证功能,以及在客户端与服务器的交互过程中,避免重复提交用户名和密码的机制. 客户端可以分为两类: JavaScript: ...
随机推荐
- 浅谈 Java 中的 AutoCloseable 接口
本文对 try-with-resources 语法进行了较为深入的剖析,验证了其为一种语法糖,同时给出了其实际的实现方式的反编译结果,相信你在看完本文后,关于 AutoCloseable 的使用你会有 ...
- redis管道技术pipeline一 ——api
import java.io.UnsupportedEncodingException; import java.util.Set; import org.springframework.beans. ...
- S3C2440移植linux3.4.2内核之修改分区以及制作根文件系统
上一节S3C2440移植linux3.4.2内核之内核框架介绍及简单修改我们简单配置了内核,这节来根据继续修改内核. 启动内核 内核启动的打印信息如下图所示 可以看到内核有8个分区,而我们的u ...
- PHPCMS V9安装出现DNS解析失败的解决方法-不支持采集和保存远程图片
目前因为phpcms官网停止解析后,很多人安装phpcms v9出现如下错误: 这是因为检测dns解析的域名是phpcms官网的域名,官网域名停止解析后肯定检测失败.解决方法如下: 打开/ ...
- Laravel - 配置 数据库
- 如何部署两个JMS网关,形成双机热备
大家使用JMS的过程中,可能会留意到,不管是微服务在注册时,还是RemoteClient构造时,所指向的网关都是一个NetAddress数组,之所以网关地址是多个,而不是一个,那是因为网关是一个双击热 ...
- GitLab的安装、配置、使用
前言 上周去参与"中国数字经济创新发展大会"了,然后又忙新项目的事情,博客又有一段时间没有更新,今天周一事情比较少,立刻开始写文,最近有挺多值得记录的东西~ 进入正文,最近我们搭了 ...
- [转帖]OceanBase 中租户管理
https://zhuanlan.zhihu.com/p/464504887 概述 租户的概念类似于传统数据库的数据库实例.租户也叫实例,拥有一定的资源能力(如CPU.内存和空间).租户下可以建立数据 ...
- [转帖]Linux内核参数 rp_filter
https://www.cnblogs.com/chenmh/p/6001977.html 简介 rp_filter (Reverse Path Filtering)参数定义了网卡对接收到的数据包进行 ...
- [转帖]LSM-Tree:从入门到放弃——入门:基本概念、操作和Trade-Off分析
https://zhuanlan.zhihu.com/p/428267241 LSM-Tree,全程为日志结构合并树,有趣的是,这个数据结构实际上重点在于日志结构合并,和 tree 本身的关系并不是特 ...