Using routes.MapPath to accommodate missing URL parameter - asp.net-mvc

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

Related

How to map same routes for two controller?

I have two controller in my MVC application. One is Home controller and other is User controller. I am using following RouteConfig settings.
routes.MapRoute(
"actiononly",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I want abc.com/Blog
abc.com/Login
Instead of abc.com/Home/Blog
abc.com/User/Login.
Above configuration works fine with abc.com/Blog but it is not working with abc.com/Login.
How to remove controller name from the link for both controllers?
Also how can I only show abc.com when website launches instead of abc.com/index? I am using following code in my webpage to access the particular page.
#Html.ActionLink("Home", "Blog", "Home")
#Html.ActionLink("Login", "Login", "User")
Your default route should automatically cater for wanting to nav to abc.com without requiring the index part of the URL
You need to ensure that your main route is specified as the default:
context.MapRoute(
"Site_Default",
"{controller}/{action}/{*id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If you want to map short routes you can do exactly what you've done above. I use this helper function in my own project to map short routes:
void ShortRoute(string action, string controller)
{
ShortRoute(action, controller, action);
}
void ShortRoute(string action, string controller, string route)
{
_context.MapRoute(route, route, new { action, controller });
}
With usage:
ShortRoute("About", "Home");
Which allows me to navigate to mywebsite.com/about instead of mywebsite.com/home/about
If it's not working for certain URLs it may be that the route handler is matching a different route - I believe it does depend on the order you register them
There's a good route debugging plugin you can use
https://www.nuget.org/packages/routedebugger/
It gives you a summary of all routes and which ones matched the current URL - very useful
Without bringing in additional packages, you simply need to add an additional route. To create the new route, you first must define what you want your URL to be. In this case, you have said you want to be able to go to /Login which is on the user controller. Ok - let's create a new route. This route should be located ABOVE your default route.
routes.MapRoute(
"UserLogin",
"Login/{id}",
new { controller = "User", action="Login", id = UrlParameter.Optional }
);
The first parameter is simply the route name. The second parameter is the format of the URL that I want this route to match. Given we know what action we want to match to this route, we don't need the {action} or {controller} catchall placeholders that are in the default route. Also note that we can declare what controller this route will hit without having to specify the controller in the URL.
Last note, you don't have to have the {id} be part of the route if you will never be passing an ID parameter to that function. If that is the case, then you can safely remove any references to id in the UserLogin route.
As I re-read your question, you should be able to do this for some of your other examples as well. Let's take the /About URL and demonstrate the removal of the {id} parameter.
routes.MapRoute(
"AboutUsPage",
"About",
new { controller = "Home", action="About"}
);
This is very simple. You just need to create a route for each of your expected URLs.
Keep in mind that if you don't pass the controller or action as a URL placeholder, you will need to do so manually by providing them as default values.
routes.MapRoute(
"Blog",
"Blog/{id}",
new { controller = "Home", action = "Blog", id = UrlParameter.Optional }
);
routes.MapRoute(
"Login",
"Login/{id}",
new { controller = "User", action = "Login", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

MVC4 Catch all requests to a controller action

My app needs to have the ability to let users navigate to a page using the foo.com/pubid convention where pubid is a randomly generated string such as "wgwrfwrcwrf".
My Home controller has an Index ActionResult defined:
public ActionResult Index(string id)
{
//do stuff
}
When I navigate to this action on my site without specifying an ID everything is fine. I return the view associated to home. This is what bit.ly does. I am trying to translate that ID inside my app.
However if I request a URL such as foo.com/wgwrfwrcwrf I get a 404. I need to be able to catch this string to send it to the right view. Does this require a custom route?
routes.MapRoute(
"id", // Route name
"{id}", // id route
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
You should write a catch-all route like this...
routes.MapRoute("catchall", "{*id}",
new {
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
Enjoy :)

How to add route parameter before MVC choses a route

I am trying to add support for different languages to existing MVC 3 application. So far my links were
oldUrl -> myapp.com/Item/Details/5/some-title
And there are links to the website from different places on the web. But now I want to change the URL so the language is included:
newUrl -> kmyapp.com/en/Item/Details/5/some-title
However I want the oldUrl links to be valid. And the question is how do I do this... The easiest way would be to add another route, but I already have too many of them and it is getting kind of ugly, and when I create urls from a page that doesn't have the language the links also don't have the language so I have to do something else then.
Another way would be to override something that stands just before MVC tries to map the request to a specific route, but has already parsed the request url and filled the routeValues dictionary. So I can just check for the language and if it is not included in the route parameters I can add it on my own and call the implementation of the parent class to continue. Is that possible? What should I look at - UrlRoutingModule, MvcHttpHandler or something else? What can I override and how to tell MVC to use my version?
Any other ideas would be also appreciated!
There are of course many solutions. I'm going to show you two:
one that you control in your application
one that you control outside of your application (on IIS)
Solution one - Asp.net MVC routing
Provide routing that covers old and new routing:
routes.MapRoute(
"New",
"{lang}/{controller}/{action}/{id}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional },
new { lang = "en|de|it|es|fr" }
);
routes.MapRoute(
"NewEx",
"{lang}/{controller}/{action}/{id}/{title}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional, title = UrlParameter.Optional },
new { lang = "en|de|it|es|fr" }
);
routes.MapRoute(
"Old",
"{controller}/{action}/{id}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"OldEx",
"{controller}/{action}/{id}/{title}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional, title = UrlParameter.Optional }
);
As you can see I've provided language default for the old routes as well since it's not present in the URL. Whether you want that or not is your own decision but this kind of routing makes it possible to not duplicate your controller actions. In any case you'd have to define a default language which can be provided this way.
There's a bigger question whether you still want to support old URLs or you'd rather redirect them (HTTP Redirect Permanent Status 301).
Permanent redirection
Change old routes to:
routes.MapRoute(
"Old",
"{controllerOld}/{actionOld}/{idOld}",
new { controller = "Redirect", action = "Permanent", id = UrlParameter.Optional }
);
routes.MapRoute(
"OldEx",
"{controllerOld}/{actionOld}/{idOld}/{titleOld}",
new { controller = "Redirect", action = "Permanent", id = UrlParameter.Optional, title = UrlParameter.Optional }
);
Then write a controller class that does redirection:
public class RedirectController : Controller
{
public ActionResult Permanent(string controllerOld, string actionOld, string idOld, string titleOld)
{
return RedirectToRoutePermanent(new {
lang = "en",
controller = controllerOld,
action = actionOld,
id = idOld,
title = titleOld
});
}
}
Solution two - IIS URL rewriting module
This solution relies on IIS URL Rewriting module, where you could rewrite any requests without language selection to your preferred default.
I'm not going to wrote how URL Rewriting works here, because there're plenty of web resources with detailed info about that.
Another way would be to override something that stands just before MVC tries to map the request to a specific route, but has already parsed the request url and filled the routeValues dictionary. So I can just check for the language and if it is not included in the route parameters I can add it on my own and call the implementation of the parent class to continue. Is that possible?
Yes It is possible.
You can Override GetRouteData in RouteBase which will have URL details in it.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
}
and in global.aspx add below code.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new MyUrlRoute()); // Add before your default Routes
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
For more detailed implementation - refer blog
url-manipulation-implementing-routebase.html

asp mvc RedirectToRoute adding current action to the redirect

I have a controller that has an action called RedirectLogin, it basically sets a TempData var to redirect back to when the login is finished.
After this, it simply calls:
return RedirectToRoute("Login");
However, the when it redirects, it goes to /login/RedirectLogin - it is tacking on the calling action's name for some reason. I think it should just be returning the defaults, not adding actions, especially actions not part of the destination controller.
If I call:
return RedirectToRoute("Login", new { action = "Index" });
Then it properly makes the path /login, but again, shouldnt it be doing that by default, without the extra bit?
I have a slightly non-standard routing setup. Basically I am creating virtual subfolders for my clients, so www.domain.com/clienturl but I still want www.domain.com/join or www.domain.com/about to redirect to non-client data. I do this by adding the name of the controller directly in the path, and add it before the more generic one called Landing. Could this be the problem, or is there a better way to do it :)
// Root paths
routes.MapRoute("About", "about", new { controller = "Home", action = "About" });
routes.MapRoute("Info", "info", new { controller = "Home", action = "Info" });
routes.MapRoute("Privacy", "privacy", new { controller = "Home", action = "Privacy" });
routes.MapRoute("Terms", "terms", new { controller = "Home", action = "Terms" });
// Root controllers
routes.MapRoute("Join", "join/{action}/{id}", new { controller = "join", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("Login", "login/{action}/{id}", new { controller = "login", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("Account", "account/{action}/{id}", new { controller = "account", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("Goodbye", "goodbye/{action}/{id}", new { controller = "goodbye", action = "Index", id = UrlParameter.Optional });
// Organization
routes.MapRoute("Landing", "{organization}/{controller}/{action}/{id}", new { controller = "Landing", action = "Index", id = UrlParameter.Optional });
// Default
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
It's reusing the data from the current request. since you have the optional parameter 'action' it is taking the action parameter from your current request who's action is RedirectLogin and it attaches that to the url. I'd make a specific route entry without the optional parameters to your login page or redirect to action and not used named routes. There is a page in the book Pro ASP.NET MVC 2 that explains what's happening in more detail.
Alternatives you may wish to try are as follows:
RedirectToAction(String, String, RouteValueDictionary);
RedirectToAction(String, RouteValueDictionary);
Redirect("~/login"); // Note, this works but if you move around the urls for your routes you'll need to fix it everytime... not recommended
The MSDN article here shows all of the available Methods on a Controller: http://msdn.microsoft.com/en-us/library/system.web.mvc.controller_methods%28v=VS.98%29.aspx
Don't use the RedirectToActionPermanent or RedirectToRoutePermanent for this purpose though...

Setting up Index as the default route for a controller

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
}

Resources