MVC routing expect the Id when it sould not - c#

I have troubles with my routing:
I have in my controller this method:
[HttpGet]
public JsonResult GetCase(CustomIdentity currentUser, string objectType, Guid objectId)
And in my routes
r.Match("CTRL/{id}", "CTRL", "details");
r.Match("CTRL/GetCase", "CTRL", "GetCase");
The problem is when i want to initiate a GET to my method:
I can do that only with
http://a.com/CTRL/GetCase/EE5014C2-C4AA-44E2-80F9-A23D01317790?objectType=123&objectId=1E5014C2-C4AA-44E2-80F9-A23D01317790
But i need
http://a.com/CTRL/GetCase?objectType=123&objectId=1E5014C2-C4AA-44E2-80F9-A23D01317790
What is wrong with my code?

Switch the order of the route setup. The first URI...
http://a.com/CTRL/GetCase/EE5014C2-C4AA-44E2-80F9-A23D01317790?objectType=123&objectId=1E5014C2-C4AA-44E2-80F9-A23D01317790
matches the CTRL/{id} route you set up where GetCase in the URI satisfies {id} template parameter. The convention uses the first matched route it finds when mapping routes.
You need to change the order you set up the route. From what you've shown, that would be
r.Match("CTRL/GetCase", "CTRL", "GetCase");
r.Match("CTRL/{id}", "CTRL", "details");

Related

MVC Attribute Routing RoutePrefix does not work with default routing

I have a work-around, but I'd really like to know why this doesn't appear to work in MVC. (.Net 4.6.1)
I have a controller which I want to use a RoutePrefix:
[RoutePrefix("entry")]
public class DefaultController : Controller
{
[HttpGet]
[Route(), Route("Index")]
public ActionResult Index()
{
// ...
}
}
In the route config:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "entry", action = "index", id = UrlParameter.Optional }
The issue is that with this configuration, running under local IIS to test, navigating to "localhost/testApp/entry" works, "localhost/testApp/entry/index" works, however the default "localhost/testApp/" results in a 404.
This has been doing my head in because on a fresh project with the default controllers and a default set to "home" and "index" the "localhost/testApp" would render Home/Index without an issue.
I narrowed it down to the RoutePrefix being the issue. If I remove the RoutePrefix and change the defaults to: new { controller = "default", action = "index", id = UrlParameter.Optional }
Then "localhost/testApp" works, but obviously this requires using /default for other routes rather than /entry.
Also, if I leave the prefix in (switching the default controller back to "entry") and add Route("~/") to the Index method, then "localhost/testApp" also works as according to the doco that a ~ route overrides the route prefix.
I'd like to know if there is an explanation why RoutePrefix doesn't seem to play nice with default routing? I'm fine with adding a ~/ route for that default action, but it seems I'm missing some understanding on how RoutePrefix is intended to be used.
Action attribute routing has the highest priority. If you use it only route attributes will be working, everything else will be ignored. You can to one action as many routes as you need.
Since you have 2 variants - Route() and Route("Index") it works only for 2 urls -"localhost/testApp/entry" and "localhost/testApp/entry/index".
if you remove Route() it will work only for one url-lcalhost/testApp/entry/index".
If you add 3rd Route("~/") it will work for 3rd url "localhost/testApp"
Sign ~ means that any prefixes should be ignored, it starts from root.
So you can not use default conventional routing on Index action since it is only obeys routing attributes.
Also, you have a controller [RoutePrefix("entry")] attribute routing too and it that next highest priority and because of this it overrides your convention routing in the config file. This is why default routing doesn't work for this controller and it doesn't go to Index automaticaly. To make default route work you need to remove route prefix and fix web config
defaults: new { controller = "default", action = "index", id = UrlParameter.Optional }
Current default controller = "entry" doesn't exist at all.
So you have two choices to have Index as default route action - remove all attribute routing and lost all another extra routes or add one more.
Thanks to Serge for helping point out a bad assumption I had about [RoutePrefix]. The problem here turns out that [RoutePrefix] is not a substitute name for a controller, (though that is how it behaves on the surface) but rather a prefix to each individual action. While the mapping in the URL will be identical:
Example 1:
public class EntryController
{
public ActionResult Index() { ... }
}
Example 2:
[RoutePrefix("Entry")]
public class DefaultController
{
[Route("Index")]
public ActionResult Index() { ... }
}
Both of these examples would resolve "localhost/testApp/entry/index", however only the first mapping would be considered as a match for {controller}/{action} and resolve a "defaults" mapping of "entry/index".
So if an action /w Attribute-based routing needs to be made a root default you need to explicitly declare it as the root using [Route()] if there is no [RoutePrefix], or [Route("~/")] if there is a [RoutePrefix]. since it won't be included in the {controller}/{action} routing. (Verified by removing the Default {controller}/{action} routing entirely).

What is the Name property on Http verbs attribute?

In the docs for this property, nothing is said, so what exactly does this property do?
Reference Routing to controller actions in ASP.NET Core :
Route Name
The following code defines a route name of Products_List:
public class ProductsApiController : Controller
{
[HttpGet("/products/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id) { ... }
}
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.
Reference Generating URLs by route

WebAPI 2 : Default GET ALL is invoked with wrong parameter

I am using WebAPI 2 with EF and scaffolding webapi controllers from visual studio.
Each controller is created with 4 default verbs (GET,PUT,DELETE,POST) and 5 actions. while there are two versions of GET action.
IQueryable<entity> GetEntities ()
Task<IHttpActionResult> GetEntity(GUID key) // default is int id but I changed to guid.
I am using attribute routing and route prefix for the controller. just some fancy keywords for better management of url. [RoutePrefix("api/v3/Company")]
Problem :
Ideally when a wrong parameter is sent in url, it should return error, but it is not raising error, instead it fall back to the action without parameter.while if I send a wrong GUID, it shows error.
Like if I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
it shows the right result.
when I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e96500 (wrong key)
it set back to GetEntity() function and shows all records
when I call:
http://localhost:8080/api/v3/Company/1 (not a GUID length parameter)
it do the same and shows all records.
I am using attribute [Route("{id:guid}")]
Really appreciate if I can get some guidance on this!
It is most likely that the route is defaulting back to the convention-based mapping.
You need to explicitly make apply the route attribute on actions to let the routing know that it is the default route got GET
[RoutePrefix("api/v3/Company")]
public class CompanyController : ApiController {
//GET api/v3/Company
[HttpGet]
[Route("")] //Default Get
public IQueryable GetEntities() { ... }
//GET api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
[HttpGet]
[Route("{id:guid}")] // ALSO NOTE THAT THE PARAMETER NAMES HAVE TO MATCH
public Task<IHttpActionResult> GetEntity(Guid id) { ... }
//...other code removed for brevity
}
Make sure that attribute routing is enabled in the web api config
config.MapHttpAttributeRoutes();

C# MVC utilising parameter on default route

I'm using MVC 4.
My default route on my site is Home/Index so when the user enters the URL www.example.com it goes to that route.
Could you let me know if it is also possible to receive a parameter appended to that URL i.e. www.example.com/param? It works if I use www.example.com/Home/Index/param but that's not ideal.
I'm guessing its something I need to add to the Global.asax but I can't find examples anywhere.
context.MapRoute(
"Home_all",
"/{*actions}",
new { controller = "Home", action = "Index"}
);
But be aware that route will match all urls , so you need to register it at last :) That Routing will be called like this in your Home Controller for example... Actions will be a part from the url, and you can even add some parameter in the query string
public ActionResult Index(string actions, string id)
{
}

MVC Routes... How do I set up my controller to deal with /configuration/building/add

I have an architecture where I have numerous objects I can configure. Examples of URLs I want are as follows:
/configuration/building/add
/configuration/building/edit/1
/configuration/group/add
/configuration/group/edit/1
I have a Configuration controller but how do I intercept or deal with building/add and building/edit/1 etc... If it were AddBuilding I could simply add an AddBuilding() function, and similarily how do I get it to work for configuration/building/edit/
Here's what you can do for the first one - open up the Global.asax.cs file of your site and put this in RegisterRoutes before the standard MVC catch-all route (the one that uses the route "{controller}/{action}/{id}"):
routes.MapRoute("AddBuilding", "configuration/building/add",
new { controller = "Configuration", action = "AddBuilding" });
The others will be the same, but different names (first parameter) and action, whislt the edit routes but would include an {id} route placeholder and route parameter (but not optional - unlike the MVC default route):
routes.MapRoute("EditBuilding", "configuration/building/edit/{id}",
new { controller = "Configuration", action = "EditBuilding" });
By leaving the id off the route defaults we make it required. I'm assuming this, because I'm guessing the Url /Building/Edit doesn't logically map to anything.
As a side node - including verbs in your urls isn't really in keeping with REST methodology, however you're not the first to do it by a long way (I include myself in that too). That said - trying to keep to it usually makes your life a lot easier, as you'll find your controllers will be cleaner, as will your route table, and your site's URL space will be a lot smaller and more obviously hierarchical. This last point is - handy for zooming around the site at dev time, but more importantly it's crucial for SEO.
So (I've commented this code heavily, hopefully enough to provide some nuggets of knowledge!):
public class ConfigurationController{
////HTTP GET /Buildings
/// DISPLAYS BUILDINGS
public ActionResult Buildings(){
//get model and return view that shows all buildings with perhaps a
//partial in that for creating a new one (or you can use another action)
//The HTML form on that view will POST to the URL handled by the method below.
}
////HTTP POST /Buildings
/// CREATES A NEW BUILDING
//use ActionName here to make this and the method above accessible through
//the same URL
[ActionName("Buildings")]
[HttpPost]
public ActionResult CreateBuilding(BuildingModel model){
//validate the model, create the object and return the same
//view as the Buildings() method above (after re-loading all the
//buildings. Or, you can issue a redirect, effectively transferring
//control back to the method above.
}
////HTTP GET /Configuration/Building/id
///DISPLAYS A BUILDING
public ActionResult Building(int id){
//get building and return view, which also contains Edit functionality
}
////HTTP POST /Configuration/Building/id
///EDITS A BUILDING
[HttpPost]
public ActionResult Building(int id, BuildingModel model){
//very similar to the CreateBuilding method - and again you might
//either simply return a building view at the end, or redirect
//to the method above.
//Note that we don't need [ActionName] here because this and the
//get method can have the same method names, because they are overloads
//i.e. since they have different method signatures we can call them the same
//thing in code.
}
}
I've left off the group stuff to keep it short, and hopefully you'll be able to see how to do it from there.
With this in place, we only need at most two routes in Global.asax.cs - although I think the order will be important:
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("IndividualBuilding", "/configuration/buildings/{id}",
new { controller = "Configuration", action = "Building" });
routes.MapRoute("Buildings", "/configuration/buildings",
new { controller = "Configuration", action = "Buildings" });
Now we are using the HTTP verbs to signify what we intend to do with a particular request, and our URLs have become more 'logical'.
Another refactor
If you want to be 'clever' you can lump both buildings and groups under two routes
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("Individual", "/configuration/{controller}/{id}",
new { controller = "Configuration", action = "List" });
//again, handles GET and POST
routes.MapRoute("Collection", "/configuration/{controller}",
new { controller = "Configuration", action = "Single" });
Now you do both buildings and groups controllers as I showed above, but replace Buildings (remembering the ActionName attribute on the second method) with List and Building with Single.
One final thing to consider is that because of the default MVC route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Default", action="Home", id = UrlParameter.Optional });
Both of your two controllers can still be routed via /Buildings/Single/1 or /Groups for example. This is a minor issue (dupe content isn't great SEO) but it can be something that people can use to sniff your site.
If you absolutely want to prevent this other url format; you can take out the default route, meaning you'd have to explicitly route other stuff that might already work (not a great issue).
Or you can use a little trick that will make it far harder: use explicit [ActionName] attributes with characters in the route name that won't be allowed through IIS - e.g. ":Single" or ":List", and then adjust our two routes from a couple of code blocks back accordingly.
So firstly you can create a controller action called AddBuilding() as you have hinted.
Then in your Global.asax file in the RegisterRoutes method you can add a route like so:
routes.MapRoute(
"AddBuilding", // Route name
"configuration/building/add", // URL with parameters
new { controller = "Configuration", action = "AddBuilding" }
);
You should not though that you will likely still be able to access the page using "/configuration/addbuilding" because of your default route mapping.
You edit one will be similar expect you will want to map the ID value for this:
routes.MapRoute(
"EditBuilding", // Route name
"configuration/building/edit/{id}", // URL with parameters
new { controller = "Configuration", action = "AddBuilding", id = UrlParameter.Optional }
);
I think you will need to add this code with the default MapRoute setup to ensure that one does not take priority
Another approach would be to create a Configuration MVC area, and then have a building and group controller in that Area.
You can do that by Attribute Routing in ASP.NET MVC 5. Something like following;
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
You can add multiple route for the same controller as well. For details please check here

Categories

Resources