我在.NET Core中编写ASP.NET MVC站点。我试图封装一些常见的异常处理。在Base类中,我有这种方法。
public abstract class BaseController<TController> : Controller where TController : Controller
{
protected IActionResult ExceptionHandledOperation(Func<IActionResult> operation, Func<IActionResult> handleException)
{
try
{
return operation.Invoke();
}
catch (Exception exception)
{
Logger.LogError($"Operation {Request.Path} Exception", exception);
return handleException.Invoke();
}
}
}
从继承自这个基类的控制器,我像这样使用这个方法:
[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
[HttpGet]
public IActionResult Get()
{
return ExceptionHandledOperation(() => Ok(_someService.GetAsync().Result),
NotFound);
}
}
假设_someService.GetAsync()方法是这样的:
public class SomeService
{
public async Task<PreconfigurationData> GetAsync()
{
// http request removed for brevity
if (!response.IsSuccessStatusCode)
throw new Exception("SomeService Exception");
}
}
这很好用,会在基类方法中捕获我的异常并返回NotFound结果。
但是,我想避免从SomeService.GetAsync方法调用.Result。我读到它的任何地方都说不要这样做,因为它可能会陷入僵局。
所以我将我的基本控制器修改为:
public abstract class BaseController<TController> : Controller where TController : Controller
{
protected async Task<IActionResult> ExceptionHandledOperationAsync(Func<IActionResult> operation, Func<IActionResult> handleException)
{
try
{
return await Task.Run(() => operation.Invoke());
}
catch (Exception exception)
{
Logger.LogError($"Operation {Request.Path} Exception", exception);
return await Task.Run(() => handleException.Invoke());
}
}
}
MyController是这样的:
[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
[HttpGet]
public async Task<IActionResult> Get()
{
return await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()),
NotFound);
}
}
但是,从我的SomeService.GetAsync方法抛出的异常永远不会被捕获,并且在处理异常时我从未得到我打算发送的NotFound响应。
我读到的任何地方都说你只需要等待试验中的任务,然后任何异常都会被捕获,但我的确没有。
解决
我终于能够解决这个问题了。感谢Tseng的帮助。
答案 0 :(得分:1)
您的代码中存在逻辑错误。您调用return await Task.Run(() => handleException.Invoke());
但在函数委托内部运行异步代码而不等待它(此处:await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()), NotFound)
。
所以在你的try / catch块中,该方法被执行并立即返回,之前异步调用完成。
就像我的赞扬所指出的那样,你的职能委托也需要等待,阅读:返回Task
。
public abstract class BaseController<TController> : Controller where TController : Controller
{
protected async Task<IActionResult> ExceptionHandledOperationAsync<T>(
Func<Task<T>> operation,
Func<object, IActionResult> successHandler,
Func<IActionResult> exceptionHandler
)
{
try
{
return successHandler.Invoke(await operation.Invoke());
}
catch (Exception exception)
{
//Logger.LogError($"Operation {Request.Path} Exception", exception);
return exceptionHandler.Invoke();
}
}
}
[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
[HttpGet]
public async Task<IActionResult> Get()
{
return await ExceptionHandledOperationAsync(() => _someService.GetAsync(), Ok, NotFound);
}
}
您需要将successHandler
(将结果传递给Ok
的位置)移动到方法中,如上所示。但这真是丑陋的代码。 Imho SomeService
应该自己处理服务失败,并在找不到任何值时返回null
。
在异常时返回NotFound()
似乎很奇怪,因为它表明该记录不存在但由于网络连接或数据序列化而可能失败。
答案 1 :(得分:0)
我同意上面关于在ASP.NET应用程序中不使用Task.Run的评论。话虽这么说,你可以尝试在你的Invoke方法中试一试。
示例:
try
{
return await Task.Run(() =>
{
try
{
operation.Invoke();
}
catch (Exception ex)
{
// log exception
}
});
}
catch (Exception ex)
{
// captured SynchronizationContext
}
答案 2 :(得分:0)
在终于理解曾告诉我的内容之后,我能够完成这项工作。这就是我改变它的方式:
public abstract class BaseController<TController> : Controller where TController : Controller
{
protected async Task<IActionResult> ExceptionHandledOperationAsync(Func<Task<IActionResult>> operation, Func<IActionResult> handleException)
{
try
{
return await operation.Invoke();
}
catch (Exception exception)
{
Logger.LogError($"Operation {Request.Path} Exception", exception);
return handleException.Invoke();
}
}
}
MyController是这样的:
[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
[HttpGet]
public async Task<IActionResult> Get()
{
return await ExceptionHandledOperationAsync(async () => Ok(await _someService.GetAsync()),
NotFound);
}
}