Peculiar problem with method naming in C#

Viewed 148

I have come across a peculiar issue in ASP.NET Core. I have a method which returns an entity after creation.

        [HttpGet("{id}")]
        public async Task<IActionResult> GetItemAsync(int id)
        {
            var entity = await _unitOfWork.UserRepository.FindByIdAsync(id);
            if (entity == null)
                return NotFound();
            return Ok(entity);
        }

        [HttpPost]
        public async Task<IActionResult> CreateUser(UserNewDto userNewDto)
        {
            if (ModelState.IsValid)
            {
                var entity = new User()
                {
                    UserName = userNewDto.UserName,
                    FirstName = userNewDto.FirstName,
                    LastName = userNewDto.LastName,
                    Email = userNewDto.Email,
                    PhoneMobile = userNewDto.PhoneMobile,
                    Password = userNewDto.Password,
                };

                await _unitOfWork.UserRepository.AddAsync(entity);
                await _unitOfWork.Complete();
                return CreatedAtAction("GetItemAsync", new { entity.Id }, entity);
            }

            return new JsonResult("Something went wrong") { StatusCode = 500 };
        }        

The CreateUser method should redirect to GetItemAsync after creating the user. Instead, I get an error stating the following

System.InvalidOperationException: No route matches the supplied values.
   at Microsoft.AspNetCore.Mvc.CreatedAtActionResult.OnFormatting(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncCore(ActionContext context, ObjectResult result, Type objectType, Object value)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
   at Microsoft.AspNetCore.Mvc.ObjectResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultAsync(IActionResult result)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

If I change the method name from GetItemAsync to GetItem, the redirection works as intended. What am I doing wrong?

1 Answers

This is an issue in .net-core 3.0. Please see Async suffix removal from controller action names:

In ASP.NET Core 3.0, ASP.NET Core MVC removes the Async suffix from controller action names. Both routing and link generation are impacted by this new default.

This change doesn't affect names specified using the [ActionName] attribute. The default behavior can be disabled with the following code in Startup.ConfigureServices:

services.AddMvc(options =>
    options.SuppressAsyncSuffixInActionNames = false);

So you could either:

  1. Decorate your action with [ActionName]:
    [ActionName("GetItemAsync")]
    [HttpGet("{id}")]
    public async Task<IActionResult> GetItemAsync(int id)
    
  2. Or setting SuppressAsyncSuffixInActionNames = false when configuring services:
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(options =>
            options.SuppressAsyncSuffixInActionNames = false); 
    
Related