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 : ""
Related
I am writing an MVC 4 application which has controllers and many lengthy action names, and they create a lengthy URL which is not easy to remember.
Any idea of how we can implement short URLs (Preferably, without affecting routing) in MVC 4? May be using custom attributes?
Actually, you can specify your routes in RouteConfig.cs. Here is the code from my application:
routes.MapRoute("SignUp", "signup", new { controller = "SignUp", action = "Index" });
routes.MapRoute("SignOut", "signout", new { controller = "Login", action = "SignOut" });
routes.MapRoute("Login", "login", new { controller = "Login", action = "Login" });
Second parameter here (signup, signout, login) are the short urls. If you want something more, you can specify your routes like this:
routes.MapRoute("SetNewPassword", "set-new-password/{userId}/{passwordResetKey}", new { controller = "SetNewPassword", action = "Index" });
The url here is something like /set-new-password/123/blabla
New routes don't affect default routes. Just make sure you have this default line at the end:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Btw, you can use handy route debugger from Phil Haack. It's really easy to set up and use (you just need to comment/uncomment one single line in your Global.asax.cs). It's absolutely must have tool for every Asp.Net MVC developer.
May be a "catchall" routing will help you.
You could set up a catch-all type route, to direct all /something requests to a specific action and controller, something like:
routes.MapRoute(
"ShortUrls",
"{name}",
new {controller = "ShortUrl", action = "Index", name = UrlParameter.Optional}
);
(depending on how the rest of your routing is set up, you probably don't want to do it exactly like this as it will likely cause you some serious routing headaches - but this works here for the sake of simplicity)
Then just have your action redirect to the desired URL, based on the specified value:
public class ShortUrlController : Controller
{
//
// GET: /ShortUrl/
public ActionResult Index(string name)
{
var urls = new Dictionary<string, string>();
urls.Add("disney", "http://..your lengthy url");
urls.Add("scuba", "http://another..lengthy url");
return Redirect(urls[name]);
}
}
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
I'm trying to set up my MVC project to have URLs so that I can go to:
/Groups/
/Groups/Register
/Groups/Whatever
But in my controller, I can also flag some actions as admin only, so that they are accessed at:
/Admin/Groups/Delete/{id}
I would like to keep one GroupController, and have actions so that:
public class GroupController : Controller
{
public ActionResult Index(){
return View();
}
[AdminAction]
public ActionResult Delete(int id){
...
return View();
}
}
Allows:
/Groups is a valid URL.
/Admin/Groups is a valid URL (but would call some other action besides Index - maybe)
/Admin/Groups/Delete/{id} is a valid URL (post only, whatever)
/Groups/Delete is an INVALID url.
I realize this is probably a pretty broad question, but I'm new to MVC and I'm not really sure where to start looking, so if you could just point me in the right direction that would be hugely appreciated.
As we discussed in the comments below, while it is possible to use my original answer below to achieve the routing solution you requested, a better solution is to use Areas, establish an Admin area, and create controllers in your Admin area to handle the administrative tasks for different objects, such as Group, User, etc. This allows you to set up restricted administrative functions more easily, and is both a better design and a better security model.
ORIGINAL ANSWER
What you want can be accomplished by using the following routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Admin", // Route name
"admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
However, as Akos said in the comments, it is a much better design to separate the administrative functions into a different controller. While this is possible, I would recommend against using this design.
UPDATE
It is possible to use a RouteConstraint on your Default route to make it fail if Admin actions are requested. The Default route would look like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, // Parameter defaults
new { action = IsNotAdminAction() } // route constraint
);
The RouteConstraint would look like this:
public class IsNotAdminAction : IRouteConstraint
{
private string adminActions = "create~delete~edit";
public IsNotAdminAction()
{ }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return false if there is a match
return !adminActions.Contains(values[parameterName].ToString().ToLowerInvariant());
}
}
I want to remove the controller name from my URL (for one specific controller). For example:
http://mydomain.com/MyController/MyAction
I would want this URL to be changed to:
http://mydomain.com/MyAction
How would I go about doing this in MVC? I am using MVC2 if that helps me in anyway.
You should map new route in the global.asax (add it before the default one), for example:
routes.MapRoute("SpecificRoute", "{action}/{id}", new {controller = "MyController", action = "Index", id = UrlParameter.Optional});
// default route
routes.MapRoute("Default", "{controller}/{action}/{id}", new {controller = "Home", action = "Index", id = UrlParameter.Optional} );
To update this for 2016/17/18 - the best way to do this is to use Attribute Routing.
The problem with doing this in RouteConfig.cs is that the old route will also still work - so you'll have both
http://example.com/MyController/MyAction
AND
http://example.com/MyAction
Having multiple routes to the same page is bad for SEO - can cause path issues, and create zombie pages and errors throughout your app.
With attribute routing you avoid these problems and it's far easier to see what routes where. All you have to do is add this to RouteConfig.cs (probably at the top before other routes may match):
routes.MapMvcAttributeRoutes();
Then add the Route Attribute to each action with the route name, eg
[Route("MyAction")]
public ActionResult MyAction()
{
...
}
Here is the steps for remove controller name from HomeController
Step 1:
Create the route constraint.
public class RootRouteConstraint<T> : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var rootMethodNames = typeof(T).GetMethods().Select(x => x.Name.ToLower());
return rootMethodNames.Contains(values["action"].ToString().ToLower());
}
}
Step 2:
Add a new route mapping above your default mapping that uses the route constraint that we just created. The generic parameter should be the controller class you plan to use as your “Root” controller.
routes.MapRoute(
"Root",
"{action}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { isMethodInHomeController = new RootRouteConstraint<HomeController>() }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Now you should be able to access your home controller methods like so:
example.com/about,
example.com/contact
This will only affects HomeController. All other Controllers will have the default routing functionality.
If you want it to apply to all urls/actions in the controller (https://example.com/action), you could just set the controller Route to empty above ApiController. If this controller going to be your starting controller, you'll also want to remove every launchUrl line in launchSettings.json.
[Route("")]
[ApiController]
You'll have to modify the default routes for MVC. There is a detailed explanation at ScottGu's blog:
http://weblogs.asp.net/scottgu/archive/2007/12/03/asp-net-mvc-framework-part-2-url-routing.aspx
The method you should change is Application_Start. Something like the following might help:
RouteTable.Routes.Add(new Route(
Url="MyAction"
Defaults = { Controller = "MyController", action = "MyAction" },
RouteHandler = typeof(MvcRouteHandler)
}
The ordering of the routes is significant. It will stop on the first match. Thus the default one should be the last.
routes.MapRoute("SpecificRoute", "MyController/{action}/{id}",
new {controller = "MyController", action = "Index",
id = UrlParameter.Optional});
// default route
routes.MapRoute("Default", "{controller}/{action}/{id}",
new {controller = "Home", action = "Index",
id = UrlParameter.Optional} );
I have an application here with a mix of webform and mvc. I specify the routing as below
routes.Add("AspxRoute", new Route("Upload/New", new WebFormRouteHandler<Page>("~/Uploads.aspx")));
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
So that virtual path to "Upload/New" actually maps to an aspx webform page.
But my problem is that Html.ActionLink("Test", "Controller", "Action") now renders
/Upload/New?Controller=Controller&Action=Action
Having looked at the MVC source code, I understand that it is because ActionLink calls to RouteCollection.GetVirtualPath(requestContext, routeName, mergedRouteValues), where routeName is left to null. And somehow this defaults to use the AspxRoute route to construct the url. I tried to added another route before "AspxRoute", but it seems it always defaults to the non-mvc routehandler one.
How does RouteCollection.GetVirtualPath behave when routeName is null? And why is it behaving this way for my case?
How do I construct a correct url? Do I need to write a new Htmlhelper extension?
Cheers
An alternative option would be to add a custom constraint to your WebFormRoute(s). For example, you could create an implementation of IRouteConstraint to match RouteDirection.IncomingRequest, then use it to ensure the route is ignored by Server-Generated routes (such as ActionLink) but still used by client-generated requests. Something like:
public class IncomingOnlyRouteConstraint: IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
return true;
}
return false;
}
}
And then add the constraint to your route:
routes.Add("AspxRoute", new Route("Upload/New", null,
new RouteValueDictionary() { {"WebFormsConstraint", new IncomingOnlyRouteConstraint()} },
new WebFormRouteHandler<Page>("~/Uploads.aspx")));
Of course you may prefer to add your own style of constraint, this one is quite limiting on the route that implements it, but it's just an example of one way you could resolve the issue.
Try:
<%=Html.RouteLink("Test", "Default", new {controller = "Controller", action = "Action"})%>
Using RouteLink instead of ActionLink allows you to specify the route you want to use, which in this case is the Default MVC route mapping as opposed to the custom one you have added.
Also: Make sure your Default route is the LAST entry in the routing table. That's another easy way to wind up with the sort of html action link you're getting.
Force the route defaults to have no controller:
var routeDefaults = new RouteValueDictionary() { { "controller", null } };
routes.Add("RouteName", new Route("some/path", routeDefaults, new SomeHandler()));
I experienced the same thing where the routes worked correctly "inbound", but Html.ActionLink() was picking the wrong route. I worked around it by adding a route constraint so that the controller must be empty:
var constraints = new RouteValueDictionary()
{
{ "controller", string.Empty }
};
routes.Add(new Route("sso/server", null, constraints, new OpenIDServerRouteHandler()));
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{identity}",
defaults: new { controller = "Pages", action = "Home", identity = UrlParameter.Optional }
);
Since the "controller" route value is restricted to nothing, a call to ActionLink() ends up ignoring the route. Hope this helps someone!