asp.net core 'No route matches the supplied values' with controller-level route values - asp.net-core

The following controller throws a System.InvalidOperationException: No route matches the supplied values exception when posting a new item (by the CreatedAtRoute method):
namespace MyApp.Controllers
{
[ApiController]
[Route("api/offices/{officeId}/[controller]")]
public class ResourcesController : ControllerBase
{
/* ... */
[HttpGet]
public async Task<IActionResult> GetAsync(Guid officeId) =>
this.Ok(await this.client.GetResourcesAsync(officeId));
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}
[HttpPost]
public async Task<IActionResult> PostAsync(Guid officeId, Resource resource)
{
try
{
var result = await this.client.CreateResourceAsync(officeId, resource);
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
}
catch (Exception e)
{
this.logger.LogError(e, "Error while creating a resource");
return this.StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
Is this a bug or is there another way to use route values specified at controller level?
Tested on asp.net core 3 (preview 8).

The Id value of the resource was null, and therefore the anonymous object passed to the method was missing the id value, which is not optional on the route template and therefore the route was not found.
Using result.Id solved it, because the client was assigning the id (generated by the MongoDb instance behind).

I think its because of the parameters that you gave.
In this line you are giving 2 anonymus objects params to your root, but your root contains and asks for 1 parameter.
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
EDIT
I think the {id} parameter makes a conflict over your root parameters.
[Route("api/offices/{officeId}/[controller]")]
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}

Related

CreatedAtAction says the route doesn't exist in .NET 6

I'm having issues getting a CreatedAtAction to work properly. In my controller I have these methods:
[HttpGet("{uid:guid}")]
public async Task<IActionResult> GetAsync(Guid uid) => Ok(await _repository.GetAsync(uid));
[HttpPut]
public async Task<IActionResult> StoreAsync([FromQuery] Guid? uid, [FromBody] UpdateInputDTO data)
{
var output = await _repository.StoreAsync(uid, data);
if (uid is null)
return CreatedAtAction(nameof(GetAsync), new {
uid = output.Uid
});
else
return Ok(output);
}
and in my startup I did this:
services.AddControllers(x => x.SuppressAsyncSuffixInActionNames = false);
But when that code runs, I always get an exception saying the route doesn't exist.
Use "Get" instead of "nameof(GetAsync)".
For example, the action name for ProductsController.ListProductsAsync will be canonicalized as ListProducts.. Consequently, it will be routeable at /Products/ListProducts with views looked up at /Views/Products/ListProducts.cshtml.
SuppressAsyncSuffixInActionNames

How to change api return result in asp.net core 2.2?

My requirement is when the return type of the action is void or Task, I'd like to return my custom ApiResult instead. I tried the middleware mechanism, but the response I observed has null for both ContentLength and ContentType, while what I want is a json representation of an empty instance of ApiResult.
Where should I make this conversion then?
There are multiple filter in .net core, and you could try Result filters.
For void or Task, it will return EmptyResult in OnResultExecutionAsync.
Try to implement your own ResultFilter like
public class ResponseFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// do something before the action executes
if (context.Result is EmptyResult)
{
context.Result = new JsonResult(new ApiResult());
}
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
public class ApiResult
{
public int Code { get; set; }
public object Result { get; set; }
}
And register it in Startup.cs
services.AddScoped<ResponseFilter>();
services.AddMvc(c =>
{
c.Filters.Add(typeof(ResponseFilter));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
All you have to do is to check the return type and on the basis of the return you can perform whatever operations you want.
Here is the abstract demo:
You have a method:
public Action SomeActionMethod()
{
var obj = new object();
return (Action)obj;
}
Now in your code you can use the following code to get the name of the method:
MethodBase b = p.GetType().GetMethods().FirstOrDefault();
var methodName = ((b as MethodInfo).ReturnType.Name);
Where p in the above code is the class which contains the methods whose return type you want to know.
after having the methodname you can decide on variable methodName what to return.
Hope it helps.

CreatedAtRouteResult with swagger attribute

I have asp.net core web api. Where one of my controller returns CreatedAtRouteResult. How can I add swagger attribute for this method.
[HttpPost]
[SwaggerResponse(400, typeof(NotFoundResult))]
[SwaggerResponse(201, typeof(CreatedAtRouteResult))]
public async Task<IActionResult> Create([FromBody] SubscriptionDTO dto)
{
var issuedTo = (await _tokenService.Get()).IssuedTo;
SubscriptionDresult = await _subscriptionService.CreateAsync(dto, issuedTo.Id);
return result == null ? (IActionResult)NotFound(): CreatedAtRoute(new {id = result.Id}, result);
}
Can someone explain how to set the swagger response attribute from such type?
If you use ProduceResponseType attribute on your method, swagger will automatically pick it up and add it to the swagger.json document.
[HttpPost]
[ProducesResponseType(typeof(SubscriptionDTO), (int)HttpStatusCode.Created)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Create([FromBody] SubscriptionDTO dto)
{
var issuedTo = (await _tokenService.Get()).IssuedTo;
SubscriptionDTO result = await _subscriptionService.CreateAsync(dto, issuedTo.Id);
return result == null ? (IActionResult)NotFound(): CreatedAtRoute(new {id = result.Id}, result);
}
Note that you need to specify the actual DTO type returned for each response code in ProduceResponseType. Use void for no return body.

How does one determine the route of an Web API 2.2 Action implemented in a base class?

Assume for a moment that I have an abstract controller
public abstract class ResourceController<TResource> : ApiController where TResource: Resource,new()
{
[Route("{id}")]
[HttpGet]
public async Task<IHttpActionResult> FindById([FromUri] string id)
{
TResource resource = null;
// go fetch the resource from a database or something
return Ok(resource)
}
[Route("")]
[HttpPost]
public async Task<IHttpActionResult> Create(TResource resource)
{
TResource resource = null;
// go create the resource or something
return CreatedAtRoute("XXXXX", new { id = resource.Id }, resource);
}
// more methods
}
[RoutePrefix("foo")]
public class FooResourceController : ResourceController<Foo>
{
}
[RoutePrefix("baa")]
public class BaaResourceController : ResourceController<Baa>
{
}
public class Resource
{
public string Id { get; set; }
// some other properties all resources shared
}
At this stage all the actions work, except for creating a new resource. Without overriding the Create method in every subclass, how do I find the correct route of the FindById of the respective controllers from the ResourceController Create method?
For example, if I create a foo resource with id 123 then it would return foo/123. If I created a resource baa with id 456, then it woulds return baa/456.
I'm unable to name the route using attributes, since only one can exist for the application.
Had the same problem. I fixed it by using the Created method in combination with the calling url.
This will only work if yout post doesn't have a dedicated template
My get:
[HttpGet("{id:int}")]
public async Task<IActionResult> GetAsync(int id)
{
try
{
var codetabel = await _reader.GetAsync(id);
var model = Mapper.Map<TCodeTabelModel>(codetabel);
return OkResult(model);
}
catch ....
}
And post:
[HttpPost()]
public async Task<IActionResult> InsertAsync(TCodeTabelModel model)
{
if (!ModelState.IsValid)
return BadRequestResult(ModelState);
try
{
var entity = Mapper.Map<TCodeTabelEntity>(model);
var insertedEntity = await _writer.InsertAsync(entity);
return Created($"{Request.Path.Value}/{insertedEntity.Id}" , Mapper.Map<TCodeTabelModel>(insertedEntity));
}
catch ....
}

MVC 6 Web Api: Resolving the location header on a 201 (Created)

In Web Api 2.2, we could return the location header URL by returning from controller as follows:
return Created(new Uri(Url.Link("GetClient", new { id = clientId })), clientReponseModel);
Url.Link(..) would resolve the resource URL accordingly based on the controller name GetClient:
In ASP.NET 5 MVC 6's Web Api, Url doesn't exist within the framework but the CreatedResult constructor does have the location parameter:
return new CreatedResult("http://www.myapi.com/api/clients/" + clientId, journeyModel);
How can I resolve this URL this without having to manually supply it, like we did in Web API 2.2?
I didn't realise it, but the CreatedAtAction() method caters for this:
return CreatedAtAction("GetClient", new { id = clientId }, clientReponseModel);
Ensure that your controller derives from MVC's Controller.
In the new ASP.NET MVC Core there is a property Url, which returns an instance of IUrlHelper. You can use it to generate a local URL by using the following:
[HttpPost]
public async Task<IActionResult> Post([FromBody] Person person)
{
_DbContext.People.Add(person);
await _DbContext.SaveChangesAsync();
return Created(Url.RouteUrl(person.Id), person.Id);
}
There is an UrlHelper class which implements IUrlHelper interface.
It provides the requested functionality.
Source code
There is also CreatedAtRoute:
public async Task<IActionResult> PostImpl([FromBody] Entity entity)
{
...
return CreatedAtRoute(entity.Id, entity);
//or
return CreatedAtRoute(new { id = entity.Id }, entity);
}
My GET action has a route name
[HttpGet("{id:int}", Name = "GetOrganizationGroupByIdRoute")]
public async Task<IActionResult> Get(int id, CancellationToken cancellationToken = default(CancellationToken))
{
...
}
And my POST action uses that route name to return the URL
[HttpPost]
public async Task<HttpStatusCodeResult> Post([FromBody]OrganizationGroupInput input, CancellationToken cancellationToken = default(CancellationToken))
{
...
var url = Url.RouteUrl("GetOrganizationGroupByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent());
Context.Response.Headers["Location"] = url;
...
}
Resulting response using Fiddler
Hope that helps.
I use this simple approximation based on the Uri being served at the web server:
[HttpPost]
[Route("")]
public IHttpActionResult AddIntervencion(MyNS.MyType myObject) {
return Created<MyNS.MyType>(Request.RequestUri + "/" + myObject.key, myObject);
}