I'm trying to set up some routing in one of my MVC area's.
I have a controller named AgentGroups. I am trying to achieve the following:
Remove Index from the URL
Supply a parameter to the Index action
Allow all other actions to have their name in the URL and provide an optional parameter to them
So for example I'd like the following to work
/s/agentgroups < (Index action)
/s/agentgroups/1 < (Index action)
/s/agentgroups/someotheraction
/s/agentgroups/someotheraction/1
I currently have this in my RegisterArea method:
// s/agentgroups/action
context.MapRoute(
"Suppliers_actions",
"s/{controller}/{action}/{agentgroupid}",
new { controller = "AgentGroups", agentgroupid = UrlParameter.Optional },
new { action = "^(?!Index$).*$" }
);
// s/agentgroups/
context.MapRoute(
"Suppliers_index",
"s/agentgroups/{agentgroupid}",
new { controller = "AgentGroups", action = "Index", agentgroupid = UrlParameter.Optional }
);
This works for 3 of the 4 URL examples I gave, the one that doesn't work correctly is:
/s/agentgroups/1 < (Index action)
I'm pretty sure it thinks the 1 parameter is an action name and therefore it doesn't work..? It does work however, if, I pass the parameter like a regular query string ie: ?agentgroupid=1, but I'd like to avoid this if possible.
How can I change my routes to achieve the desired behavior?
You can reorder the area routes as Suppliers_index is more specific (only intended for the index action) than Suppliers_actions. Then you will need to add a constraint for the agentgroupid parameter in Suppliers_index.
As the parameter is optional and has to match an integer, we can use the regex \d*, but for more complicated patterns you might need to create your own route constraint as in this answer.
So your area routes may look like this: (namespaces will be different or even not needed in your case):
context.MapRoute(
"Suppliers_index",
"s/agentgroups/{agentgroupid}",
defaults: new { controller = "AgentGroups", action = "Index", agentgroupid = UrlParameter.Optional },
constraints: new { agentgroupid = #"\d*" },
namespaces: new[] { "WebApplication6.Areas.AgentGroups.Controllers" }
);
context.MapRoute(
"Suppliers_actions",
"s/{controller}/{action}/{agentgroupid}",
defaults: new { controller = "AgentGroups", agentgroupid = UrlParameter.Optional },
namespaces: new[] { "WebApplication6.Areas.AgentGroups.Controllers" }
);
Related
I am not an advanced developer. I just started working with MVC. Few days back I had seen an example of ASP.NET MVC routing code where two controller or action name has been referenced.
routes.MapRoute(
name: "test",
url: "{controller}/{action}/{page}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
defaults: new { action = "Index" }
);
Just started working with ASP.NET MVC, so I am curious to know what is the objective to mention controller or action name twice in routing code?
In above example there are two defaults.... When and why is it required?
Just requesting some one to explain the same with a nice example.
Thanks in advance.
The example from the link you posted:
context.MapRoute(
"UserHome",
"User/{id}",
new { action = "Index", controller = "Home", area = "User", id = 0,
httproute = true },
new { controller = #"Home", id = #"\d+" }
);
is a bit confusing, because it is using anonymous types without named arguments. If you add named arguments to that example, it would look like this:
context.MapRoute(
name: "UserHome",
url: "User/{id}",
defaults: new { action = "Index", controller = "Home", area = "User", id = 0,
httproute = true },
constraints: new { controller = #"Home", id = #"\d+" }
);
This makes it more clear what is going on here. The example is not showing two sets of defaults, instead there are constraints that limit the range of values that will match.
It basically says:
controller = #"Home" - When generating the URL, only match this route if controller route value is Home.
id = #"\d+" - Match if the id route value contains only numerals.
Actually, both of the above constraints run for both incoming URL matching and URL generation, but controller = #"Home" will always be true when matching the incoming URL because the default value is the only thing that can set it (and the default value is also Home).
I have an MVC website which used to use URLs in the standard format of: Controller/Action.
Recently, I have changed it to: Site/Controller/Action.
The problem is, there are several links to my site out there which follow the old format, and I want to redirect them accordingly.
for example: mydomain.com/Home/CustomerSearch now should go to mydomain.com/Online/Home/CustomerSearch
whereas: mydomain.com/AffiliatesHome/CustomerSearch now should go to mydomain.com/Affiliate/AffiliatesHome/CustomerSearch
How can I get it to handle the redirecting by putting in the extra routing, depending on the link they came in by?
The current routing I am using is:
routes.MapRoute(
"Default", // Route name
"{site}/{controller}/{action}/{id}",
new {site="IS", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Since I do not really see an schema in your old to new URL mapping I would suggest to add routes that match the old Controller/Action Schema and map them to the new Site/Controller/Action route schema.
So you could add the following routes
routes.MapRoute(
"LegacyHome",
"Home/{action}/{id}",
new { site="Online", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"LegacyAffiliates",
"AffiliatesHome/{action}/{id}",
new { site="Affiliate", controller = "AffiliatesHome", action = "Index", id = UrlParameter.Optional }
);
From an SEO standpoint this is not ideal because you have different URLs for the same page. A permanent redirect via status code 301 and the new URL passed in the location is better suited.
You could build a redirect controller and use the legacy routes to map legacy URLs to the redirect controller somehow like this
routes.MapRoute(
"LegacyHome",
"Home/{newAction}/{id}",
new { controller = "Redirect", action = "Redirect", newSite = "Online", newController="Home", newAction = "Index", id = UrlParameter.Optional }
);
Code of the redirect controller
public class RedirectController : Controller
{
public ActionResult Redirect(string newSite, string newController, string newAction)
{
var routeValues = new RouteValueDictionary(
new
{
site = newSite,
controller = newController,
action = newAction
});
if (RouteData.Values["id"] != null)
{
routeValues.Add("id", RouteData.Values["id"]);
}
return RedirectToRoutePermanent(routeValues);
}
}
I have a url
http://www.roadkillwiki.org/Page/Index/documentation
which I want to turn into
http://www.roadkillwiki.org/Page/documentation
That could also be something like http://www.roadkillwiki.org/Page/my-url-with-spaces - the parameter is a string. The route setup I've tried is:
routes.MapRoute(
"ControllerDefault",
"{controller}/{id}",
new { controller = "Page", action = "Index", id = UrlParameter.Optional }
);
However this is interfering with the default "id" route that MVC projects come with. Is there any way of achieving this?
You don't need to lose the default route. The key to avoiding your routes interfere with each other is to order them so the more specific rules precede the less specific ones. For example:
// Your specialized route
routes.MapRoute(
"Page",
"Page/{slug}",
new { controller = "Page", action = "Index" }
);
// Default MVC route (fallback)
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Then your PageController would look like this:
using System.Web.Mvc;
public class PageController : Controller
{
public string Index(string slug)
{
// find page by slug
}
}
That said, I would strongly advice you to do this instead:
// Your specialized route
routes.MapRoute(
"Page",
"Page/{id}/{slug}",
new { controller = "Page", action = "Index", slug = UrlParameter.Optional }
);
// MVC's default route (fallback)
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
And your PageController:
using System.Web.Mvc;
public class PageController : Controller
{
public string Index(int id)
{
// find page by ID
}
}
By including the page ID either at the beginning of your URL (like StackOverflow does) or at the end, you can then just ignore the slug, and instead retrieve your pages by ID. This will save you a ton of headaches if your users change the page name. I have gone through this and it's painful; you basically have to keep a record of all names your pages have had in the past, just so your visitors/search engines don't get a 404 every time a page is renamed.
Hope this helps.
If you don't need a default route that came with project template you can set up one like this:
routes.MapRoute(
"ControllerDefault",
"{controller}/{pagename}",
new { controller = "Page", action = "Index" }
);
And than in your controller you would have an action:
public ActionResult Index(string pagename)
{
//do something
}
I'm looking to make a really simple route in my ASP.NET MVC 2.0 website. I've been googling for help but all the examples I can find are for really complex routing.
Basically I want all the pages in my Home Controller to resolve after the domain as opposed to /Home/
For example I want http://www.MyWebsite.com/Home/LandingPage/
To become http://www.MyWebsite.com/LandingPage/
But only for the Home controller, I want the rest of my controllers to function as normal.
I thought about creating a controller for each and just using an index, but we need lots of landing pages for our marketing like this and it would quickly make the site loaded with controllers for a single page each, which is less than ideal.
One way to do this would be to have a separate route for each landing page. Another way would be to have a single route with a constraint that matches each landing page (and nothing else).
routes.MapRoute(
"LandingPage1"
"landingpage1/{id}",
new { controller = "home", action = "landingpage", id = UrlParameter.Optional } );
routes.MapRoute(
"LandingPage2"
"landingpage2/{id}",
new { controller = "home", action = "landingpage2", id = UrlParameter.Optional } );
Note that you could probably do this with a bit of reflection as well (untested).
foreach (var method on typeof(HomeController).GetMethods())
{
if (method.ReturnType.IsInstanceOf(typeof(ActionResult)))
{
routes.MapRoute(
method.Name,
method.Name + "/{id}",
new { controller = "home", action = method.Name, id = UrlParameter.Optional } );
}
}
The RouteConstraint solution would be similar except that you'd have a single route with a custom constraint that evaluated whether the appropriate route value matched one of the methods on the HomeController and, if so, replaced the controller and action with "home" and the matched value.
routes.MapRoute(
"LandingPage",
"{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional },
new LandingPageRouteConstraint()
);
public LandingPageRouteContstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
// simplistic, you'd also likely need to check that it has the correct return
// type, ...
return typeof(HomeController).GetMethod( values.Values["action"] ) != null;
}
}
Note that the route per page mechanism, even if you use reflection, is done only once. From then on you do a simple look up each time. The RouteConstraint mechanism will use reflection each time to see if the route matches (unless it caches the results, which I don't think it does).
I think you are missing the default route.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
So, when you type www.mywebsite.com, the controller, action, and id parameters would have the following values:
controller : Home
action: Index
id : ""
I've got two (so far) different types of routes in my ASP.NET MVC app, one is: {controller}/{action}/{id} and the other {controller}/{action}/{title}
Currently I need to define the routes like this:
routes.MapRoute (
"Default_Title_Slug", // Route name
"product/details/{title}", // URL with parameters
new { controller = "product", action = "details", title = "" } // Parameter defaults
);
routes.MapRoute (
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "site", action = "index", id = "" } // Parameter defaults
);
Notice that the first one I've had to tie down to the product controller, this seems to be the only way I can get it work...otherwise the other routes end up looking like this:
/controller/action?id=number
Now I need to add another MapRoute call targeting another controller with the {title} segment...I don't want to create a new route for each specific entry I come up with in the future...is there a generic route I can create to map the /controller/action/title that'll play nicely with the /controller/action/id route?
Thanks,
Kieron
You can do that with a route-constraint, such as regex - a very similar example is here. Something like:
routes.MapRoute (
"Default",
"{controller}/{action}/{id}",
new { controller = "site", action = "index", id = "" },
new { id = #"\d+" }
);
routes.MapRoute (
"Default_Title_Slug",
"{controller}/{action}/{title}",
new { controller = "product", action = "details", title = "" }
);