I have a setup where I use one route with url's on the following format:
www.example.com/friendly-url-1
www.example.com/friendly-url-2
www.example.com/another-friendly-url
The route is defined like this:
routes.MapRoute("FriendlyUrl", "{name}",
new { controller = "FriendlyUrl", action = "Index", name = UrlParameter.Optional }
);
Name is an internal property in my application that lets me look up which controller should be used in a custom ControllerFactory, and change the name of the controller that is created (there is no actual controller called FriendlyUrl).
It works well if I only have one action per controller, but since action isn't part of the route, it always uses the default action. I want to have more than one action, but I'm not able to find a good way for me to write logic that controls which action should be used for each request. Is it possible?
If I correctly understand, you have urls in form "www.example.com/friendly-url/name" and want name to determine both controller and action, e.g.
"example.com/friendly/foo" would resolve to SomeController and XxxAction
"example.com/friendly/boo" would resolve to AnotherController and ZzzAction
I think, the easiest way would be to use custom route handler
public class MyRouteHander : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var values = requestContext.RouteData.Values;
var token = GetRequestToken(requestContext);
switch (token) {
case "friendly/foo") {
values["controller"] = "some";
values["action"] = "xxx";
break;
case "friendly/boo") {
values["controller"] = "another";
values["action"] = "zzz";
break;
...
}
return new MvcHandler(requestContext);
}
}
and then you register the handler with
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
).RouteHandler = new MyRouteHander();
You can write dedicate route for your URL's and it will call different action in that case. but if you want to use a expression to route to different actions i don't think you can do it directly by writing route in routeConfig as you need to specify somewhere the mapping of URL Segment to Action which is equivalent to creating dedicated routes in routeConfig
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 have a site with the normal, default route and several controllers. I would like to distribute "short URL" links that can link back to the home/index action. For example, I can do
/MySite - takes you to Home/Index as default
/MySite/SomeController/SomeAction - takes you to the specified controller/action as default
but I would also like to do:
/MySite/SomeID - takes you to Home/Index with the id param supplied.
I can add a "shortUrl" route and distribute a url like "/MySite/ShortUrl/SomeID", but is there any other way to use an "id-only" url like the one above?
The problem you've got with doing something like this is that the following would then be ambiguous:
/MySite/SomeID
/MySite/SomeController
How do you expect to be able to differentiate between the two? If you don't mind the second being impossible (i.e. you are happy always specifying an action when you specify a controller), you could try something like this:
routes.MapRoute(
"ShortUrl",
"{id}",
new { controller = "Home", action = "Index", id = Url.OptionalParameter }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = Url.OptionalParameter }
);
Requesting /MySite/SomeID should then take you to the same action as MySite/Home/Index/SomeID.
If you need to be able to specify either and ID or a controller (with default action), you could do something like the following (also using the above routing):
public class HomeController : Controller
{
public ActionResult Index(string id)
{
// If the ID represents something, show that something.
if (IdMatchesSomeResource(id))
{
// Do something
return View();
}
// Otherwise, treat it as a request for a controller.
else
{
return RedirectToAction("Index", id);
}
}
}
I've managed to get my Home controller working by only using the action name to access it, but as soon as I add another controller and try do the same thing by adding another route declaration the same at my Home route declaration, all the routes default to the Home controller.
routes.MapRoute("HomeTest", "{action}/{id}", new { controller = "Home", action = "TestHome", id = UrlParameter.Optional });
routes.MapRoute("TestTest", "{action}/{id}", new { controller = "Test", action = "Test", id = UrlParameter.Optional });
public class HomeController : Controller
{
public ActionResult TestHome(int? id)
{
return View();
}
}
public class TestController : Controller
{
public ActionResult Test(int? id)
{
return View();
}
}
Is there any way I can access both controllers without including the controller name in the url?
I also have the default route included if that makes any difference. It's under these two routes.
Routes in MVC are compared in the order you add them. For your code sample, this means that all requests will be compared to the "HomeTest" route first, so any matches will follow that route. The "TestTest" route has the exact same format, which means that it will never be used because any matches will have already used the "HomeTest" route. If you want to get to get to a controller's actions without using the controller name, there must be something in the route to tell it which controller to use. This doesn't have to be the actual controller name - you can do this:
routes.MapRoute("TestTest", "keyWord/{action}/{id}", new { controller = "Test", action = "Test", id = UrlParameter.Optional });
routes.MapRoute("UsersTest", "otherKeyWord/{action}/{id}", new { controller = "Users", action = "Test", id = UrlParameter.Optional });
where the keywords basically act as aliases to hide your controller names. You can even go one step further and mask your action names as well, which can let you use multiple controllers under one URL root:
routes.MapRoute("TestTest", "test/key1/{id}", new { controller = "Test", action = "Test", id = UrlParameter.Optional });
routes.MapRoute("UsersTest", "test/key2/{id}", new { controller = "Users", action = "Test", id = UrlParameter.Optional });
Doing something like this can let you give your users a much tidier URL structure to help them navigate your site and understand where links will take them (potentially also handy for bookmarking) but it does mean you have to manually define your URLs, which is more work.
With the way you have defined your url patterns routing engine will always redirect all the actions matching pattern {action}/{id} to Home controller.
What are you looking for is probably something like:
routes.MapRoute("HomeTest", "testhome/{id}", new { controller = "Home", action = "TestHome", id = UrlParameter.Optional });
routes.MapRoute("TestTest", "test/{id}", new { controller = "Test", action = "Test", id = UrlParameter.Optional });
Is it possible to map a route to a controller programmatically? In other words, I want to create a route without a controller, and based on the values of the rest of the parameters in the url map the correct controller to the request?
An example:
url: example.com/about-us
I want to look-up in our system which controller "about-us" is using and then set the controller for the request. It can't be a default controller since there will be many different pages like the one above, that uses different controllers.
Why would you need this? Normal MVC way for handling such situations is to add different routes for different controllers, specifying values of parameters inside routes themselves or using RouteConstraints.
Another approach (if you really inist on doing routing logic yourself) might be creating a "Routing controller" with, say, a single action which processes all the queries. Inisde this action code you may check for parameter values and do return RedirectToAction(...) to redirect request to any action on any controller you need.
UPDATE: Example code
In Global.asax.cs create the following default route:
routes.MapRoute(
"Default", // Route name
"{*pathInfo}", // URL with parameters
new { controller = "Route", action = "Index"} // Parameter defaults
);
Also add a controller class RouteController.cs with the following content:
// usings here...
namespace YourApp.Controllers
{
public class RouteController : Controller
{
public ActionResult Index(string pathInfo)
{
...
// programmatically parse pathInfo and determine
// controllerName, actionName and routeValues
// you want to use to reroute current request
...
return RedirectToAction(actionName, controllerName, routeValues);
}
}
}
I would suggest using custom IRouteHandler implementation. You can restrict route matching with constraints and then rewrite a controller to be instantiated within IRouteHandler implementation.
E.g.
public class RewriteController : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
// here some logic to determine controller name you would like
// to instantiate
var controllerName = ...;
requestContext.RouteData.Values["controller"] = controllerName;
return new HttpControllerHandler(requestContext.RouteData);
}
}
Then your route may be like the following:
routes.MapHttpRoute
(
name: Guid.NewGuid().ToString(),
routeTemplate: "{controller}/{action}",
defaults: new { action = "Index" },
constraints: new
{
controller = "about-us"
}
).RouteHandler = new RewriteController();
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 : ""