Add route prefix to all actions in controller using one attribute - c#

Here is my controller
public class SpecializationsController : Controller
{
public ActionResult Action1()
{
//body
}
public ActionResult Action2()
{
//body
}
Default url for Action1 is of course /Specialization/Action1. I want to add prefix to all Actions in my controller to make my ulr like /prefix/Specialization/Action1.
I tried to add [RoutePrefix("prefix")] to my controller but it doesn't work. I would like to avoid adding [Route] attribute for each action in my controller. So how can I add this prefix?

I would create Areas:
Areas are an ASP.NET MVC feature used to organize related functionality into a group as a separate namespace (for routing) and folder structure (for views). Using areas creates a hierarchy for the purpose of routing by adding another route parameter
I know you may think this is an overkill for simply having a "prefix" but the reason I suggest this approach is because if you have the need to add a "prefix", chances are you have the need to separate the views, models etc. as well.

You need to add a route to your route collections instead of using route attributes
routes.MapRoute(
"Route",
"prefix/{controller}/{action}",
new { controller = "Specializations", action = "Index" });

Related

C# Core MVC How to apply custom URL to controller action

Question:
Edit: It seems my question is actually not a routing issue but an anchoring issue.
If I have assigned a route:
[Route("~/Envelope/List/AcademicYear/{year}")]
public IActionResult AcademicYear(string year)
{
}
How would I correctly use an asp-action to call this route?
using
<a asp-action="List/AcademicYear/" asp-route-id="#Model.AcademicYear">#Model.AcademicYear</a>
returns a url with a %2f (Envelopes/List%2fAcademicYear/2122) instead of / (Envelopes/List/AcademicYear/2122) and thus results in a 404 error
How do I use Custom URL with asp-action to call a specific Action in my Controller?
or
How do I change the routing so I can call an action from a controller with a non default route mapping?
Context:
I've read https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0
and yet i'm still confused on the whole concept of routing and how to interacts with controllers and actions.
In my application I have a controller called Envelope - it controls everything to do with my Envelopes.
I have a class in the Envelopes controller called
public class EnvelopeController : Controller {
public IActionResult List() {... return View()}
and it returns the List.cshtml view. The current url as set by default route mapping: /Envelope/List
In the List.cshtml I have a link that is intended to filter the List on a year parameter
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"> #Model.AcademicYear</a>
My intention is to pass this into a method in the Envelopes controller called "AcademicYear" that gathers the Envelope data stored in temp data, deseralises it and then returns a filtered version based on the parameter:
public IActionResult AcademicYear(string academicYear) { return View("List", newViewModel)}
The return url after this point is correctly: /Envelope/AcademicYear?academicYear=21%2F22
However I would Like to know how to change this so even though I call the Action
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"/>
the URL returned would look like this /Envelope/List/AcademicYear/2122/
Is there a way of doing this? Am I looking at the problem the wrong way? I have thought about simply passing a parameter in the List action and running some form of control to do either set of operations depending on the parameters existence but realistically the List method and the filtering AcademicYear method aren't really doing the same thing and I'd like to seperate out the code into different methods if possible.
Even if its not the appropriate solution I would still like to know if it is possible to change the URL routing for an action after it has been called.
Edit :
I have tried using HttpGet(List/AcademicYear/{academicYear:[a-zA-Z]} however when I do this I can't actually call List/AcademicYear as an asp-action due to the "/" and how that encodes to %2f
Answer:
With the help of the below solutions I realised I was looking at the problem wrong and was actually having issues creating correct anchors.
Reading: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#url-generation-and-ambient-values-1
I realised the answer was staring me in the face and I used this alongside the Routes provided for in the answers
<a id="#academicYear.Name" href="#Url.Action("AcademicYear", "Envelope",new {year= academicYear.Name})">
Maybe you just need to decorate your action like that:
[HttpGet("List/AcademicYear/{year:int}")] // or HttpPost it depends on you
public IActionResult AcademicYear(string year) { }
You can add several attribute routes to the action.
[Route("~/Envelope/List/AcademicYear/{year}", Name="ListRoute")]
[Route("~/Envelope/AcademicYear/{year}")]
public IActionResult AcademicYear(string year) { }
and if you need this url
http://localhost:xxxx/Envelope/List/AcademicYear/2021
you can use this html helper, that will create a right anchor tag for you using an attribute route name.
#Html.RouteLink(#Model.AcademicYear, "ListRoute", new { year = #Model.AcademicYear })
I added Name="ListRoute" to the attribute routing (see above)

Request matched multiple actions resulting in ambiguity for actions with different parameters in ASP.NET 5 / MVC 6

I have a simple route in my project:
routes.MapRoute(
name: "api",
template: "api/{controller}/{action}");
In my controller I have two actions:
[HttpGet]
public string Get(string value)
{
return value;
}
[HttpGet]
public string Get(int id)
{
return id.ToString();
}
Now when I try to do a url like api/controller/get?id=1 it does not work because the framework cannot distinguish between two actions. As far as I remember it did work pretty well in ordinary web api because it's obvious that this url matches only one of the actions based on it's parameter. Did I do something wrong or it's not supported in the new MVC6?
Did I do something wrong or it's not supported in the new MVC6?
MVC Action Selector dosen't regard Action's parameters during select action. Therefore you can't have two actions correspond one route template. Except e.g actions have different Action Constraints (HttpPost, HttpGet).
Choose action logic in code.
In theory choosen logic between some actions based on parameters have to be into SelectBestActions method, but it do nothing

Controller actions naming convention

As naming convention says, WebApi controller actions name should be Get(), Put(). Post() etc. But tell me if I have a controller as CustomerController, now I want to have two actions inside of it. One is GetCustomerById(int id) and another one is GetCustomerByAge(int age). Here both the actions accept one parameter as int.
So, if I want to make the url user friendly like "api/customer/" also I want to follow the actions naming convention like only Get(int id)/Get(int age), how will I do it?
If you want Web Api to look for the action name when routing, change the WebApiConfig.cs class in the App_Start folder to below:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then you can just make a GET request to
http://mysite/api/customer/GetCustomerById/1
Also I recommend you to study the article below for deeper understanding:
Routing by Action Name
Restful services should not contain CRUD function names in the URI (https://restfulapi.net/resource-naming/)
this would more appropriate:
for GetById -
http://mysite/api/customers/123
for GetByAge - http://mysite/api/customers?age=21
An alternative way is HTTP Methods attribute.
Instead of using the naming convention for HTTP methods, you can explicitly specify the HTTP method for an action by decorating the action method with the HttpGet, HttpPut, HttpPost, or HttpDelete attribute.
In the following example, the FindProduct method is mapped to GET requests:
public class ProductsController : ApiController
{
[HttpGet]
public Product FindProduct(id) {}
}
To allow multiple HTTP methods for an action, or to allow HTTP methods other than GET, PUT, POST, and DELETE, use the AcceptVerbs attribute, which takes a list of HTTP methods.
public class ProductsController : ApiController
{
[AcceptVerbs("GET", "HEAD")]
public Product FindProduct(id) { }
}
This is one of those situations where following standards to the letter may not actually help you much.
One solution would be to allow yourself to deviate from the REST style.
You could have two get methods:
one could be GetByID, another could be GetByAge.
Your routes could look like this:
api/customer/getbyage/20
api/customer/getbyid/1134
This isn't exactly REST but it's close enough and one exception won't break anything.
My point is to use whatever implementation helps your product make sense and don't worry too much about standards.

MVC URL path extensions

I was just wondering whether its possible to have something like this: I have an Area named Admin and a Controller named 'Edit'. Within this controller I have my Index() which simply lists a bunch of hyperlinks that is treated by the 'Brand' action.
Therefore my url so far is: Admin/Edit/{Brand}.
My question is whether it is possible to have for example: Admin/Edit/{Brand}/Create (as well as edit and delete). This isn't to delete brands, its just to create things within those brands?
I approach that my approach may be misguided and this may necessitate being split into multiple controllers or whatever so don't think that I would like a workaround to make it work this way.
You could define the following route in your area registration:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{brand}/{action}",
new { action = "Index" }
);
And if you wanted to have other controllers than Edit in this area which have the default route, you could register 2 routes but you will have to define a constraint for the {brand} token or the routing engine won't be able to disambiguate between a brand and a controller action name.

How Does ASP.NET MVC Dynamically Create Controllers and Views?

This is a point of curiosity rather than an actual problem I'm having: how does the ASP.NET MVC framework create the appropriate controller and view objects from the string name in the RouteCollection? I've tried searching through the actual ASP.NET MVC2 code to figure this out but get lost in the controller factory class.
When I register my routes, I map the first block to the controller, the second to an action, the third to an ID, but how does the program take string names and create new objects?
/Users/Update/15354 = New UserController, executes Update() - how?
The only thing I can imagine working is reflecting through all the classes in the project and creating one that matches the requested name, but then how would you resolve a conflict in class names without knowing which namespace to use?
The standard naming convention "FooController" dictates that all controllers should have the suffix "Controller"; thus, when your current route indicates the controller "Foo", ASP.NET MVC examines the types within your application which inherit from Controller and looks for one whose name matches "Foo" + "Controller".
You are correct to assume that ASP.NET MVC is using reflection to find the desired controller, but it is a relatively low impact because the reflection data is cached by the application during its initial startup.
If you have two controllers with the same name and different namespaces, you can use the alternative form of MapRoute() to indicate which controller you intend to resolve.
routes.MapRoute(
"Error_ServiceUnavailable",
"error/service-unavailable",
new { controller = "Error", action = "ServiceUnavailable" },
new[] { "Controllers" }
);
routes.MapRoute(
"Admin_Error_ServiceUnavailable",
"admin/error/service-unavailable",
new { controller = "Error", action = "ServiceUnavailable" },
new[] { "Areas.Admin.Controllers" }
);

Categories