Mapping routes to controller programmatically - asp.net-mvc

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

Related

MVC - Passing path to action in controller

Fairly new to MVC, I would like the URLs of my article pages to be like this:-
http://www.example.com/article1
http://www.example.com/article2
http://www.example.com/article3
How can I set up the routing such that whenever someone types in the above it calls an action called article in the controller and passes the path to it?
I tried something like this but to no avail: -
routes.MapRoute(
name: "article",
url: "{article}",
defaults: new { controller = "Home", action = "article" }
);
One solution is to add multiple routes.
routes.MapRoute(
name: "article1",
url: "article1",
defaults: new { controller = "<YourControllerName>", action = "article1" }
);
routes.MapRoute(
name: "article2",
url: "article2",
defaults: new { controller = "<YourControllerName>", action = "article2" }
);
Edit:
From OP's comment, it is understood that there would be 'n' number of articles(urls). To deal with that, we can create a custom route handler.
Step 1: Create a new custom route handler inheriting from MvcRouteHandler
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var controller = requestContext.RouteData.Values["controller"].ToString();
requestContext.RouteData.Values["controller"] = "Home";
requestContext.RouteData.Values["action"] = "Index";
requestContext.RouteData.Values["articleId"] = controller;
return base.GetHttpHandler(requestContext);
}
}
Step 2: Register the new route. Make sure to add this route before default route.
routes.Add("Article", new Route("{controller}", new CustomRouteHandler()));
In the given CustomRouteHandler class, the Controller and Action is hard coded with "Home" and "Index" respectively. You can change that to your own controller and action name. Also you would see a "articleId" setting to RouteData.Values. With that setting, you would get the articleId as a parameter in your Action method.
public ActionResult Index(string articleId)
{
return View();
}
After all the changes, for the url http://www.example.com/article1, the Index() method of HomeController gets invoked with articleId set to 'article1'.
Similarly for http://www.example.com/article2, the Index() method gets invoked with parameter articleId set to 'article2'.

Can I control (manipulate) the address bar using MVC .NET

I would like to invoke this URL:
www.example.com/home/brand1
but have the resulting URL be:
www.example.com/brand1
I want to do this with multiple brands. I know I can have an action in the home controller for each brand which redirects to a brand controller but I don't want a controller for each brand. I imagine I can do this with routing but just don't know how.
routes.MapRoute(
"Default",
"{brandName}",
new { controller = "Home", action = "YourBrandAction", brandName = "" }
); // inside RegisterRoutes method
//Your Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult YourBrandAction(String brandName)
{
//your controller logic...
return View(yourBrandModel);
}
}
You must be very careful with the above route configuration. Some URLs, e.g. www.example.com/login will not direct you to the login page but treat 'login' as a brand name.

ASP.NET MVC 4 Routing I just can't figure out

I'm working on a project in ASP.NET MVC 4 and I'm at a bit of a loss with a particular routing. I have a lot of custom routes already in the project.
I am currently making a bunch of controllers for the frontend of the site (publicly visible part) to be able to do thing like abc.com/OurSeoFeatures that gets routed to /OurSeoFeatures/Index
Is there any way to do this so that the above would route to something like /frontend/OurSeoFeature and another page would route to /frontend/anotherpage and also still have my other routes correctly? It seems to me that the above would hit the default route and if I put something like the following it would just catch all the request and would not let me hit anything else.
routes.MapRoute(
name: "ImpossibleRoute",
url: "{action}/{id}",
defaults: new { controller = "frontend", id = UrlParameter.Optional }
);
Am I just stuck with making a bunch of controllers? I really don't want to make one controller like page and put a bunch of actions there as I don't think its very pretty. Any Ideas?
In order to do what you're asking, you simply need to add a route constraint:
routes.MapRoute(
name: "Frontend",
url: "frontend/{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeature", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = "OurSeoFeature|Products" }
);
This constraint means the route will only match controllers with the names OurSeoFeatureController or ProductsController. Any other controller will trigger the default route. However, this wouldn't handle redirecting those controllers to /frontend/..., if that's what you're after. Instead, that gets a little more involved.
Firstly, you'll need to create a class that implements IRouteConstraint, in order to supply the controller names you want to redirect to /frontend/.... The reason we need this now, is because we'll need to access those names in an ActionFilter, and we can't do that if we supply a regex constraint like constraints: new { controller = "OurSeoFeature|Products" above. So, the constraint could look something like this:
public class FrontendControllerConstraint : IRouteConstraint
{
public FrontendControllerConstraint()
{
this.ControllerNames = new List<string> { "OurSeoFeature", "Products" };
}
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
string value = values[parameterName].ToString();
return ControllerNames.Contains(value, StringComparer.OrdinalIgnoreCase);
}
public List<string> ControllerNames { get; private set; }
}
Next up, the action filter could look like this:
public class RedirectToFrontendActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RouteData.Values["controller"].ToString();
var path = filterContext.HttpContext.Request.Url.AbsolutePath;
var controllersToMatch = new FrontendControllerConstraint().ControllerNames;
if (controllersToMatch.Contains(controller, StringComparer.OrdinalIgnoreCase)
&& path.IndexOf(pathPrefix, StringComparison.OrdinalIgnoreCase) == -1)
{
filterContext.Result =
new RedirectToRouteResult(routeName, filterContext.RouteData.Values);
}
base.OnActionExecuting(filterContext);
}
private string routeName = "Frontend";
private string pathPrefix = "Frontend";
}
Now that we have those in place, all that's left is to wire it all up. Firstly, the constraint is applied in a slightly different way:
routes.MapRoute(
name: "Frontend",
url: "frontend/{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeature", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = new FrontendControllerConstraint() }
);
Finally, you need to add the filter to FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new RedirectToFrontendActionFilter());
}
One warning here is that because I'm checking against Request.Url.AbsolutePath, you cannot pass anything in the path that contains the word frontend. So make sure all controllers, actions and route values added to the path, do not contain that. The reason is that I'm checking for the existence of /frontend/ in the path, to ensure that the matched controllers will only redirect to that route if they they're not already using it.
There are a lot of added things you could do with that setup, but I don't know your requirements. As such, you should treat this code simply as a skeleton to get started, making sure to test that it does what you want it to do.
Updated per comments
I'll leave everything above there, just in case someone finds that useful. To address what you'd like to do, however, we need a different approach. Again, we need some route constraints, but the way I see this working is to flip your idea on its head and make the frontend the default route. Like so:
routes.MapRoute(
name: "Backend",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = "Home|Backend" }
);
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Frontend", action = "Index", id = UrlParameter.Optional },
constraints: new { action = "Index|OurSeoFeature" }
);
Just as before, I've applied some constraints to get the correct behaviour. In particular, for this constraint:
constraints: new { controller = "Home|Backend" }
if you have a lot of controllers that aren't part of the frontend, it might be an idea to implement IRouteConstraint to keep a list of the controller names there. You could even go as far as deriving all of your backend controllers from a base controller, so you can grab all of them with reflection in the IRouteConstraint implementation. Something like this:
public BackendController : Controller
{
//
}
Then:
public AdminController : BackendController
{
//
}
Constraint:
public class BackendConstraint : IRouteConstraint
{
// Get controller names based on types that
// BackendController
}
This same idea also applies to getting the action names of FrontendController for the second constraint. The only thing you need to be careful of here is that you don't have any backend controllers which have the same name as an action on your FrontendController, because it will match the wrong route.
I appreciate the question is over a year old with an accepted answer but the accepted answer involves route constraints when none are necessary. It's really just as simple as:
routes.MapRoute("SEO", "OurSeoFeatures",
new { controller = "frontEnd", action = "OurSeoFeatures"});
The basic idea of the route is controller/action.
So if you want to hit the OurSeoFeatures controller's index action then you have to give your route like
routes.MapRoute(
name: "BasicController",
url: "{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeatures",action="Index", id = UrlParameter.Optional }
);
In your case you have left out the controller from your route url. Please specifiy the controller also as part of URL and have a default controller.

How to control which Action is used in ASP.NET MVC

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

ASP.NET MVC3 Routing various subfolders to the same controller

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

Resources