FormatAttribute needs forward slash for action without parameter - asp.net-web-api

I have a GET method without parameter and want below to work
/api/books.xml
This however works with forward slash
/api/books/.xml
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
[HttpGet]
[Route(".{format}")]
[FormatFilter]
public ActionResult<List<Book>> Get()
{
return bookService.Get();
}
}
Possible solutions that I tried are
Annotating without {id}
[Route("[controller]/[action].{format}")] // no slash between [action] and .{format}
Adding a default route in Startup.cs without {id}, so that if id parameter is not passed like in this problem then the routing should not expect a slash after {action}.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}");
});

Based on the currently defined routes on the controller, what you describe is by design.
Consider changing the routes to match the desired URL format
[ApiController]
public class BooksController : ControllerBase {
[HttpGet]
[Route("api/[controller].{format}")] //<--- GET api/books.xml
[FormatFilter]
public ActionResult<List<Book>> Get() {
return bookService.Get();
}
}

Related

how to set up url routing with sub-paths

i am new to webapi and MVC in general. If I wanted to group my service URLs like this
/api/account/create
/api/account/login
/api/account/resetpass
Am I able to put all 3 method calls in the same controller file and somehow map a particular request to the right method?
Create a Controller named Account and Create 3 [GET, POST, PUT, DELETE] method and name them create , login ,resetpass.
By Default, this is the routing for MVC / API(Id can be optional)
route Template: "api/{controller}/{id}",
Example :
public class AccountController : ApiController
{
[HttpPost]
public string Create()
{
// CODE
}
[HttpPost] // or [HttpGet]
public string Login ()
{
// CODE
}
[HttpPost]
public string Resetpass()
{
// CODE
}
}
if you had trouble calling them, try to give them a specific route :
[HttpGet("GetSubject/{subject}")]
public int GetSubjectId(String subject)
{
//CODE
}
Please if you get any error or misunderstanding, don't hesitate to post a comment

asp-action tag helper doesn't work with Route attribute on controller

I tried to do some simple thing. I wanted to have different action name and different method name:
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
}
Look that there is no Route attribute on the controller.
In such case tag helper asp-action works perfectly. But I thought that my action dosth would be placed in: localhost/Super/dosth
But it was not. So I figured it out that I probably should set the route for the whole controller, like this:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
But now asp-action stopped working. For example:
<a asp-controller="Super" asp-action="Register">
creates anchor to: localhost/Super and not to: localhost/Super/Register
When I remove Route tag from controller it works again.
My mappings are configured as standard says:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
So, how come asp-action does not work when there is a Route attribute on the whole controller
Applying [Route] attribute on a controller enables attribute routing for all controller methods. So, by doing so you force yourself to provide the route for every method (in one way or another).
With [Route("[controller]")] the base route template for your controller actions is just the controller name, so if you have multiple actions without [Route] or [HttpGet] (and other HTTP verbs) attributes applied on them:
[Route("[controller]")]
public class SuperController: Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
...you get yourself a AmbiguousMatchException because multiple controller actions will be matching same route:
/Super
You can either explicitly specify the route for every action:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
[HttpGet("Register")]
public IActionResult Register()
{
return View();
}
}
or specify action name as an part of expected route already on controller level:
[Route("[controller]/[action]")]
public class SuperController : Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
then, you don't have to specify the routes for actions, because you applied a route template on a controller level. Your actions will inherit that route template.
But, be aware that in order to overwrite it you'll have to overwrite it all, otherwise you'll append to the route template.
[HttpGet("/[controller]/dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
Read more about routing in official documentation.

Is it possible to have two controllers with the same route?

Is it possible to have two controllers in two assemblies with the same Route prefix attribute, but different Route attributes on the actions?
[RoutePrefix("api/route")]
public class Controller1 : ApiController
{
[Route("action1")]
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] string body)
{ }
[Route("{id}")]
public async Task<HttpResponseMessage> Delete(string id)
{ }
}
[RoutePrefix("api/route")]
public class Controller2 : ApiController
{
[Route("action2")]
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] string body)
{ }
}
This is possible. What was my problem was that the first controller had defined a DELETE action with route "{id}". Since id was an unconstrained parameter Web Api could not see the difference between "api/route/action2" and "api/route/idtodelete".
I ended up creating a regex constraint on the delete which excludes "action2" and allows all alpanumeric characters. Now it works.
[HttpDelete]
[Route("{id:regex(^(?!action2)[a-zA-Z0-9]*$)}")]
If you are use the different Route for all action method than it will be working. But if you are using the same Route prefix with same Route action than it will give an error.

Web Api Controller not resolving action

I am using .NET Core 2.2 and I have the controller below
[Route("api/[controller]")]
[ApiController]
public class CarsController : ControllerBase
{
[HttpPost]
[Route("api/cars/search")]
[ActionName("search")]
public ActionResult<IEnumerable<string>> SearchForCar([FromBody] SearchCriteria searchCriteria)
{
return new string[] { "value1", "value2" };
}
}
I am new to pure web api controllers.
I am confused about why when I post json to
http://localhost:51285/api/cars/search
I get 405 method not allowed?
I would normally have a route of
[Route("api/[controller]/action")]
That does work (once I remove route from the method attributes), but this wasn't the default provided in the template
Could someone let me know what I am missing?
Am I breaking convention by changing to
[Route("api/[controller]/[action]")]
Cheers
Paul
Since you not using the root slash in your action "/", the MVC middleware will search concatenating the route for controller and the action, should work like this
[Route("/api/[controller]/[action]")] // Check the root slash as first character
public ActionResult<IEnumerable<string>> SearchForCar([FromBody]
SearchCriteria searchCriteria)
Or like this
[Route("api/[controller]/[action]")] [ApiController] public class
CarsController : ControllerBase
Or
In controller:
[Route("api/[controller]")] [ApiController] public class
CarsController : ControllerBase
In action:
[Route("search")] // [action] Takes the method name
public <ActionResult<IEnumerable<string>>
SearchForCar([FromBody] SearchCriteria searchCriteria)

Attribute routing and inheritance

I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?
Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22
Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)

Resources