ASP.NET Core MVC — IActionResult执行(0) IActionResult与执行的工作原理

IActionResult接口表达了Action执行完毕之后的结果,该接口只规定了一个ExecuteResultAsync(ctx)方法:

1
2
3
4
public interface IActionResult
{
Task ExecuteResultAsync(ActionContext context);
}

当某个Action返回IActionResult实例之后,此实例的该方法会被MVC调用,从而把相应的IActionResult对象写到响应中。例如,我们可以自定义一个IActionResult实现,用于生成带BOM头的CSV文件流,代码可能长这样(引自我的SO回答):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Utf8ForExcelCsvResult : IActionResult
{
public string Content{get;set;}
public string ContentType{get;set;}
public string FileName {get;set;}
public Task ExecuteResultAsync(ActionContext context)
{
var Response =context.HttpContext.Response;
Response.Headers["Content-Type"] = this.ContentType;
Response.Headers["Content-Disposition"]=$"attachment; filename={this.FileName}; filename*=UTF-8''{this.FileName}";
using(var sw = new StreamWriter(Response.Body,System.Text.Encoding.UTF8)){
sw.Write(Content);
}
return Task.CompletedTask ;
}
}

对于一个简单的情形这种写法是没有太大问题的,但是对于更复杂的IActionResult,这种方式在代码解耦方面并不好。因为IActionResult更多是在描述结果,而这里参杂了如何把结果写入响应流。比如说我们要根据请求的Accept字段来返回不同的响应,就需要加入更多逻辑。这种逻辑可以纳入IActionResult的执行。

为了解耦”执行IActionResult“这一过程,MVC又引入了IActionResultExecutor<in TResult>接口:

1
2
3
4
public interface IActionResultExecutor<in TResult> where TResult : IActionResult
{
Task ExecuteAsync(ActionContext context, TResult result);
}

那么,对于某种IActionResult只要拿到对应的IActionResultExecutor<TResult>,然后转发调用IActionResultExecutor::ExecuteAsync(ctx,result)即可。比如,内置的ObjectResult::ExecuteResultAsync(ctx)的实现即是如此:

1
2
3
4
5
6
7
8
9
10
public class ObjectResult : ActionResult, IStatusCodeActionResult
{
...

public override Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<ObjectResult>>();
return executor.ExecuteAsync(context, this);
}
}

当然,这需要把这些IActionResultExecutor<>都提前注册为服务。不过这部分工作并不需要我们操心,相关服务会在AddMvcCore()时被注册到DI容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal static void AddMvcCoreServices(IServiceCollection services)
{
...
services.TryAddSingleton<IActionResultExecutor<ObjectResult>, ObjectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<PhysicalFileResult>, PhysicalFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<VirtualFileResult>, VirtualFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileStreamResult>, FileStreamResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileContentResult>, FileContentResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectResult>, RedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<LocalRedirectResult>, LocalRedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToActionResult>, RedirectToActionResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToRouteResult>, RedirectToRouteResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<ContentResult>, ContentResultExecutor>();
...
}