I have controller with Attribute routing similar to this.
[RoutePrefix("api/v1/user")]
public class UserController : ApiController
{
[Route("")]
[HttpGet]
public HttpResponseMessage Get([FromUri] UserModel user)
{
return Request.CreateResponse(HttpStatusCode.OK, user)
}
}
If I send in no parameters to this controller, "user" is null. But if I remove the attribute routing from the controller/method, the "user" object is instantiated with default values.
I read from previous posts that JSON.net will instantiate these objects by default but what I cannot find is why attribute routing removes this functionality.
Related
I have a simple ASP.NET Core API with the following controller:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
public IActionResult Index()
{
return Ok("index");
}
public IActionResult Get()
{
return Ok("get");
}
}
When I hit URL:
"http://localhost:6001/api/home"
With a GET request, I get the following error:
"The request matched multiple endpoints."
Normally, if I don't specify an action name, shouldn't it call the Index() method?
In ASP.NET Core API, action methods are matched by a combination of HTTP verb used as attribute on the method (which is HttpGet by default if you don't add any) and the route parameter used with it. In that sense both your action methods looks similar to the routing system. It sees two HttpGet method with no route parameter. That's why both method matches the request -
"http://localhost:6001/api/home"
making the routing system confused which to select. That's exactly what the error message is saying.
You need to make your non-default action method more specific, so that the routing mechanism can differentiate between the two. Something like -
[HttpGet("data")]
public IActionResult Get()
{
return Ok("get");
}
Now your Index method will be matched as default action method with the above URL.
Both of your endpoints are identical. Use routes to differentiate them. For example:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index()
{
return Ok("index");
}
[HttpGet, Route("data")]
public IActionResult Get()
{
return Ok("get");
}
}
This would open up:
http://localhost:6001/api/home/
and
http://localhost:6001/api/home/data
respectively using GET
You could also change the verb, for example use HttpGet for one and HttpPost for another.
I'm trying to implement a REST API in ASP.Net MVC. This is my code:
[Route("api")]
[ApiController]
public class ContactsController : ControllerBase
{
[HttpGet]
[Produces("application/xml")]
[Route("api/areas/{areaName}/contacts/{contactID}")]
public ActionResult<XmlDocument> Get(string areaName, string contactID)
{
return new XmlDocument();
}
}
However, when I surf to /api/areas/foo/contacts/bar, I encounter HTTP 404.
How can I fix my code?
Note that ActionResult<T> is part of asp.net-core and not the previous asp.net-mvc version. This means that you originally tagged the question incorrectly.
That said, the URL being called does not match the route template of the intended action and thus you will get a not found when trying to call the action.
Reference Routing to controller actions in ASP.NET Core
The provided route has [Route("api")] on the controller and [Route("api/areas/{areaName}/contacts/{contactID}")] on the action, which when combined would result in a URL that looks like
api/api/areas/foo/contacts/bar
I believe the intention was to have api only once so I would suggest remove one of them
Either from the action
[Route("api")]
[ApiController]
public class ContactsController : ControllerBase {
[HttpGet]
[Produces("application/xml")]
//GET api/areas/foo/contacts/bar
[Route("areas/{areaName}/contacts/{contactID}")]
public ActionResult<XmlDocument> Get(string areaName, string contactID) {
return new XmlDocument();
}
}
or removing the prefix on the controller
[ApiController]
public class ContactsController : ControllerBase {
[HttpGet]
[Produces("application/xml")]
//GET api/areas/foo/contacts/bar
[Route("api/areas/{areaName}/contacts/{contactID}")]
public ActionResult<XmlDocument> Get(string areaName, string contactID) {
return new XmlDocument();
}
}
I have a custom IRouter implementation, which looks in its basic form like this, for simplicity's sake I hardcoded some values:
public class MyRouter : IRouter
{
private readonly IRouter router;
public MyRouter (IRouter router)
{
this.router = router;
}
public async Task RouteAsync(RouteContext context)
{
context.RouteData.Values["controller"] = "Home";
context.RouteData.Values["action"] = "Index";
context.RouteData.Values["area"] = "";
await router.RouteAsync(context);
}
}
This works for a simple controller without a [Route] attribute defined:
public class HomeControlller
{
public IActionResult Index()
{
return View();
}
}
Again, this works correctly. Going to / will show the page.
However, as soon as I add [Route] attributes, I get a 404:
[Route("foo")]
public class HomeControlller
{
[Route("bar")]
public IActionResult Index()
{
return View();
}
}
Now, if I go to /foo/bar, I will see the page. However, if I go to /, I get a 404.
How can I fix this? If I look at the RouteData values when going to /foo/bar, I still see the values Home and Index as values for controller and action, respectively.
This is by design.
Actions are either conventionally routed or attribute routed. Placing a route on the controller or the action makes it attribute routed. Actions that define attribute routes cannot be reached through the conventional routes and vice-versa. Any route attribute on the controller makes all actions in the controller attribute routed.
Reference: Mixed routing: Attribute routing vs conventional routing.
For Conventional router, it is using MvcRouteHandler, and Attribute route will use MvcAttributeRouteHandler. When Controller or Action used with Route[], it will not go to Converntional router when you request the specific method.
I have the following code:
[Produces("application/json")]
[Route("api/[controller]")]
public class MarketReportInstancesTableController : BaseController
{
internal readonly MyIRIntegrationDbContext Context;
public MarketReportInstancesTableController(ILogger<MarketReportInstancesTableController> logger,
MyIRIntegrationDbContext context) : base(logger)
{
Context = context;
}
[HttpGet (Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request)
{
.... methd body in here
}
And I try to access with a URL like:
http://somehost/pca/api/MarketReportInstancesTable/pagedata
Which DOES NOT work, but
http://somehost/pca/api/MarketReportInstancesTable/
DOES WORK.
My question would be, why does the route do that? I want to have many paths in the same WebAPI controller.
Am I approaching it wrong?
You have no route template in the route. You only have a route name
Route names can be used to generate a URL based on a specific route. Route names have no impact on the URL matching behavior of routing and are only used for URL generation. Route names must be unique application-wide.
emphasis mine
//GET api/MarketReportInstancesTable/pagedata
[HttpGet ("pagedata", Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request) {
//.... methd body in here
}
Using [HttpGet] without a route template is the same as [HttpGet("")] which will map to the root of the controller with route prefix.
This explains why your root call works.
Reference Routing in ASP.NET Core
Reference Routing to Controller Actions
I have this configuration in the HttpConfiguration
config.Routes.MapHttpRoute("Default", "api/{controller}");
config.Routes.MapHttpRoute("Another", "api/{controller}/{action}");
config.Routes.MapHttpRoute("WithKey", "api/{controller}/{action}/{key}");
For that reason I cannot access my controller like this
http://<host>/api/products (works)
http://<host>/api/products/1 (doesn't work)
So I added the annotation Route in the get method but it doesn't work
[RoutePrefix("products")]
public class ProductsController : ApiController
{
[HttpGet]
public IQueryable<IProduct> GetProducts()
{
return db.GetProducts();
}
//[Route("products/{productID}")] Tried. Doesn't work
//[Route("{productID:int}")] Tried. Doesn't work
[HttpGet]
public IProduct GetProduct(int productID)
{
return db.GetProduct(productID);
}
}
The only way to make it work is typing the address like this http://<host>/api/products?productID=1, but I'd really want to access with this url http://<host>/api/products/1.
I can add new routes in the http configuration but cannot modify the existing ones. And I don't want to affect existing controllers.
How can I solve this, please?
First ensure that attribute routing is enabled before convention-based routes.
config.MapHttpAttributeRoutes();
//...convention-based routes.
config.Routes.MapHttpRoute("Default", "api/{controller}");
//...other code removed for brevity
Next you want to update the attribute routes.
[RoutePrefix("api/products")]
public class ProductsController : ApiController {
//GET api/products
[HttpGet]
[Route("")]
public IQueryable<IProduct> GetProducts() {
return db.GetProducts();
}
//GET api/products/1
[HttpGet]
[Route("{productID:int}")]
public IProduct GetProduct(int productID) {
return db.GetProduct(productID);
}
}