ASP.NET WebAPI 2 Routing - c#

I just have one quick question about what seems to have been a limitation with ASP.NET Web API Attribute Routing, but hoping I just suck at research. In my controller, I'm trying to do something like this:
public class OrdersController : ApiController
{
[HttpGet]
[Route("{apiRoot}/customers/{id:int}/orders")]
public IHttpActionResult GetCustomerOrders(int id) {...}
}
Where {apiRoot} is defined in either a configuration file.
This may not actually be necessary, but I'd like to know how to put a specific path in the route attribute without having to code a static path. Is the general idea here supposed to be that you only put text into the route path, except for your parameters which go in {}?

How about switching to using a RoutePrefix:
[MyRoutePrefix]
public class OrdersController : ApiController
{
[HttpGet]
[Route("customers/{id:int}/orders")]
public IHttpActionResult GetCustomerOrders(int id) {...}
}
public class MyRoutePrefixAttribute : RoutePrefixAttribute
{
public MyRoutePrefixAttribute()
{
Prefix = "the route prefix";
}
}
RoutePrefixAttribute isn't sealed like RouteAttribute so extending it should allow you do what you need. Assuming, of course, that all of the controllers in a single class using the same root path.
Note: I haven't had a chance to try this but given what I know of attribute routing, I don't see why it shouldn't work.

Related

How to add suffix on API Endpoints .NET Core

Is there a way to automatically add a suffix on all endpoint routes e.g .json.
v1/users.json
v1/users/{id}.json
so what I have tried so far is I created a BaseController which look like this
[ApiController]
[Route("v1/[controller].json")]
public class BaseController : ControllerBase
{
}
but every time I use it to my controller it looks like this
v1/users.json
v1/users.json/{id}
You can add extra routing to the actual endpoints rather than the controllers
[ApiController]
[Route("v1/[controller]")]
public class BaseController : ControllerBase
{
[HttpGet(".json")]
public IActionResult Get()
{
}
// Without Route Parameters
[HttpGet("{id}.json")]
public IActionResult Get([FromRoute] int id)
{
...
}
// With Route and Query Parameters
[HttpGet("{id}.json/friend")]
public IActionResult Get([FromRoute]int id,[FromQuery] string friendName)
{
...
}
// With Route and Query Parameters and Body
[HttpPost("{id}.json/friends")]
public IActionResult Get([FromRoute]int id,[FromQuery] string message, [FromBody]IFilter filter)
{
...
}
}
You could use the URL Rewriting Middleware to accept URLs with .json and then simply remove it. So something like:
/api/users/123/picture.json?query=123
would become:
/api/users/123/picture?query=123
You can do this by adding the following code to your Startup's Configure method:
var rewriteOptions = new RewriteOptions()
.AddRewrite(#"^(.*?)(?:\.json)(\?.*)?$", "$1$2");
app.UseRewriter(rewriteOptions);
See the docs for more information.
Caveat: If you use Url.Action(...), etc. to generate a URL within code, then it won't include .json. The rewrite only affects incoming requests.

.Net 5.0 - In case of multiple attribute routes defined on an action, query parameter always comes as null

In .Net 5.0 API, I have an action on which I have applied multiple attribute routes as defined below
[HttpGet, Route("Get/{id?}"), Route("{id}")]
public void Get(long id)
{
//do something
}
With this routing, the following paths work fine
api/Controller/1
api/Controller/Get/1
However, if I pass the id as a query parameter, I always get 0 in 'id'.
e.g.
api/Controller/Get?id=1.
Any idea what might be causing this issue and how it can be fixed?
Just tested using both net 5 and net 6 , VS 2022 and Postman
api/Controller/Get?id=1
it is working properly
I've used this controller, action
[Route("api/[controller]")]
public class MyController : ControllerBase
{
[HttpGet, Route("Get/{id?}"), Route("{id}")]
public void Get(long id)
{
//do something
}
...
}
UPDATE
but if I add [ApiController] attribute then query string id is 0
[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
It seems that ApiController is not supposed to have a query string at all. It doesn't work even if I change route to this
[HttpGet, Route("Get}")]
public void Get(long id)
{
//do something
}
UPDATE 2
See PO answer how to make ApiController to accept the parameters from a query string.
The problem seemed to be with [APIController] attribute along with the [Route] attribute. I set the SuppressInferBindingSourcesForParameters to true and it seemed to have resolved the issue and now all three URL formats work.
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
});
Update:
For Post and Put requests, you need to add [FromBody] attribute before parameter.

Query String on Web API not working

I have a web api controller that has two actions, but only one of them can receive requests.
public class ApiBase : ApiController
{
}
[RoutePrefix("api/Test")]
public class TestController : ApiBase
{
[HttpGet]
[Route("")]
public IHttpActionResult Get() {} // I only want this action to handle http://blah/api/Test but it's also handling http://blah/api/Test?id=1
[HttpGet]
[Route("{id}")]
public IHttpActionResult Get([FromUri] int id){} // http://blah/api/Test?id=1 couldn't reach here
}
I realized the problem is with base class. If TestController doesn't inherit from base class, it works as expected. What's missing from my base class?
Your attribute routing definition has a routing pattern api/Test/{id} mapped to the second Get method. So you should be accessing it like /api/test/34
When using attribute routing , the default routing with the querystrings and ? won't work. You should use the pattern defined in the attribute route definition.
/api/test/34 will work for Get([FromUri] int id){}
/api/test/ will work for Get() {}

ASP.NET MVC - Nesting Routes / Controllers

I have an ASP.NET MVC app. I have seen similar question asked. However, I haven't found a good answer. Essentially, I want to use the following routes:
/admin/users
/admin/users/create
/admin/users/[someId]
/admin/roles
/admin/roles/create
/admin/roles/[someId]
I have the following file structure:
/Controllers
AdminController.cs
/Admin
UsersController.cs
RolesController.cs
/Views
/Admin
Index.cshtml
/Users
Index.cshtml
Detail.cshtml
Create.cshtml
/Roles
Index.cshtml
Create.cshtml
Detail.cshtml
When I run my app, I just get The resource cannot be found.
What am I doing wrong? I set breakpoints, but none of them are being hit. It's like the routes aren't mapping to the controllers. I'm not sure what I need to do though.
You do not need to create sub folders for this to work. Just have 2 controllers(UsersController and RolesController) and you can use attribute routing to define the custom routing pattern you want.
Assuming you have attribute routing enabled
public class UsersController : Controller
{
[Route("admin/users")]
public ActionResult Index() { // to do : Return something }
[Route("admin/users/create")]
public ActionResult Create() { // to do : Return something }
[Route("admin/users/{id}")]
public ActionResult View(int id) { // to do : Return something }
}
Or you can do the RoutePrefix on the controller level.
[RoutePrefix("admin/users")]
public class UsersController : Controller
{
[Route("")]
public ActionResult Index() { // to do : Return something }
[Route("create")]
public ActionResult Create() { // to do : Return something }
[Route("{id}")]
public ActionResult View(int id) { // to do : Return something }
}
You can do the samething for the RolesControllers as well.
You can enable attribute routing in the RegisterRoutes method in RouteConfig.cs file.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes(); //This line enables attribute routing
//Existing default Route definition goes here
}
You may also consider creating an "Admin" area and put your controllers inside that. Areas are the right solution if you want to logically group similar functionality.
If you do not prefer attribute routing ( why not ?) , you an define these custom route patterns in your RouteConfig. The order in you define the route matters.So make sure you define your specific routes before the default generic one.
You can also override your route tables by decorating your action methods with the RouteAttribute class.
For example:
class AdminController
{
[Route("/admin/users/create")]
public ViewResult CreateUser()
{
...
}
}
This has the advantage of separating the method name from the url component.
You can also route multiple URLs to a single method:
class AdminController
{
[Route("/admin/users/{someId:guid}")]
[Route("/admin/users/{someId:guid}/details")]
public ViewResult UserDetails(Guid someID)
{
...
}
}
As mason said, the file structure isn't important in MVC routing.
If you want to use convention (folder) based routing, you could use MvcCodeRouting to do exactly what you have specified here. It uses namespaces by default, so when you add controllers in a hierarchy, it will generate routes in the same hierarchy automatically. No need to apply the [Route] attribute everywhere and setup your routes manually.

How to set this Area up in my ASP.NET MVC Application

I'm trying to setup an Area Route in my ASP.NET MVC application.
I'm also using the nuget package AttributeRouting, not the normal MVC register area routes thingy.
From my understanding, area routes look like this : /area/controller/method
What I'm trying to do is :- /api/search/index
which means:
Area => Api
Controller => SearchController
ActionMethod => Index
.
[RouteArea("Api")]
public class SearchController : Controller
{
[POST("Index")]
public JsonResult Index(IndexInputModel indexInputModel) { .. }
}
But that doesn't create that route. This is what it creates: /api/index
The search controller is missing.
I've had a look the docs and noticed the RoutePrefix so I tried this..
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public JsonResult Index(IndexInputModel indexInputModel) { .. }
}
and that actually creates the route /api/search/index.
But why do i need to put the RoutePrefix in there? Shouldn't it be smart enough to already figure out that this is a SearchController and create the 3-segment route?
You don't need to put a RoutePrefix anywhere. It's just there as a refactoring/DRY aid. Consider:
[RouteArea("Api")]
public class SearchController : Controller
{
[POST("Search/Index")]
public ActionResult Index() { }
}
If you had a number of actions, maybe you want them all with the "Search" prefix, so you'd do:
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public ActionResult Index() { }
// Other actions to prefix....
}
Shouldn't it be smart enough?
Not to be cheeky, but no. AR was never intended to read all your code for you and magically generate routes. It was intended to keep your URLs top of mind, and to do that you should see your URLs. Not that this is the best or only way of doing things, just that was my intent from the get.
The real reason why it isn't smart enough is that the concept of "Area" has nothing to do with URL. An area is a logical unit. You could expose that logical unit without any route prefix (so it would be hanging off ~/) or you could expose it off "This/Is/A/Prefix".
However, if you want it to be smart enough.... I just released v3.4, which will let you do this (if you want to; don't have to):
namespace Krome.Web.Areas.Api
{
[RouteArea]
[RoutePrefix]
public class SearchController : Controller
{
[POST]
public ActionResult Index() { }
}
}
This will yield the following route: ~/Api/Search/Index. The area comes from the last section of the controller's namespace; the route prefix comes from the controller name; and the rest of the url comes from the action name.
One more thing
If you want to get out a route area url and route prefix rat's nest for individual actions in a controller, do this:
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public ActionResult Index() { }
[GET("Something")] // yields ~/Api/Search/Something
[GET("NoPrefix", IgnoreRoutePrefix = true)] // yields ~/Api/NoPrefix
[GET("NoAreaUrl", IgnoreAreaUrl = true)] // yields ~/Search/NoAreaUrl
[GET("Absolutely-Pure", IsAbsoluteUrl = true)] // yields ~/Absolutely-Pure
public ActionResult Something() {}
}

Categories

Resources