Search code examples
asp.net-coreasp.net-core-mvcurl-routingasp.net-core-3.0nameof

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


Let's say I have a ASP.NET Core 3.0 MVC application, which features a simple controller containing two actions and using attribute based routing:

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

So calling either action should just yield a page showing URLs for both actions.

Opening /home/a1 and /home/a2 correctly calls the respective actions, but the output is kind of unexpected:

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

This indicates that Url.Action() returned an empty string for the second action, while it worked perfectly fine for the first action.

After debugging this for quite a while, I found a blog post tracking down this very problem to a breaking change in ASP.NET Core 3.0, where the Async suffix is somehow ignored by Url.Action().

The author fixed this problem by hard-coding strings as action names ("Action1" und "Action2" in my case). He also uploaded some example code reproducing this behavior.

However, I would really prefer to keep the nameof, to avoid later problems with renaming/refactoring.

Is there a clean way to use nameof or other type-safe constructs to supply a method with Async suffix to the Url.Action function?


Solution

  • The described behavior is caused by a breaking change introduced with ASP.NET Core 3.0.

    You can go back to the old behaviour by disabling SuppressAsyncSuffixInActionNames:

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

    Disable this switch in your AddControllers call while configuring the application services:

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

    You can find more information about this change in the official announcement and in the docs.