在 ASP.NET Core 响应后做一些工作

Posted

技术标签:

【中文标题】在 ASP.NET Core 响应后做一些工作【英文标题】:Do some work after the response in ASP.NET Core 【发布时间】:2017-01-16 00:16:28 【问题描述】:

我有一个使用 EFCore 的 ASP.NET Core 网站。 我想做一些工作,比如登录数据库,但在将响应发送给用户以便更快地回答之后。

我可以在不同的线程中执行此操作,但由于 DbContext 的异步访问,我不确定它是否安全。有什么推荐的方法吗?

public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)

    try 
    
      var newModel = new ResponseViewModel(model);
      // Some work 
      return View("RequestView",newModel)
    
    finally
    
        // Some analysis on the request
        // I would like to defer this part
        await Log(model);
    

其中一个原因是我想调用一个网络服务(地理编码),它不需要回答,但可以很好地处理日志(我需要坐标的城市/国家)。

【问题讨论】:

1.你在做什么,你的日志记录比你减慢整个事情的请求慢得多?听起来像微优化,可能根本不需要线程分配,上下文切换)。 3. 如果你真的坚持并有可靠的数据支持这是一个瓶颈,请将消息(内存或分布式)排队并在后台处理) 它并不是真正的“重”,但需要对数据库进行一些读/写(例如检查请求是否已经完成),我只想在这样做之前能够回答因为它没有给用户任何东西,用户更愿意得到他的回应。 【参考方案1】:

我看到这个问题从未得到解答,但实际上有一个解决方案。 简单的解决方案:

public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)

    try 
    
      var newModel = new ResponseViewModel(model);
      // Some work 
      return View("RequestView",newModel)
    
    finally
    
        Response.OnCompleted(async () =>
        
            // Do some work here
            await Log(model);
        );
    

安全的解决方案,因为OnCompleted曾经在响应发送之前被调用,所以延迟响应:

public static void OnCompleted2(this HttpResponse resp, Func<Task> callback)

    resp.OnCompleted(() =>
    
        Task.Run(() =>  try  callback.Invoke();  catch  );
        return Task.CompletedTask;
    );

并致电Response.OnCompleted2(async () =&gt; /* some async work */ )

【讨论】:

只是指出从ASP.NET Core 2.1.0-preview2 开始修复了在发送响应之前调用了OnCompleted的bug,所以如果你使用的是更高版本,第一个解决方案是可以的: GitHub issue【参考方案2】:

以Jeans answer 和try - return - finally 模式上的question and answer 为基础,可以删除tryfinally 块(如果您真的不想捕获异常)。

这导致以下代码:

public async Task<IActionResult> Request([FromForm] RequestViewModel model, string returnUrl = null)

    var newModel = new ResponseViewModel(model);

    // Some work 

    Response.OnCompleted(async () =>
    
        // Do some work here
        await Log(model);
    );

    return View("RequestView", newModel);

【讨论】:

【参考方案3】:

没有开箱即用的方式来做你想做的事。

但是,这是一种可能的方法:

    拥有一个队列和一个工作线程(线程或进程) 就在将请求发送回客户端之前,在该队列中添加一条消息 工作人员将在未来的某个时间接收该消息,并对其进行处理。

由于工作在其他地方而不是在请求线程上运行,因此服务器可以完成请求线程并且工作人员可以完成剩下的工作。

【讨论】:

这是我正在考虑的一个选项,即使不是最优的,它也可能是最有效的。我的替代选择是记录“原始”请求并在 Windows 服务中对原始请求进行后处理,独立于 ASP...由于 EF 对线程不太友好,我不确定最佳解决方案(第二个将肯定不是问题,但需要更多的工作来设置和更新)【参考方案4】:

创建一个继承自ActionFilterAttribute 的新类,覆盖OnResultExecuted 方法以执行日志记录,然后将您的属性类应用于您要执行日志记录的控制器操作。

【讨论】:

这不会带来预期的目标,因为他想在请求**完成之后进行日志记录。请求仍在运行时仍会执行 ActionFilter【参考方案5】:

尝试使用 Hangfire。 Hangfire 是一种在 .NET 和 .NET Core 应用程序中执行后台处理的简单方法。无需 Windows 服务或单独的进程。 由持久存储支持。开放且免费用于商业用途。

你可以做类似的事情

var jobId = BackgroundJob.Enqueue(() => Log(model));

这是我在 ASP.NET Core 中使用 HangFire 的 blog post

【讨论】:

以上是关于在 ASP.NET Core 响应后做一些工作的主要内容,如果未能解决你的问题,请参考以下文章

asp.net core 中优雅的进行响应包装

NET Core 1.0

ASP.NET Core中如何限制响应发送速率(不是调用频率)

如何使用 MVC 的内容协商在 ASP.NET Core MVC 中间件中返回响应?

asp.net core 系列之Performance的 Response compression(响应压缩)

asp.net core序列化json配置,适用于mvc,webapi