can I have domain.com/action/id as well as domain.com/controller/action?
how would I register these in the route-table?
Is ID always guaranteed to be a number? If yes, then you could use RouteConstraints:
routes.MapRoute("ActionIDRoute",
"{action}/{id}",
new { controller = "SomeController" },
new {id= new IDConstraint()});
routes.MapRoute("ControllerActionRoute",
"{controller}/{action}",
new {}); // not sure about this last line
The IDConstraint class looks like this:
public class IDConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var value = values[parameterName] as string;
int ID;
return int.TryParse(value,out ID);
}
}
Basically what is happening is that you have two identical routes here - two parameters, so it's ambigous. Route Constraints are applied to parameters to see if they match.
So:
You call http://localhost/SomeController/SomeAction
It will hit the ActionIDRoute, as this has two placeholders
As there is a constraint on the id parameter (SomeAction), ASP.net MVC will call the Match() function
As int.TryParse fails on SomeAction, the route is discarded
The next route that matches is the ControllerActionRoute
As this matches and there are no constraints on it, this will be taken
If ID is not guaranteed to be a number, then you have the problem to resolve the ambiguity. The only solution I am aware of is hardcoding the routes where {action}/{id} applies, which may not be possible always.
Yes, you can add a new rule above the default rule and provide a default value for the controller.
routes.MapRoute(
"MyRole", // Route name
"{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
The sample routs all actions to the "Home" controller.
Related
I am facing an issue having 2 default asp.net mvc routes (applied through custom constraints). What I am trying to do is, load different views based on if the parameters are supplied in the routedictionary or not. Below are my two routes in RouteConfig.cs
routes.MapRoute(
name: "DefaultWatch",
url: "{controller}/{action}/{title}",
defaults: new { controller = "Watch", action = "Index", title = ""},
constraints: new { title = new VideoTypeRouteConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Main", action = "Index"}
);
I want to open /watch/Index/{title} if the title string is supplied or just open my default route /Main/Index. Below is the implementation for my route constraint.
In VideoTypeRouteConstraint.cs
public class VideoTypeRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (values.ContainsKey(parameterName))
{
string value = values[parameterName].ToString();
return !String.IsNullOrEmpty(value) ? true : false;
}
return false;
}
}
What I am trying to do check if RouteValueDictionary contains the title variable and if so, returns true so my /Watch/Index/{title} is executed.
Now it works when I hit the below urls
http://localhost:53923/ //returns /Main/Index correctly
http://localhost:53923/?title=routing-optional-parameters-in-asp-net-mvc-5 //Also returns /Main/Index because the value in RouteValueDictionary is null but I can see the value in httpContext.Request[parameterName]
http://localhost:53923/routing-optional-parameters-in-asp-net-mvc-5 //this DOES NOT WORK - Returns 404
RouteValueDictionary contains the key (title) but its value is always null. This is where the issue is I believe but I'm not being able to identify it.
The whole idea of this was to clean my urls for SEO which were way longer when I used a separate controller.
I have the following Route defined in Global.asax:
routes.MapRoute(
"IncidentActionWithId", // Route name
"Incidents/{companyId}/{action}/{id}", // URL with parameters
new { controller = "Incidents" } // Parameter defaults
);
I have a special case of request, like this one:
/Incidents/SuperCompany/SearchPeople/Ko
In this case, action should indeed map to SearchPeople action, comapnyId to this action's parameter, but only when action is SearchPeople, the Ko should not be mapped to an id parameter of the action, but to searchTerm.
My action declaration is:
[HttpGet]
public ActionResult SearchPeople(string companyId, string searchTerm)
How can I achieve Ko to be mapped to searchTerm parameter in my action method?
You can define two routes, one with id and one with searchTerm if the id is supposed to be numeric (or you can specify regex constratints) and have different pattern to searchTerm.
See here how you can define constraints.
Example:
routes.MapRoute(
"IncidentActionWithId", // Route name
"Incidents/{companyId}/{action}/{id}", // URL with parameters
new { controller = "Incidents" }, // Parameter defaults
new {id = #"\d+"} // numeric only
);
routes.MapRoute(
"IncidentActionWithId", // Route name
"Incidents/{companyId}/{action}/{searchterm}", // URL with parameters
new { controller = "Incidents" }
);
NOTE
Define the one with constraint first.
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'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 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!