MVC Routing : "Multiple action ids - asp.net-mvc

I am having problems with my MVC Routes.
I am trying to get to the following url ... "http://localhost/api/Countries"
I have defined the following routes in the following order ...
RouteTable.Routes.MapHttpRoute(
name: "Continents",
routeTemplate: "api/countries/Continents",
defaults: new { controller = "countries", Action="Continents" }
);
RouteTable.Routes.MapHttpRoute(
name: "CountryRegions",
routeTemplate: "api/countries/Regions",
defaults: new { controller = "countries", Action = "CountryRegions" }
);
RouteTable.Routes.MapHttpRoute(
name: "CountryByCodeApi",
routeTemplate: "api/{controller}/{countryCode}",
defaults: new { controller="countries", countryCode = System.Web.Http.RouteParameter.Optional }
);
Whenever I go to the desired URL I am getting the error "Multiple actions were found that match the request". This would make sense if the third segment of the routeTemplate property was optional but it was my understanding that by NOT enclosing it in braces that it made it a required segment in the target URL. Obviously "http://localhost/api/countries" does not include "Continents" or "Regions" so why would they be identified as matching the request.
Ya' know. These routes SEEM like a simple enough thing but when you get down to it it's a cryptic as RegEx's !!!
Any thoughts?

The last route definition doesn't provide action name through route definition nor it provides it through route defaults. If route definition should omit it, then add it to defaults as this:
routes.MapRoute(
"CountryByCodeApi",
"api/{controller}/{countryCode}",
new {
controller="countries",
countryCode = RouteParameter.Optional,
action = "CountryCodes"
}
);
Note that this is just the last route definition. The upper couple stays as it is.
public ActionResult CountryCodes(string countryCode)
{
// do whatever you please
}

Related

MVC Additional Custom routes

I want to implement a custom route in my MVC app and I just can't get it to work. I want to keep the exist default route as specified when you create your MVC app.
The routes I want to be valid should look like this:
default: /{controller}/{action}/{id}
new custom: /{controller}/{appid}/{action}/{id}
With the custom domain, I will be passing the appid in with every request, but the {id} should be optional. The routes are thus defined as follow:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Updates", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "NewPackageRoute",
url: "{controller}/{appid}/{action}/{id}",
defaults: new { controller = "apps", appid = "00000000000000000000", action = "Index", id = UrlParameter.Optional }
);
On the AppsController, I have an action method Index:
public ActionResult Index(string appid, string id)
If I supply the id parameter, this action method is hit as expected. I am expecting this method to also be called if the {id} parameter is not supplied, thus leaving the id parameter as null. This however does not happen.
How should I define my route differently? Should I perhaps rather make use of AttributeRouting for achieve my goal?
I should maybe also add... If I call the Search action on the Apps controller, I can get to the action. This would happen through the default route...
Hope I have all and enough info...
Oh my, but I guess I should've tried before I posted this. I left this issue for a day and now I got it working without any effort...
Thanks #StephenMuecke. You did point out the ordering of the routes which I forgot about. I played with the order initially, but at that point I had other issues in the route definitions that caused it not to work.
All I added was as length check on the appid route value and it is working... My routes are defined as follow now:
routes.MapRoute(
name: "NewPackageRoute",
url: "apps/{appid}/{action}/{id}",
defaults: new { controller = "Apps", action = "Index", id = UrlParameter.Optional },
constraints: new { appid = #"^[a-zA-Z0-9]{20}" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Updates", action = "Index", id = UrlParameter.Optional }
);

I'm trying to pass a value to a controller from a url in mvc5

I'm having trouble passing a string variable to a controller in ASP.NET MVC 5. As far as I know my code should work but the controller just receives a null value no matter what I do.
First off I have my route.config above the default route, here is my route:
routes.MapRoute(
name: "Serial number",
url: "serial/{letterCase}",
defaults: new { controller = "Home", action = "Serial", letterCase="upper" }
);
and here is my simple code in the home controller
public ActionResult Serial(string letterCase)
{
var serial = "ASPNETMVC5ATM1";
if(letterCase == "lower")
{
return Content(serial.ToLower());
}
return Content(serial);
}
So in theory if I browse to the url home/serial/lower, it should accept the lower string but it isn't working I even tried using "home/serial?lower" and that doesn't work either.
Your route definition says the url to match is serial/{lettercase}. It does not say anything about the controller name ! So it will match for yoursite.com/serial/lower or yoursite.com/serial/anystring But not yoursite.com/home/serial/lower
So with your current route definition, You can access your action method without home(controller name) in the url. That is yoursite.com/serial/somestring
If you want to make it work with home/serial/lower url pattern, you need to add the controller part to the url in your route definition.
The below will direct any request to home/serial/{anystring} to your Serial method in Home controller.
routes.MapRoute(
name: "Serial number",
url: "home/serial/{letterCase}",
defaults: new { controller = "Home", action = "Serial",
letterCase="upper" }
);
Also this specific route definitions should always above the default route definitions. The order really matters.
Make sure that you have placed this specific route before any other route definitions:
routes.MapRoute(
name: "Serial number",
url: "serial/{letterCase}",
defaults: new { controller = "Home", action = "Serial", letterCase="upper" }
);
Now if you call serial/lower (and not home/serial/lower) you should get the expected results.

Identify tenant from the url in multi-tenant asp.net mvc application

I am creating a multi-tenant asp.net application. I want my url to follow
**http://www.example.com/test1/test2/**{tenantName}/{controller}/{action}
**http://www.example.com/test1/**{tenantName}/{controller}/{action}
**http://www.example.com/**{tenantName}/{controller}/{action}
Here the part of the url in bold is fixed (will not change)
{tenantName}=will be logical tenant instance.
I have followed this link
What will be the routing to handle this?
It's as simple as this:
routes.MapRoute(
"MultiTenantRoute", // Route name
"test1/test2/{tenantName}/{controller}/{action}/{id}", // URL with parameters
new { id = UrlParameter.Optional } // Parameter defaults, if needed
);
The part without braces must match. The parts inside the braces will be transfer into route data parameters. I've added an optional parameter id, as you usualy find in the controllers, but you can customize it. You can also give default values to tenantName, controller or action as usual.
Remember that routes are evaluated in the order they're registered, so you should probably register this route before any other.
EDIT after question update
You cannot specify a catch all parameter like this: {*segment} at the beginning of a route. That's not possible. ASP.NET MVC wouldn't know how many segments to include in this part, and how many to be left for the rest of the parameters in the route.
So, you need to add a route for each possible case,taking into account that the first route that matches will be used. So you'd need routes starting with extra parameters like this:
{tenanName}...
{segment1}{tenanName}...
{segment1}/{segment2}/{tenanName}...
Depending on the structre of the expected urls you may need to add constraints to ensure that the route is being correctly matched. This can be done passing a fourth parameter to thw MapRoute method. This is an anonymous class, like the deafults parameter, but the specified value for each parameter is a constraint. These constraints, on their simplest forma, are simply strings which will be used as regular expressions (regex).
If the expected URLs are extremely variable, then implement yout own routing class.
You could define the route as
routes.MapRoute(
name: "TennantRoute",
url: "test1/test2/{tenantName}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index"}
);
and your action must take parameter with name tenantName because you may want make some decision based on that ...for example
public ActionResult Index(string tenantName)
{
return View();
}
example : http://localhost:19802/test1/test2/PrerakT/Home/Index
Please make sure you define this path above the default route for following urls to work
http://localhost:19802/test1/test2/PrerakT/
http://localhost:19802/test1/test2/PrerakT/Home/
http://localhost:19802/test1/test2/PrerakT/Home/index
What if I want test1 and test2 to be changeable ...
routes.MapRoute(
name: "TennantRoute",
url: "{test1}/{test2}/{tenantName}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
and
public ActionResult Index(string tenantName, string test1, string test2)
{
return View();
}
as per your update on the question
routes.MapRoute(
name: "TennantRoute1",
url: "test1/test2/{tenantName}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "TennantRoute2",
url: "test1/{tenantName}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "TennantRoute3",
url: "{tenantName}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Aspnet MVC 4 routing challenges

I want customized routing based on Department Name and Product Name. for example /mobiles/nokia-6303
when i am calling products page it's working fine. when i am calling other than product page like Home page by default following controller and action method is executing
defaults: new { controller = "ContentPage", action = "ProductDetail" }
how to avoid this problem?
routes.MapRoute(
name: "ProductDetailsPage",
url: "/{DepartmentName}/{ProductName}",
defaults: new { controller = "ContentPage", action = "ProductDetail" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
Thanks in advance
Rajesh
Your routes are exactly the same. It's impossible to differentiate between /DepartmentName/ProductName and /Controller/Action. You need something else in the URL in order to differentiate between the two things, e.g.:
routes.MapRoute(
name: "ProductDetailsPage",
url: "/Products/{DepartmentName}/{ProductName}",
defaults: new { controller = "ContentPage", action = "ProductDetail" }
);
And then navigate to /products/departmentname/productname
Perhaps a slight modification to Ant P's excellent suggestion would be to place the text in between department name and product name?
routes.MapRoute(
name: "ProductDetailsPage",
url: "/{DepartmentName}/Products/{ProductName}",
defaults: new { controller = "ContentPage", action = "ProductDetail" }
);
Or to have details afterwards:
routes.MapRoute(
name: "ProductDetailsPage",
url: "/{DepartmentName}/{ProductName}/details",
defaults: new { controller = "ContentPage", action = "ProductDetail" }
);
Either of these URL approaches might get past your 'SEO team', since it is including soemwhat relevant information within the URL.
As other answers mention, the routing system can't differentiate between {controller}/{action} and {DepartmentName}/{ProductName}.
You can solve this problem by adding a constraint to a route. If a constraint isn't fulfilled, the route won't match the URL. You will probably need to create a custom implementation of IRouteConstraint. I see two options:
Create a constraint for the {controller}/{action} route, that will contain a list of possible names of controllers, that should be use the default URL pattern
Create a constraint for the {DepartmentName}/{ProductName} route, that will check the database (or some in-memory cache) whether department name and product name match some product
finally i have fixed this issue using routing config itself, please find the below code.
foreach (var d in departmentTranslation)
{
routes.MapRoute(
"ContentPage" + d.Name,
d.Name + "/{ProductName}",
new
{
controller = "ContentPage",
action = "ProductDetails",
id = d.DepartmentId,
ProductName = UrlParameter.Optional
});
}

ApiController GET Action naming

I have an ApiController with multiple GET Actions. The problem is that I wan't to name my actions without "Get" in the start of their names.
For instance, I can have an Action named "GetImage" and it will work just fine.
If I will name it "UpdateImage" it wont call the Action, because it probably want an explicit "Get" in the start of the action name.
I can solve it by defining different route for each action I want to use, but I am sure there must be an easier way achieving it.
I also tried [HttpGet] attribute and unfortunately it didn't do the trick.
My Route Config:
routes.MapHttpRoute(
name: "ImagesApi",
routeTemplate: "api/images/{action}/{id}",
defaults: new { controller = "ImageStorageManager",id = RouteParameter.Optional }
);
and I am accessing it by api/images/GetImage or api/images/UpdateImage
The way I've been creating api controller that aren't just for a single object might help you. I got the approach from John Papa's SPA talk on PluralSight (I highly recommend that for learning single page applications). He also walks through this in one of the modules.
It has 2 parts.
Part 1, setting up the routes to do the 2 normal scenarios and then added a 3rd for what i want:
// ex: api/persons
routes.MapHttpRoute(
name: ControllerOnly,
routeTemplate: "api/{controller}"
);// ex: api/sessionbriefs
// ex: api/persons/1
routes.MapHttpRoute(
name: ControllerAndId,
routeTemplate: "api/{controller}/{id}",
defaults: null, //defaults: new { id = RouteParameter.Optional } //,
constraints: new { id = #"^\d+$" } // id must be all digits
);
// ex: api/lookups/all
// ex: api/lookups/rooms
routes.MapHttpRoute(
name: ControllerAction,
routeTemplate: "api/{controller}/{action}"
);
Part 2, in the lookups controler (in John Papa's case), add an ActionName attribute to the methods:
// GET: api/lookups/rooms
[ActionName("rooms")]
public IEnumerable<Room> GetRooms()
{
return Uow.Rooms.GetAll().OrderBy(r => r.Name);
}
// GET: api/lookups/timeslots
[ActionName("timeslots")]
public IEnumerable<TimeSlot> GetTimeSlots()
{
return Uow.TimeSlots.GetAll().OrderBy(ts => ts.Start);
}
Decorate your action with [HttpGet]. See http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api for details on why, and how ApiController routing works.

Resources