近期工作比较忙,确实没太多精力与时间写博文,博文写得少,但并不代表没有研究与总结,也不会停止我继续分享的节奏,最多有可能发博文间隔时间稍长一点。废话不多说,直接上干货,虽不是主流的ASP.NET CORE但ASP.NET WEB API仍然有很多地方在用【而且核心思路与.NET CORE其实都是一样的】,而且如下也适用于OWIN WEBAPI方式。

  1. 全局异常捕获处理几种方式

具体可参见: ASP.NET Web API 中的错误处理

  1. 自定义异常过滤器(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"); }
    }
  2. 自定义异常处理器(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));
    }
    }
  3. 自定义委托处理程序中间件(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; }
}
  1. ACTION方法入参验证的几种方式

    1. 在Model类对应的属性上增加添加相关验证特性

      一般是指System.ComponentModel.DataAnnotations命名空间下的相关验证特性,如: PhoneAttribute、EmailAddressAttribute 、EnumDataTypeAttribute 、FileExtensionsAttribute 、MaxLengthAttribute 、MinLengthAttribute 、RangeAttribute 、RegularExpressionAttribute 、RequiredAttribute 、StringLengthAttribute 、UrlAttribute 、CompareAttribute 、CustomValidationAttribute【这个支持指定自定义类名及验证方法,采取约定验证方法签名,第一个参数为具体验证的对象类型(不限定,依实际情况指定),第二个则为ValidationContext类型】

    2. 让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));
      }
  2. 出入参JSON格式及日期处理

    1. 大小写及强制仅支持JSON交互方式(以便符合目前通行的JSON规范,若不加则是大写)

      config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
      
      config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    2. 日期格式化(如果不设置,则默认序列化后为全球通用时,不便于阅读)

      config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
      config.Formatters.JsonFormatter.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
    3. 自定义类型转换(通过自定义实现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)的几个编码最佳实践总结的更多相关文章

  1. 使用 OWIN Self-Host ASP.NET Web API 2

    Open Web Interface for .NET (OWIN)在Web服务器和Web应用程序之间建立一个抽象层.OWIN将网页应用程序从网页服务器分离出来,然后将应用程序托管于OWIN的程序而离 ...

  2. 使用 OWIN 作为 ASP.NET Web API 的宿主

    使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...

  3. Use OWIN to Self-Host ASP.NET Web API 2

      Open Web Interface for .NET (OWIN)在Web服务器和Web应用程序之间建立一个抽象层.OWIN将网页应用程序从网页服务器分离出来,然后将应用程序托管于OWIN的程序 ...

  4. [转] 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/ ...

  5. 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 ...

  6. 在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)

    在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Ap ...

  7. 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证

    基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...

  8. ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API

    在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...

  9. Exception Handling in ASP.NET Web API webapi异常处理

    原文:http://www.asp.net/web-api/overview/error-handling/exception-handling This article describes erro ...

  10. (转)【ASP.NET Web API】Authentication with OWIN

    概述 本文说明了如何使用 OWIN 来实现 ASP.NET Web API 的验证功能,以及在客户端与服务器的交互过程中,避免重复提交用户名和密码的机制. 客户端可以分为两类: JavaScript: ...

随机推荐

  1. vue用qrcodejs2生成二维码,解决多个二维码追加的问题

    vue使用qrcodejs2生成二维码 1.安装qrcodejs2 npm install qrcodejs2 2.代码 //导入组件 import QRCode from 'qrcodejs2' / ...

  2. CMake学习,我们怎么从零开始狂写大型项目

    CMake 说明 cmake的定义是什么 ?-----高级编译配置工具 当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库(dll,so等等)这时候神器就出现了-----C ...

  3. python常见面试题讲解(十一)字符串反转-五种解法

    题目描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串.(字符串长度不超过1000) 输入描述: 输入N个字符 输出描述: 输出该字符串反转后的字符串 示例1 输入 abcd 输出 d ...

  4. [转帖]k8s ipv4/ipv6双栈实践

    https://www.iceyao.com.cn/post/2020-11-28-k8s_dual_stack/ Posted by 爱折腾的工程师 on Saturday, November 28 ...

  5. [转帖](1.2)sql server for linux 开启代理服务(SQL AGENT),使用T-SQL新建作业

    https://www.cnblogs.com/gered/p/12518090.html 回到顶部 [1]启用SQL Server代理 sudo /opt/mssql/bin/mssql-conf ...

  6. [转帖]HAProxy 在 TiDB 中的最佳实践

    https://docs.pingcap.com/zh/tidb/stable/haproxy-best-practices 本文介绍 HAProxy 在 TiDB 中的最佳配置和使用方法.HAPro ...

  7. [转帖]大模型训练,英伟达Turing、Ampere和Hopper算力分析

    https://www.eet-china.com/mp/a219195.html 大 GPU 优势在于通过并行计算实现大量重复性计算.GPGPU即通用GPU,能够帮助 CPU 进行非图形相关程序的运 ...

  8. [转帖]SPEC2006

    安装步骤 # Ubuntu16.04 # 注意安装gFortran . ./install.sh . ./shrc 一般情况下经过以上步骤即可安装完毕,进行使用,注意需要执行shrc设置完环境变量以后 ...

  9. 神通奥斯卡数据库是否兼容Oracle, 以及参数修改的办法

    1. 最近公司要适配神通数据库, 但是因为一些功能异常.参数可能存在风险. 为了减少问题, 想着简单描述一下这些的处理. 开发和客户给的默认参数建议 1. 不选择 兼容oracle模式 2. 字符集选 ...

  10. CentOS8 设置开机自动登录账户的方法

    CentOS8 设置开机自动登录账户的方法 修改/etc/gdm/custom.conf文件, 并且添加内容即可. vim /etc/gdm/custom.conf # 在配置节下添加如下内容. [d ...