记录和异常处理,ASP.NET WEB API中的版本控制

目录

介绍

1.异常处理

发送Get请求

处理异常前的响应

注册异常处理程序

抛出异常的快照

异常处理后的响应

异常记录

API_错误

APIError类

SqlErrorLogging类

UnhandledExceptionLogger类

在WebApiConfig类中注册 UnhandledExceptionLogger

存储异常

2. 记录请求和响应

创建自定义处理程序

请求方法

主机地址

用户代理

请求正文

绝对URI

标头

为ApiLog类赋值

传入请求记录

传出响应记录

注册RequestResponseHandler

访问Values API控制器

请求和响应Web API日志记录

3. 版本控制

AddApiVersioning方法的代码片段

WebApiConfig的完整代码片段

添加Values2Controller API控制器

将ApiVersion属性和路由属性添加到Values API控制器

将ApiVersion属性和路由属性添加到Values2 API控制器

请求Values API

结论


介绍

在本文中,我们将学习如何记录API的每个请求和响应,以帮助维护日志。接下来,我们将处理所有API异常,这样如果发生错误,我们可以存储错误并尽快修复它,最后一部分是API的版本控制。

  1. 异常处理
  2. 日志记录
  3. 版本控制

当您开发生产API时,所有这些部分都是关键。

Freepikwww.flaticon.com制作的图标由CC 3.0 BY授权:

1.异常处理

首先,我们创建一个简单的Web API应用程序WebDemoAPI

创建一个简单的Web API解决方案后,您将获得一个默认的Home控制器和Values API控制器。让我们首先运行应用程序并调用get请求。

注意:您可以使用任何Rest Client发送此演示的请求,我将使用POSTMAN Rest客户端。

URL: http://localhost:50664/api/values

发送Get请求

API发送请求后,我们得到了响应。

现在让我们改变Get方法,在这里,我要抛出一个异常。

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        throw new NotImplementedException("");
        //return new string[] { "value1", "value2" };
    }
   }

现在,如果我们向values API请求发送get请求,那么它将在响应中抛出错误。

处理异常前的响应

现在我们得到了错误,让我们看看如何全局处理这个错误。

使用ExceptionHandler类处理API异常:

为了处理异常,我们将创建一个GlobalExceptionHandler类,其继承自的ExceptionHandlerabstract类。在这里面,我们将实现Handle方法。在此之前,我们将创建CustomHandler文件夹。在这个文件夹中,我们将添加GlobalExceptionHandler类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;

namespace WebDemoAPI.CustomHandler
{
    public class GlobalExceptionHandler : ExceptionHandler
    {
        public override void Handle(ExceptionHandlerContext context)
        {
            var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("Internal Server Error Occurred"),
                ReasonPhrase = "Exception"
            };

            context.Result = new ErrorMessageResult(context.Request, result);
        }

        public class ErrorMessageResult : IHttpActionResult
        {
            private HttpRequestMessage _request;
            private readonly HttpResponseMessage _httpResponseMessage;

            public ErrorMessageResult
            (HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
            {
                _request = request;
                _httpResponseMessage = httpResponseMessage;
            }

            public Task<HttpResponseMessage> 
                   ExecuteAsync(CancellationToken cancellationToken)
            {
                return Task.FromResult(_httpResponseMessage);
            }
        }
    }
}

现在我们已经实现了ExceptionHandler类中的Handle方法。

在做之前,首先我们去创建HttpResponseMessage。为此,我们将添加一个继承自IHttpActionResult接口的类ErrorMessageResult。这个类将有一个带两个参数的参数化构造函数:

  1. HttpRequestMessage
  2. HttpResponseMessage

我们带参数的HttpResponseMessage将被ExecuteAsync用来创建HttpResponseMessage

然后,这个HttpResponseMessage我们将把它赋值给context.Result

处理完异常后,接下来我们需要注册这个处理程序。

注册异常处理程序

我们将在WebApiConfig类中注册GlobalExceptionHandler,以便可以全局处理任何Web API异常。

//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.MapHttpAttributeRoutes();
            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

现在让我们运行这个应用程序并检查我们现在是否处理异常。

抛出异常的快照

抛出异常后,现在我们向消费者显示正确的错误消息而不是错误堆栈跟踪。

异常处理后的响应

现在我们已经处理了异常,但我们还没有记录异常。

异常记录

在这一部分中,我们将把异常存储到数据库中,为此,让我们首先看看我们将存储它的表结构。

API_错误

在查看了表结构之后,我进一步编写了一个简单的过程来将此异常存储在表中。

现在我们已经完成了数据库部分,接下来,让我们添加类和方法以将异常写入数据库。

APIError

public class ApiError
{
    public string Message { get; set; }
    public string RequestMethod { get; set; }
    public string RequestUri { get; set; }
    public DateTime TimeUtc { get; set; }
}

注意:存储过程和表脚本可供下载。

SqlErrorLogging

在这一部分中,我们将在数据库中写入错误,在这个类中,我们有将ApiError类作为输入参数的InsertErrorLog方法。

public class SqlErrorLogging
{
    public void InsertErrorLog(ApiError apiError)
    {
        try
        {
            using (var sqlConnection = new SqlConnection
            (ConfigurationManager.ConnectionStrings
            ["APILoggingConnection"].ConnectionString))
            {
                sqlConnection.Open();
                var cmd =
                    new SqlCommand("API_ErrorLogging", connection: sqlConnection)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                cmd.Parameters.AddWithValue("@TimeUtc", apiError.TimeUtc);
                cmd.Parameters.AddWithValue("@RequestUri", apiError.RequestUri);
                cmd.Parameters.AddWithValue("@Message", apiError.Message);
                cmd.Parameters.AddWithValue("@RequestMethod", apiError.RequestMethod);

               cmd.ExecuteNonQuery();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
}

添加完类和方法后,接下来我们要添加UnhandledExceptionLogger类,其继承自的ExceptionLoggerabstract类。

UnhandledExceptionLogger

我们将添加一个类UnhandledExceptionLogger,它将继承自ExceptionLogger一个abstract类,因为我们将覆盖Log方法,在此方法中,我们将获得从该异常发生的异常,我们将提取SourceStackTraceTargetSite等信息,并将其分配给ApiError类以存储在数据库中。

using System;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.Models;

namespace WebDemoAPI.CustomHandler
{
    public class UnhandledExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            var ex = context.Exception;

           string strLogText = "";
            strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;
            strLogText += Environment.NewLine + "StackTrace ---\n{0}" + ex.StackTrace;
            strLogText += Environment.NewLine + "TargetSite ---\n{0}" + ex.TargetSite;

            if (ex.InnerException != null)
            {
                strLogText += Environment.NewLine + 
                "Inner Exception is {0}" + ex.InnerException;//error prone
            }
            if (ex.HelpLink != null)
            {
                strLogText += Environment.NewLine + "HelpLink ---\n{0}" + 
                              ex.HelpLink;//error prone
            }

            var requestedURi = (string)context.Request.RequestUri.AbsoluteUri;
            var requestMethod = context.Request.Method.ToString();
            var timeUtc = DateTime.Now;

            SqlErrorLogging sqlErrorLogging = new SqlErrorLogging();
            ApiError apiError = new ApiError()
            {
                Message = strLogText,
                RequestUri = requestedURi,
                RequestMethod = requestMethod,
                TimeUtc = DateTime.Now
            };
            sqlErrorLogging.InsertErrorLog(apiError);
        }
    }
}

创建UnhandledExceptionLogger类并将错误写入数据库后,接下来我们将在WebApiConfig类中全局注册该类。

//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

WebApiConfig类中注册 UnhandledExceptionLogger

using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.MapHttpAttributeRoutes();
            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            //Registering UnhandledExceptionLogger
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

注册UnhandledExceptionLogger类之后,现在让我们运行应用程序,看看它是否存储了数据库中发生的异常。

收到错误后,我们对其进行了处理并向用户显示了正确的错误消息,并将错误记录在数据库中。

处理异常和Logging异常后的响应:

存储异常

在处理和记录异常之后,接下来我们将记录每个请求和对Web API的响应。

2. 记录请求和响应

在这一部分中,我们将记录WEB API的每个请求和响应。

为此,我们将继承一个abstractDelegatingHandler并覆盖SendAsync方法。

如果您看到下表,您将清楚地了解我们从请求和响应存储到数据库中的所有数据。

让我们首先创建一个API_Log表,我们将在其中存储此请求以作为响应。

创建表后,我们创建了一个用于将Log插入API_Log表的简单存储过程。此存储过程可供下载。

接下来,我们将添加ApiLog类来将数据传递给存储过程。

namespace WebDemoAPI.Models
{
    public class ApiLog
    {
        public string Host { get; set; }
        public string Headers { get; set; }
        public string StatusCode { get; set; }
        public string RequestBody { get; set; }
        public string RequestedMethod { get; set; }
        public string UserHostAddress { get; set; }
        public string Useragent { get; set; }
        public string AbsoluteUri { get; set; }
        public string RequestType { get; set; }
    }
}

添加ApiLog类后,接下来我们要Add一个ApiLogging类。在该类中,我们将添加InsertLog方法,它将采用ApiLog类作为参数,ApiLog类数据将映射到SQL参数,将数据插入数据库。插入数据库。

public class ApiLogging
{
    public void InsertLog(ApiLog apiLog)
    {
        try
        {
            using (var sqlConnection = new SqlConnection
            (ConfigurationManager.ConnectionStrings
            ["APILoggingConnection"].ConnectionString))
            {
                sqlConnection.Open();
                var cmd =
                    new SqlCommand("API_Logging", connection: sqlConnection)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                cmd.Parameters.AddWithValue("@Host", apiLog.Host);
                cmd.Parameters.AddWithValue("@Headers", apiLog.Headers);
                cmd.Parameters.AddWithValue("@StatusCode", apiLog.StatusCode);
                cmd.Parameters.AddWithValue("@RequestBody", apiLog.RequestBody);
                cmd.Parameters.AddWithValue("@RequestedMethod", apiLog.RequestedMethod);
                cmd.Parameters.AddWithValue("@UserHostAddress", apiLog.UserHostAddress);
                cmd.Parameters.AddWithValue("@Useragent", apiLog.Useragent);
                cmd.Parameters.AddWithValue("@AbsoluteUri", apiLog.AbsoluteUri);
                cmd.Parameters.AddWithValue("@RequestType", apiLog.RequestType);
                cmd.ExecuteNonQuery();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
}

完成添加ApiLogging类后,接下来我们将编写此过程的主要核心,即添加自定义处理程序。

创建自定义处理程序

我们将添加一个名为RequestResponseHandler的类,然后我们将从DelegatingHandler abstract类和override SendAsync方法继承。

public class RequestResponseHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage>
                       SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
    }
}

在实现SendAsync方法之前,我编写了一个简单的类MessageLogging,其中有两个方法,IncomingMessageAsyncOutgoingMessageAsync。我创建此方法仅用于分配请求类型并分别调用这两种方法。

public class MessageLogging
{
    public void IncomingMessageAsync(ApiLog apiLog)
    {
        apiLog.RequestType = "Request";
        var sqlErrorLogging = new ApiLogging();
        sqlErrorLogging.InsertLog(apiLog);
    }

    public void OutgoingMessageAsync(ApiLog apiLog)
    {
        apiLog.RequestType = "Response";
        var sqlErrorLogging = new ApiLogging();
        sqlErrorLogging.InsertLog(apiLog);
    }
}

现在添加MessageLogging类之后,接下来我们将实现DelegatingHandler abstract类中的SendAsync方法。

public class RequestResponseHandler: DelegatingHandler
{
    protected override async Task<HttpResponseMessage>
        SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var requestedMethod = request.Method;
        var userHostAddress = HttpContext.Current != null ?
            HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
        var useragent = request.Headers.UserAgent.ToString();
        var requestMessage = await request.Content.ReadAsByteArrayAsync();
        var uriAccessed = request.RequestUri.AbsoluteUri;

        var responseHeadersString = new StringBuilder();
        foreach (var header in request.Headers)
        {
            responseHeadersString.Append($"{header.Key}:
            {String.Join(", ", header.Value)}{Environment.NewLine}");
        }

        var messageLoggingHandler = new MessageLogging();

        var requestLog = new ApiLog()
        {
            Headers = responseHeadersString.ToString(),
            AbsoluteUri = uriAccessed,
            Host = userHostAddress,
            RequestBody = Encoding.UTF8.GetString(requestMessage),
            UserHostAddress = userHostAddress,
            Useragent = useragent,
            RequestedMethod = requestedMethod.ToString(),
            StatusCode = string.Empty
        };

        messageLoggingHandler.IncomingMessageAsync(requestLog);

        var response = await base.SendAsync(request, cancellationToken);

        byte[] responseMessage;
        if (response.IsSuccessStatusCode)
            responseMessage = await response.Content.ReadAsByteArrayAsync();
        else
            responseMessage = Encoding.UTF8.GetBytes(response.ReasonPhrase);

        var responseLog = new ApiLog()
        {
            Headers = responseHeadersString.ToString(),
            AbsoluteUri = uriAccessed,
            Host = userHostAddress,
            RequestBody = Encoding.UTF8.GetString(responseMessage),
            UserHostAddress = userHostAddress,
            Useragent = useragent,
            RequestedMethod = requestedMethod.ToString(),
            StatusCode = string.Empty
        };

        messageLoggingHandler.OutgoingMessageAsync(responseLog);
        return response;
    }
}

让我们了解我们在SendAsync方法中写了什么。

请求方法

var requestedMethod = request.Method;

我们正在存储request方法,无论它是POST PUT DELETE还是GET

主机地址

var userHostAddress = HttpContext.Current != null ? 
                      HttpContext.Current.Request.UserHostAddress : "0.0.0.0";

我们正在获取此请求的IP地址。

用户代理

var useragent = request.Headers.UserAgent.ToString();

UserAgent给你一个关于浏览器的原始字符串。

请求正文

var requestMessage = await request.Content.ReadAsByteArrayAsync();

绝对URI

var uriAccessed = request.RequestUri.AbsoluteUri;

标头

var responseHeadersString = new StringBuilder();
foreach (var header in request.Headers)
{
    responseHeadersString.Append($"{header.Key}: {String.Join(", ", header.Value)}
                                {Environment.NewLine}");
}

ApiLog类赋值

var messageLoggingHandler = new MessageLogging();

var requestLog = new ApiLog()
{
    Headers = responseHeadersString.ToString(),
    AbsoluteUri = uriAccessed,
    Host = userHostAddress,
    RequestBody = Encoding.UTF8.GetString(requestMessage),
    UserHostAddress = userHostAddress,
    Useragent = useragent,
    RequestedMethod = requestedMethod.ToString(),
    StatusCode = string.Empty
};

传入请求记录

messageLoggingHandler.IncomingMessageAsync(requestLog);

传出响应记录

messageLoggingHandler.OutgoingMessageAsync(responseLog);

注册RequestResponseHandler

using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            
            //Registering UnhandledExceptionLogger
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

            //Registering RequestResponseHandler
            config.MessageHandlers.Add(new RequestResponseHandler());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

现在我们知道了这个过程是如何工作的。让我们运行应用程序,看看它是否有效。

访问Values API控制器

请求和响应Web API日志记录

3. 版本控制

Freepikwww.flaticon.com制作的图标由CC 3.0 BY许可

它是Web API开发中最重要的部分,因为我们不断改进应用程序,我们不断对应用程序进行更改,如果我们对已经在生产中的API进行更改并且许多用户正在使用它将破坏工作应用程序,解决方案是对您的API进行版本控制,以便使用您的API的老用户不会对其产生任何影响。

让我们通过简单的步骤开始在ASP.NET Web API中实现版本控制。

首先,我们将向应用程序添加Microsoft.AspNet.WebApi.Versioning”NuGet 包。

接下来安装NuGet包后,我们将在WebApiConfig.cs文件中注册AddApiVersioning方法。

当您向应用程序添加API版本控制时,该ApiVersioningOptions类允许您配置、自定义和扩展默认行为。

引用自:https ://github.com/Microsoft/aspnet-api-versioning/wiki/API-Versioning-Options

AddApiVersioning方法的代码片段

config.AddApiVersioning(o =>
    {
        o.ReportApiVersions = true;
        o.AssumeDefaultVersionWhenUnspecified = true;
        o.DefaultApiVersion = new ApiVersion(2, 0);
        o.ApiVersionReader = new HeaderApiVersionReader("version");
        o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
    }
);

WebApiConfig的完整代码片段

在这一部分中,我们将注释默认路由并启用属性基础路由。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(2, 0);
                o.ApiVersionReader = new HeaderApiVersionReader("version");
                o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
            }
        );
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        //Registering GlobalExceptionHandler
        config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());

        //Registering UnhandledExceptionLogger
        config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

        //Registering RequestResponseHandler
        config.MessageHandlers.Add(new RequestResponseHandler());

        //config.Routes.MapHttpRoute(
        //    name: "DefaultApi",
        //    routeTemplate: "api/{controller}/{id}",
        //    defaults: new { id = RouteParameter.Optional }
        //);
    }
}

完成注册方法后,接下来我们将添加另一个名为Values2ControllerAPI控制器。

添加Values2Controller API控制器

如果你看到,我们已经添加了Values2名称API控制器,我们已经添加了一个版本的控制器的名称不是必须添加的,但名称必须是唯一的,容易理解。

public class Values2Controller : ApiController
{
    // GET: api/Values2
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET: api/Values2/5
    public string Get(int id)
    {
        return "value";
    }

    // POST: api/Values2
    public void Post([FromBody]string value){}

    // PUT: api/Values2/5
    public void Put(int id, [FromBody]string value) {}

    // DELETE: api/Values2/5
    public void Delete(int id) {}
}

添加Values2 API控制器后,接下来我们将向旧的API控制器和新的API控制器添加路由属性。

ApiVersion属性和路由属性添加到Values API控制器

[ApiVersion("1.0")]
[Route("api/values")]
public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        //throw new NotImplementedException("");
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    public string Get(int id) {  return "value";}

    // POST api/values
    public void Post([FromBody]string value){}

    // PUT api/values/5
    public void Put(int id, [FromBody]string value){}

    // DELETE api/values/5
    public void Delete(int id) {} 
}

ApiVersion属性和路由属性添加到Values2 API控制器

[ApiVersion("2.0")]
[Route("api/values")]
public class Values2Controller : ApiController
{
    // GET api/Values2
    public IEnumerable<string> Get()
    {
        //throw new NotImplementedException("");
        return new string[] { "version2", " version2" };
    }

    // GET api/Values2/5
    public string Get(int id){return "value";}

    // POST api/Values2
    public void Post([FromBody]string value) { }

    // PUT api/Values2/5
    public void Put(int id, [FromBody]string value) { }

    // DELETE api/Values2/5
    public void Delete(int id) { }
}

添加路由和版本属性后,接下来保存并运行应用程序。

现在要调用API,我们需要从标头中传递一个API版本,标头的名称是version

我们将把标题名称version和值作为1.0传递给调用values控制器。

请求Values API

在调用完1.0版本的值API之后,接下来我们将以相同的方式调用values2 2.0版本标头的API

我们将把标题名称version和值作为2.0传递给调用Values2控制器。

在使用2.0版标头访问值控制器(values2controller)后,我们得到了预期的有效响应。

结论

在本文中,我们学习了如何处理异常记录异常,还学习了如何记录每个传入和传出的Web API请求和响应,我们如何对Web API进行版本控制,我应该不破坏现有的工作API,这一切都是我们逐步详细了解的,以便我们可以直接与Live Projects集成。

https://www.codeproject.com/Articles/1250932/Logging-and-Exception-handling-Versioning-in-ASP-N

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值