在 ASP.NET Core 3.x MVC 中将 nameof() 与 Url.Action() 和异步方法一起使用

Using nameof() with Url.Action() and async methods in ASP.NET Core 3.x MVC

假设我有一个 ASP.NET Core 3.0 MVC 应用程序,它具有一个包含两个操作并使用基于属性的路由的简单控制器:

[Route("home")]
public class HomeController : Controller
{
    public static string ControllerName { get; } = "Home";

    public HomeController()
    {
    }

    string GenerateUrls()
    {
        string url1 = Url.Action(nameof(Action1), ControllerName);
        string url2 = Url.Action(nameof(Action2Async), ControllerName);
        return $"Action1: '{url1}'\nAction2: '{url2}'";
    }

    [HttpGet("a1")]
    public IActionResult Action1()
    {
        return Ok(GenerateUrls());
    }

    [HttpGet("a2")]
    public async Task<IActionResult> Action2Async()
    {
        await Task.CompletedTask;

        return Ok(GenerateUrls());
    }
}

因此调用任一操作应该只会生成一个显示两个操作的 URL 的页面。

打开 /home/a1/home/a2 正确调用了相应的操作,但输出有点出乎意料:

Action1: '/home/a1'
Action2: ''

这表明 Url.Action() 为第二个操作返回了一个空字符串,而它在第一个操作中工作得很好。

调试了一段时间后,我发现了一个 blog post 追踪这个问题到 ASP.NET Core 3.0 中的一个突破性变化,其中 Async 后缀被莫名其妙地忽略了通过 Url.Action().

作者通过将字符串硬编码为动作名称(在我的例子中为 "Action1""Action2")解决了这个问题。他还上传了一些重现此行为的 example code

不过,我真的更愿意保留 nameof,以避免以后 renaming/refactoring 出现问题。

是否有一种干净的方法来使用 nameof 或其他类型安全的构造来为 Url.Action 函数提供带有 Async 后缀的方法?

如果将方法名称 Action2Async 更改为 Action2,问题将得到解决。

来自链接 blog post:

In ASP.NET Core 3, if you have Action methods suffixed with Async but a route path that does not include Async, refer to them without the Async suffix when resolving a URL through like Url.Action(). This seems to be a breaking change from ASP.NET Core 2.2 which is not officially documented.

public static string ControllerName { get; } = "Home";

string GenerateUrls()
{
    string url1 = Url.Action(nameof(Action1), ControllerName);
    string url2 = Url.Action(nameof(Action2), ControllerName);
    return $"Action1: '{url1}'\nAction2: '{url2}'";
}

[HttpGet("action1")]
public IActionResult Action1()
{
    return Ok(GenerateUrls());
}

[HttpGet("action2")]
public async Task<IActionResult> Action2()
{
    await Task.CompletedTask;

    return Ok(GenerateUrls());
}

所描述的行为是由 ASP.NET Core 3.0 引入的重大更改引起的。

您可以通过禁用 SuppressAsyncSuffixInActionNames 返回到旧行为:

Gets or sets a value that determines if MVC will remove the suffix "Async" applied to controller action names.

在配置应用程序服务时在 AddControllers 调用中禁用此开关:

services.AddControllers(options => {
    options.SuppressAsyncSuffixInActionNames = false;
});

您可以在 official announcement and in the docs.

中找到有关此更改的更多信息