I am attempting to create a .net core 5 API. I have the following code in my controller.
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(await _repository.GetAllUsersAsync());
}
// GET api/<ReelTypes>/5
[Route("GetUserByIdAsync")]
[HttpGet("{id}", Name ="GetUserByIdAsync")]
public async Task<ActionResult<AppUser>> GetUserByIdAsync(Guid id)
{
var appUser = await _repository.GetUserByIdAsync(id);
if(appUser == null)
{
return NoContent();
}
return Ok(appUser);
}
[HttpPost]
[Route("CreateAsync")]
public async Task<IActionResult> CreateAsync([FromBody] AppUser model)
{
var checkExists = await _userManager.FindByNameAsync(model.UserName);
if (checkExists != null)
{
return StatusCode(StatusCodes.Status409Conflict,
new _ResponseBody { Title = "Duplicate Record",
Body = "Record already exists" });
}
AppUser user = new AppUser()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.UserName
};
var result = await _userManager.CreateAsync(user, model.PasswordHash);
if (!result.Succeeded)
return StatusCode(StatusCodes.Status500InternalServerError,
new _ResponseBody { Title = "Not Created",
Body = "Unable to create record" });
return CreatedAtAction("GetUserByIdAsync", new { id = user.Id }, user);
}
I am really trying to follow the complete specification and I know when I create an object with my API the response needs to include the URI to the new object and the new object. To do that I am trying to use the CreatedAtAction function. but no matter what I do I get 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|19_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.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.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)
I have tried using the GetName for the first parameter and just ended up hard coding it like it is now. So what I need to know is am I an idiot (Obvious answer is yes) or is there a problem with this function? Is the issue that my GetById has a Guid as a parameter? Is it that I am working with one of the Identity tables? Do I have to use versioning?
I have looked through every post on here and elsewhere regarding this issue so I did not just post this first thing. If it is obvious I am sorry I am just missing it.
EDIT: I have tried using the nameof function for the first parameter. I have also tried decorating the target action with [ActionName("GetUserByIdAsync")].
CodePudding user response:
replace
return CreatedAtAction("GetUserByIdAsync", new { id = user.Id }, user);
with
return GetUserByIdAsync(user.Id );
CodePudding user response:
The exception message tells you exactly what the problem is:
No route matches the supplied values.
If you read the docs for CreatedAtAction(string actionName, object value)
:
actionName: The name of the action to use for generating the URL.
Then, if you'd read the docs for HttpGetAttribute.Name
:
Gets the route name. The route name can be used to generate a link using a specific route, instead of relying on selection of a route based on the given set of route values.
So the problem is that you didn't specify Name
when decorating your controller method with HttpGet
. In other words, all you need to do is:
// GET api/<ReelTypes>/5
[HttpGet("{id}", Name = "GetUserByIdAsync")]
public async Task<ActionResult<AppUser>> GetUserByIdAsync(Guid id)
CodePudding user response:
I figured out that I had to specify the data type of my parameter for the Get Action, add the ActionName decoration back, and change it back to using nameof. So my code now looks like:
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(await _repository.GetAllUsersAsync());
}
// GET api/<ReelTypes>/5
[HttpGet("{id:Guid}", Name ="GetUserByIdAsync")]
[ActionName("GetUserByIdAsync")]
public async Task<ActionResult<AppUser>> GetUserByIdAsync([FromRoute] Guid id)
{
var appUser = await _repository.GetUserByIdAsync(id);
if(appUser == null)
{
return NoContent();
}
return Ok(appUser);
}
[HttpPost]
[Route("CreateAsync")]
public async Task<IActionResult> CreateAsync([FromBody] AppUser model)
{
var checkExists = await _userManager.FindByNameAsync(model.UserName);
if (checkExists != null)
{
return StatusCode(StatusCodes.Status409Conflict,
new _ResponseBody { Title = "Duplicate Record",
Body = "Record already exists" });
}
AppUser user = new AppUser()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.UserName
};
var result = await _userManager.CreateAsync(user, model.PasswordHash);
if (!result.Succeeded)
return StatusCode(StatusCodes.Status500InternalServerError,
new _ResponseBody { Title = "Not Created",
Body = "Unable to create record" });
return CreatedAtAction(nameof(GetUserByIdAsync), new { id = user.Id }, user);
}
So one of those three changes seems to have fixed my issue. I suspect it is the fact that I have now specified the parameter type (Guid) but I also wonder if the order of the decorations matters. The first time I added the [ActionName] decoration I had it above the [HttpGet...] decoration.
Thank you to everyone who tried to help me. I hope this helps someone else solve this problem.