近期工作比较忙,确实没太多精力与时间写博文,博文写得少,但并不代表没有研究与总结,也不会停止我继续分享的节奏,最多有可能发博文间隔时间稍长一点。废话不多说,直接上干货,虽不是主流的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. celery与django的结合以及定时任务配置

    一.conda创建新的开发环境 C:\Users\yc>conda create --name celery_django python=3.8 C:\Users\yc>conda inf ...

  2. 移动端h5中rem适配

    1 (function (win, lib) { 2 var doc = win.document; 3 var docEl = doc.documentElement; 4 var metaEl = ...

  3. 用canvas实现验证码的绘制

    login.vue主文件 1 <template> 2 <div class="login-wrapper"> 3 <img src=".. ...

  4. 黑池舞蹈节banner

  5. spring启动流程 (3) BeanDefinition详解

    BeanDefinition在Spring初始化阶段保存Bean的元数据信息,包括Class名称.Scope.构造方法参数.属性值等信息,本文将介绍一下BeanDefinition接口.重要的实现类, ...

  6. file-loader返回object Module 路径的问题

    新版本的  file-loader生成使用ES模块语法的JS模块,所以它加载的文件,不再返回路径,而是返回一个对象,通过对象.default属性,可以取得路径 所以第一种方法,可以修改路径 <i ...

  7. Shell-流程控制-if-then-elif

  8. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.25)

    来给大家伙送福利了! 一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘, ...

  9. [转帖]sql_exporter的使用

    https://www.jianshu.com/p/df4b7a7cfc0d 一.背景 有些时候,我们想看每天系统的登录人数.或者系统中订单的数据,比如:成功的订单.异常的订单等等.这些数据都在我们的 ...

  10. [转帖]如何用 30s 给面试官讲清楚什么是 Session-Cookie 认证

    https://www.jianshu.com/p/e1121d4d7084 引言 由于 HTTP 协议是无状态的,完成操作关闭浏览器后,客户端和服务端的连接就断开了,所以我们必须要有一种机制来保证客 ...