【问题标题】:Using nameof() with Url.Action() and async methods in ASP.NET Core 3.x MVC在 ASP.NET Core 3.x MVC 中使用 nameof() 和 Url.Action() 和异步方法
【发布时间】:2020-01-31 23:43:31
【问题描述】:

假设我有一个 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,以避免以后出现重命名/重构问题。

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

【问题讨论】:

  • 您可以查看T4MVC(或R4MVC for Core)来生成强类型帮助器(参数)代码。
  • 这与您的问题无关,但请避免在您的代码中使用await Task.CompletedTask; 这样的星座。请改用return Task.FromResult()
  • @CodeCaster 感谢您的指点,这看起来很有希望!这也可能解决控制器名称属性的问题...
  • @MarcusWichelmann 好吧,这更多是尽可能简化示例的结果,但仍然感谢您的提示:)

标签: asp.net-core asp.net-core-mvc url-routing asp.net-core-3.0 nameof


【解决方案1】:

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

您可以通过禁用SuppressAsyncSuffixInActionNames 来恢复原来的行为:

获取或设置一个值,该值确定 MVC 是否会删除应用于控制器操作名称的后缀“Async”。

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

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

您可以在official announcementdocs 中找到有关此更改的更多信息。

【讨论】:

  • 不,这只会使未归属的异步后缀操作方法无法通过非后缀路由访问。虽然这看起来很相关,但这与手头的问题无关。
  • @CodeCaster 据我了解这个问题,这正是他想要的。从他的问题中可以看出,他的所有操作方法都带有所需的路由名称,因此 Async 后缀不应该引起任何问题。
  • 那么问题是你不理解它。他们希望在他们的操作方法中使用强类型字符串,是吗?但是如果你使用nameof(Action2Async),你会在你的网址中得到"Action2Async"。这与任何路由都不匹配,因为它们的操作方法是通过“action2”路由的(通过属性[HttpGet("action2")],但也可以通过命名约定)。请求“action2Async”不会以该操作方法结束。虽然您的更改将要求,当他们省略 [HttpGet("action2")] 属性时,他们将使用 URL“action2Async”!
  • 所以你的建议归结为:禁用“从路由中删除异步后缀”约定,删除路由属性,并接受你的路由将是Action2Async,这会适得其反- 并导致丑陋的 URL。
  • 谢谢!这导致生成所需的 URL。它仍然感觉有些“混乱”,使框架回退到旧的行为。我将简要介绍一下其他选项,但这绝对是一个可行的解决方案。
【解决方案2】:

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

来自链接的blog post

在 ASP.NET Core 3 中,如果您有以 Async 为后缀的 Action 方法,但路由路径不包含 Async,则在通过类似 Url.Action() 解析 URL 时引用它们而不带 Async 后缀。这似乎是 ASP.NET Core 2.2 的一个重大变化,没有正式记录。

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());
}

【讨论】:

  • 虽然可以解决所描述的问题,但它并不能真正回答我的问题。 Async 后缀约定有充分的理由——在我的情况下,放弃它并不是一个真正的选择。我正在寻找一种类型安全的方式来使用 Url.Action,即使 方法具有 Async 后缀。
  • @janw 你找到解决方案了吗?
猜你喜欢
  • 2015-02-11
  • 2020-03-20
  • 2014-07-07
  • 1970-01-01
  • 1970-01-01
  • 2018-05-14
  • 2011-03-21
  • 2016-08-29
  • 1970-01-01
相关资源
最近更新 更多