MVC Additional Custom routes - asp.net-mvc

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 }
);

Related

MVC 4 - Remove /Home/ from address bar and retain folder name for others

I am using MVC 4 and need to remove /Home/ folder from address bar...
Eg:
http://localhost:61700/Home/AboutUs
Need to be changed as...
http://localhost:61700/AboutUs
I did that by changing the default controller in "RouteConfig.cs"
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
//url: "{controller}/{action}/{id}",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The above code is working as expected. I do have another folders as
brand, admin etc... here I want to show the url as
http://localhost:61700/brand/productInfo ... But I am getting server
error here as Server Error in '/' Application.
Can somebody suggest me, where am I doing wrong?
Screenshots here for more info:
This is your current RouteConfig.cs configuration:
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
You're telling Asp.net, when a request arrives, assume the first parameter as the action and the second parameter as the id. Right now you're not telling Asp.net to parse any parameter as the controller. Because of this it uses the default value (given as the third parameter of the MapRoute method) which is in this case Home.
In that case when parsing the request http://localhost:61700/AboutUs the values end up being:
controller: Home (it uses the default controller)
action: AboutUs (from the first parameter)
id: null (this doesn't matter right now)
When parsing the request http://localhost:61700/brand/productInfo the values end up being:
controller: Home (it uses the default controller because you haven't specified where to get the controller name from)
action: Brand (from the first parameter)
id: "productInfo"
The error you're getting is because there isn't a Brand action method in HomeController.cs with a parameter of type string named id.
Asp.net processes incoming requests by trying to match with the routes configured and it uses the first route that matches.
There are several ways to achieve what you want, which include but are not limited to:
Manually mapping every action in your HomeController.cs (choosing this method will depend on the amount of actions in your HomeController). This would look like:
routes.MapRoute(
name: "AboutUs",
url: "AboutUs",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "ContactUs",
url: "ContactUs",
defaults: new {controller = "Home", action = "ContactUs" }
);
// etc...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Note how the default route is the last one, this is important because it is less specific than the others and if put before would match the request and want to look for an AboutUsController.
You could use route constraints. This would look like:
route.MapRoute(
name: "HomeControllerRoutes",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { action = "AboutUs|ContactUs|etc..." } //Here you would put all your action methods from home controller that you want to accces as /{action}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If you want to read more about route constraints, I found this article that explains that the constrains parameter can receive a regular expression (I suggest you modify the regular expression above to make it case insensitive) or an IRouteConstraint.
Update:
I just read your comment about having 160+ actions in your HomeController that would make your regular expression in my second suggestion quite long. In that case the other options you have could be:
Using a regular expression that rejects all other controller names, but that would violate the open/closed principle (OCP) and every time you add another controller you would have to add it to the regular expression.
Create the regular expression from the metadata of you HomeController class. This would look like
string.Join("|", typeof(HomeController).GetMethods().Select(info => info.Name))
Or you could take a look at IRouteConstraint to see if you could figure out a more elegant solution.
I have no experience with IRouteConstraint
Add this in your route.config / glibal.asax and don't change your default routes. Add following above it.
routes.MapRoute(
name: "About",
url: "AboutUs",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have 160+ views in the home controller
You don't mention how many views you have in the other controllers, nor how complicated they need to be.
Rather than keep the default controller/action and add routes for every view in home, you can add a route for each controller and then have your default route without a controller path.
While this means you do need a route for every controller, it's better than one for every view.
routes.MapRoute(
"AdminRoute",
"Admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"BrandRoute",
"Brand/{action}/{id}",
new { controller = "Brand", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"HomeRoute",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"DefaultRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
(afaicr you don't need the default route as all your views would be covered by the other 3 routes)
Note the path for 'HomeRoute' doesn't have a controller part.
As long as they are in this order any url with /Admin/ or /Brand/ will be picked up first.

Need to simplify URLs via ASP.NET MVC routing pattern in RouteConfig.cs

I'm trying to simplify the URLs in an application, and am struggling with the route registration. This is an administrative tool. In many cases we have a List view (List.cshtml) and a Details view (Index.cshtml). The pattern that I would like to implement for these URLs are as follows:
http://mysite/person/list (This view shows a list of people)
http://mysite/person/123 (View will show details for a person with an ID of 123)
Formatting the URls that way is more of a nice-to-have feature for polishing the site. I tried several routes, and in RouteConfig here are some of the more recent routes that I've tried.
routes.MapRoute(
name: "Person",
url: "Person/{action}/{id}",
defaults: new { controller = "Person", action = "Index" }
);
routes.MapRoute(
name: "PersonID",
url: "Person/{id}",
defaults: new { controller = "Person", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Dashboard", action = "Index", id = UrlParameter.Optional }
);
Now if I remove those custom routes, and just run with the default route, the "mysite/person/list" view works just fine. But when I select a person, the URL ends up being "mysite/person/index/[id]" instead of "mysite/person/[id]". And if I manually remove "index" from that URL and make the path "mysite/person/[id]", a "resource cannot be found" message appears.
In that second route shown, I figured that giving the route a default action would route to the Index page and treat the ID in the URL as an ID, rather than as an action. With the current configuration shown above, if I navigate to the Person/List view I'm routed to the Person/Index view.
There are a few other actions associated with that controller (like JsonResults), which I'll need to handle as well.
What is the correct way to write the routes to support the URLs that I've indicated above? Also, can you recommend a resource that shows multiple examples of route-to-URL comparisons? Thanks for your help!
=== Edit 4/9/2015 at 10:21 AM ===
Based on the answer provided by #stephen-reindl, this is the solution that I implemented. Since we have multiple interfaces with a "Detail" view, I chose a default action of "Detail". This route recognizes a GUID.
// Support URL format of http://mysite/{controller}/{guid}
// Example: http://mysite/person/474f4357-39b2-45a2-b02b-6be04b2302fe
routes.MapRoute(
name: "DetailViewWithGuid",
url: "{controller}/{id}",
constraints: new { id = #"\b[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\b" },
defaults: new { action = "Detail", id = UrlParameter.Optional }
);
You can add a constraint that a specific route is only taken into consideration if the constraint is fulfilled:
routes.MapRoute(
name: "PersonID",
url: "Person/{id}",
constraints: new { id = #"\d+" },
defaults: new { controller = "Person", action = "Index", id = UrlParameter.Optional }
);
In this case this route is only taken if id is a number.

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 }
);

Url without controllerName

I want to hide the controller name from URL. I am using the below code.
routes.MapRoute(
name: "Customized",
url: "{action}",
defaults: new { controller = "Home", action = "Default", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Default", id = UrlParameter.Optional }
);
This works perfecly.
but only works when i type
http :// domain/ActionName
otherwise, I still able to see
http :// domainname/controller/action
Is there any way to http ://domain/ActionName even if user tried to type
http :// domainname/controller/action
If you have only one Controller (Home) I guess you can have only one route definition ( assuming all your actions share the same parameter pattern) :
routes.MapRoute(
name: "Customized",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Default", id = UrlParameter.Optional }
);
You could potentially do something hacky like check the actual path once you got into the action and if it's not the right path, then redirect to the right path, but that technically causes two requests and could hammer your server if done frequently enough.
MVC Routing employs short-circuit logic to match the request path with an appropriate action. It doesn't care that there may be a "better" route that the user should have used, it only cares that it found a match.

MVC Routing Html.ActionLink creates URLs with ?id=1 instead of /id

When I use Html.ActionLink() the URL created is not in the desired format:
Html.ActionLink(Model.ProductCode, "Update", new { id = Model.ProductId })
Makes this URL
/Update?id=1
When I want to have this URL:
/Update/1
What routing options create the 2nd URL? This is our preferred URL style.
Both URLs work and the correct page is displayed - however we want to only use /id
In Global.asax the MVC default route handles both URLs
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }); // Parameter defaults
I can replicate the issue by having a route about my default route that still matches the general pattern. Example:
routes.MapRoute(
"Default2", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index"} // Parameter defaults
);
When places above my default route, I get the ?id=1 in my URL. Can you confirm that this ActionLink is not matching any routes above the route that you are expecting it to match?
EDIT: The below does not impact the URL
However, it could still be advantageous to use the UrlParameter.Optional in other scenarios. Leaving for prosperity unless mob rule says otherwise.
new UrlParameter.Optional value. If you set the default value for a
URL parameter to this special value, MVC makes sure to remove that key
from the route value dictionary so that it doesn’t exist.
I think you need to adjust your route slightly. Change id = "" to id = UrlParameter.Optional
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
This is what we use for the default route and the behavior that you are looking for is how our applications behave.
See This question/answer.
Which version of MVC are you using? If you're in MVC3, you'll need to add a fourth parameter to your call to Html.ActionLink(), passing in a null.
I've just stumbled upon this and decided to answer. It turned out that both Url.Action() and Html.ActionLink() use the first route in the route collection to format the resulted URL. So, the first mapped route in the RegisterRoutes() shoild be:
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
instead of "{controller}/{action}/{id}". The route name (i.e. "Default") does not matter, only the order does matter
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Resources