在 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 () => /* some async work */ )
【讨论】:
只是指出从ASP.NET Core 2.1.0-preview2 开始修复了在发送响应之前调用了OnCompleted的bug,所以如果你使用的是更高版本,第一个解决方案是可以的: GitHub issue【参考方案2】:以Jeans answer 和try - return - finally
模式上的question and answer 为基础,可以删除try
和finally
块(如果您真的不想捕获异常)。
这导致以下代码:
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中如何限制响应发送速率(不是调用频率)
如何使用 MVC 的内容协商在 ASP.NET Core MVC 中间件中返回响应?