Asp .Net Core WebApi:异常的第二次http调用

Posted

技术标签:

【中文标题】Asp .Net Core WebApi:异常的第二次http调用【英文标题】:Asp .Net Core WebApi : 2nd http call on exception 【发布时间】:2022-01-09 18:38:48 【问题描述】:

我正在编写一个 Webapi,当第一个 HTTP 端点失败时,我必须调用 HTTP 的第二个端点。但是,对于第二个 HTTP 调用,我的应用程序不应该阻塞主线程并且应该返回不成功的结果。我的 HTTP 客户端已经配备了 Polly 重试策略,重试后,我想静默调用第二个 URL 而不会阻塞主线程。 我的代码如下所示。

public class Request 

    public string Id  get; set; 


public class Result

    public string Id  get; set; 
    public bool IsSuccess  get; set;        
    public string Message  get; set; 


public class Service

   private readonly IHttpClientFactory _httpClientFactory;
   private readonly Ilogger _appLogger;
   private readonly string _url1="www.somedomain.com/api1";
   private readonly string _url2="www.somedomain.com/api2";

   Service(IHttpClientFactory factory,Ilogger logger)
   
     _httpClientFactory = httpClientFactory;
     _appLogger = logger;
   

   public async Task<Result> Send(Request request)
    
        try
        
         using var client = _httpClientFactory.CreateClient();
         using HttpRequestMessage httpRequest =new HttpRequestMessage(HttpMethod.Post, _url1);
         var req = JsonConvert.SerializeObject<Request>(body);
         var stringContent = new StringContent(req, Encoding.UTF8, "json");
         var result = await client.SendAsync(httpRequest);
         var resultContent = await result.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<Result>(resultContent));
         
         catch (Exception ex)
         
            if (ex is OperationCanceledException || ex is TaskCanceledException)
            
              //Second call needed but don't want to hold the result 
              // Task.Run(async ()=> await Log(request))    //Approach 1 
              // Task.Factory.StartNew(async () => await Log(request)) //Approach 2
              // HostingEnvironment.QueueBackgroundWorkItem(ct => Log(request).ConfigureAwait(false)); //Approach 3
              // QueuedHostedService defined here [Microsoft][1]
              // var jobId = BackgroundJob.Enqueue(() => Log(request).ConfigureAwait(false)); //Approach 4 by hangfire [Hangfire][2]
              // Custome singleton suggested here // [CustomImplementation][1]
            

            //No 2nd call needed
            await _appLogger.LogAsync(new ExceptionLog(ex));
            return ResultIsSuccess=false;;
         
   
   public async Task Log(Request request)
   
        try
        
         using var client = _httpClientFactory.CreateClient();
         using var httpRequest =new HttpRequestMessage(HttpMethod.Post, _url2);
         var req = JsonConvert.SerializeObject<Request>(body);
         var stringContent = new StringContent(req, Encoding.UTF8, "json");
         var result = await client.SendAsync(httpRequest);
         var resultContent = await result.Content.ReadAsStringAsync();
         var result = JsonConvert.DeserializeObject<Result>(resultContent));
          await _appLogger.LogAsync("2nd call successful.",result );

         
         
         catch (Exception ex)
         
          
            await _appLogger.LogAsync("2nd call failed",ex);
            
         
   

我一直在寻找许多解决方案,但现在对它们感到困惑。 [1]:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio [2]:https://www.hangfire.io/ [3]:https://anduin.aiursoft.com/post/2020/10/14/fire-and-forget-in-aspnet-core-with-dependency-alive

【问题讨论】:

【参考方案1】:

实际上,您正在寻找的是一个fire and forget 机制,它应该在当前范围内独立工作。

我们都知道,一旦HttpRequest 完成响应,包括我们使用的所有服务在内的当前范围将被处置(默认范围级别行为)。所以,让我们想象一下第二次调用稍后回来,在原始 http 请求完成后,一切都已经处理并清除了。糟糕的错误...

看看this,并在你的catch块中处理它,事情应该会很好。

另一种方法是使用Background service,这个想法是创建一个包含必要信息的队列,让后台扫描和处理它们,假设我们想要任何间隔率。

【讨论】:

是的,你是对的,但是,我可能需要一些范围信息,例如记录器来记录结果。 This 对我来说也是一个理想的候选人,但我不确定后台服务(这是否会过度设计简单的问题)。 对于记录东西,我们应该在调用第二个请求之前记录一些东西,并将一些独特的东西传递给fire and forget 机制,如CorrelationId 或我们需要搜索日志的任何东西。没有办法使用遗忘机制来保持整个日志上下文,因为它与当前范围紧密耦合。 对于background service,它根本没有过度设计。对于整个background service,我们完全可以用不到 100 行代码来制作我们自己的代码,并在我们需要处理它们的地方再多几行代码。实际上,100 行以下的代码似乎不是工程问题……对吧? - 我绝对同意的一件事,Hangfire 对于这个来说应该是矫枉过正。【参考方案2】:

我将尝试提供您可能的方法的细分:

- 触发并忘记呼叫: 它确实有效,是最容易实现的,但它不提供代码执行的任何保证或可见性。 fire and forget 方法中的任何异常都将被吞噬。如果您的应用程序在执行 fire and forget 方法期间停止,您将无法获得任何重试尝试。如果调用不是强制性的,请使用此方法。

- Hangfire:它做你想做的事,它有几个选项,比如定义重试策略和设置你可以有多少并行执行。它支持多种不同的存储,包括内存、SQL 和 NoSQL。就个人而言,我是 HangFire 的忠实粉丝,并认为它是最强大的方法,同时易于实施。但是,它确实添加了一个依赖项,您可能需要与团队的其他成员讨论。

- 托管服务: 我不会做你想要的开箱即用,因为托管服务更像是应用程序中的应用程序。您可以使用它来构建自定义的持久作业执行,但您将在很大程度上重新发明 Hangfire 功能的一个子集。这是迄今为止最复杂的方法,如果可能的话我会避免它。

【讨论】:

是的,关于HostedService,我觉得做起来有点复杂。关于Hangfire,我检查并发现有一个可用的内存存储,但提到不要将其用于生产here,我不能使用sql server或任何其他后端。 @MaheshJadhav 使用内存存储意味着如果您的应用程序关闭,您将失去排队的工作。在您采用的任何内存方法中都是如此。如果你不能使用存储,你打算如何坚持执行? 我希望hangfire 为.net web API 提供IHostedService 实现,以便在关闭应用程序时可以有效地处理清理。就我而言,我只会在遇到异常时触发作业,并且在任何应用程序中,这种情况经常发生,但并非每次都发生。 @MaheshJadhav 您可以告诉您的应用程序在关闭时等待 Hangfire。但这仅在您的应用程序干净退出时才有效。如果您的应用程序意外退出,则无法保证您的清理代码运行,即使使用 IHostedService。你对第二个电话没有运行还好吗? 由于限制和合规性,我认为不允许使用任何依赖服务,如 SQL 或其他存储。同样在这一点上,推荐这个并获得批准是一项乏味的任务。我同意 Hangfire 会让它变得非常简单和灵活。有时你必须忍受剩下的选择。

以上是关于Asp .Net Core WebApi:异常的第二次http调用的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net Core 异常处理中间件

在 ASP.NET Core 2 中处理异常的推荐方法? [关闭]

在 cosmos db 和 asp.net core 中处理请求时发生未处理的异常

ASP.Net Core 2 错误处理:如何在 Http Response 中返回格式化的异常详细信息?

选择 webApi 模板时如何将 ASP.Net 身份添加到 Asp.Net Core?

Asp.Net Core WebAPI CORS 不工作