This is my RegisterRoutes method in global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute("ListBooks",
"Home/Books/{id}",
new { controller = "Home", action = "Books" },
new { id = #"\d{2}" });
}
As you can see in the constraint I have specified that the id should be compulsory there of 2 digits. But having specified this, even though I enter just a single digit book id it all still works out pretty well. Can anybody tell me what is wrong in this?
Your default route should be placed after the other routes, otherwise it would take the precedence.
Your constraint works as expected and url is not matched to the "ListBooks" route. But, if you look closer to the "Default" route, it has the same signature as the "ListBooks" one - but without constraint. So "Default" handles that single digit id url. In this case, your route order does not matter, as the "Default" will catch single digit id url anyways.
Try this
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("ListBooks",
"Home/Books/{id}",
new { controller = "Home", action = "Books" },
new { id = #"\d{2}" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Related
I wrote a very simple web app in Flask and am porting it to ASP.NET Framework. All the functionality is in JavaScript and HTMl, so the framework should just act as scaffolding. I've got almost everything ported over, except for what seems to be a routing issue. My site expects a string token variable to be appended to the URL, like so: www.mysite.com/token-string. For development, the URL is localhost:*****/string-token, with my Index.cshtml page being displayed as default.
When I pass the URL without the token it works fine and my index page loads. However I get a 404 when I try it with the token. I'm assuming it's identifying the token as a route and is trying to navigate to it? I'm not sure how to fix it. Here are the important parts of my code:
HomeController.cs:
public class HomeController : Controller
{
public ActionResult Index(string token)
{
return View();
}
}
RouteConfig.cs:
NB: I've not changed this, not sure what to do with it.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
It's quite important that the token is passed in the way it is, rather than as a ? query parameter or anything like that. Additionally, the C# index view doesn't really need to do anything with the token - it gets extracted by the JavaScript.
Any advice is most welcome. Thanks.
Each segment (i.e. {controller}) in the route is a variable, and in the default route makes them all optional. Therefore, your default route is matching the request www.mysite.com/token-string.
What you need to do is insert a route that has a constraint to only match URLs with your token. Assuming your token is a GUID, you could use a regex route constraint as follows:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "TokenRoute",
url: "{token}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { token = #"^[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}$" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
If your token is not a GUID, you could either use a different regex or implement IRouteConstraint to ensure the route only matches your tokens. The logic you use could be as simple as a == statement (as shown) or more complex (such as a database lookup).
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "TokenRoute",
url: "{token}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { token = new TokenConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
public class TokenConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if ((string)values[parameterName] == "MyToken")
{
return true;
}
return false;
}
}
Note that you should use the route value key {token} in the url: parameter to match the action method parameter name token.
public ActionResult Index(string token)
{
return View();
}
I guess you could try changing the default route to include token instead of id as shown below.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{token}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The default Route pattern which you have expects the parameter with name as 'id'
Either add (or modify the default route) like below route pattern
routes.MapRoute(
name: "AnotherRoute", //your desired route name
url: "{controller}/{action}/{token-string}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have the following controller:
public class MyController : BaseController
{
public ActionResult Index(string id) { /* Code */ }
public ActionResult MyAjaxCall(string someParameter) { /* Code */ }
}
I have also added the following in the RouteConfig.cs
routes.MapRoute(
name: "MyController",
url: "MyController/{id}",
defaults: new { controller = "MyController", action = "Index" }
)
So my idea is to be able to go directly to the index action using this url /MyController/{Id}, and that seems to work.
However when on the Index page I need to make an Ajax call to /MyController/MyAjaxCall/{someParameter}. However this url is pointing to the Index controller, and is interpreting MyAjaxCall as the id in the Index action.
Any ideas how I can exclude this action from following the newly added route config setting?
If that your id can only be integer number, you can add a constraint to your id field, which specifies that your id can only be numbers:
routes.MapRoute(
name: "MyController",
url: "MyController/{id}",
defaults: new { controller = "MyController", action = "Index" },
constraints: new { id = #"\d+" } // <- constraints of your parameters
)
Here you can use any regular expression that works for your business logic.
Also make sure to register this route before your default route registration, in that case MVC will first try to match this route, and only if it doesn't match it will try to match the default route.
It sounds like you have the routes in the wrong order. When using MVC routing, the first match always wins, so you must place the most specific routes first before general routes.
routes.MapRoute(
name: "MyControllerAJAX",
url: "MyController/MyAjaxCall/{someParameter}",
defaults: new { controller = "MyController", action = "MyAjaxCall" }
)
routes.MapRoute(
name: "MyController",
url: "MyController/{id}",
defaults: new { controller = "MyController", action = "Index" }
)
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
When I pass multiple parameters to a controller action, I get question marks in the parameters like this:
http://localhost:57728/Home/AddAndManageProperties?BaseCategoryId=11&SubCategoryId=14
I want to remove the question marks to be like this:
http://localhost:57728/Home/AddAndManageProperties/BaseCategoryId=11/SubCategoryId=14
here is my code:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "MyRout",
url: "{controller}/{action}/{BaseCategoryId}/{SubCategoryId}",
defaults: new { controller = "Home", action = "AddAndManageProperties", BaseCategoryId = UrlParameter.Optional, SubCategoryId = UrlParameter.Optional }
);
}
}
And here is the Action Method:
public ActionResult AddAndManageProperties(int? BaseCategoryId, int? SubCategoryId)
{
}
And i call the method AddAndManageProperties by this method
[HttpPost]
public ActionResult AddSubCategory(SubCategory subCategory)
{
return RedirectToAction("AddAndManageProperties", new { BaseCategoryId = subCategory.BaseCategoryId, SubCategoryId = subCategory.SubCategoryId });
}
I am new to ASP.NET MVC so please help me!
Move the MyRout to be before the Default route and change it to
routes.MapRoute(
name: "MyRout",
url: "Home/AddAndManageProperties/{BaseCategoryId}/{SubCategoryId}",
defaults: new { controller = "Home", action = "AddAndManageProperties" }
);
Note that only the last parameter can be marked UrlParameter.Optional so the method needs to be
public ActionResult AddAndManageProperties(int BaseCategoryId, int SubCategoryId)
for the above route, or
public ActionResult AddAndManageProperties(int BaseCategoryId, int? SubCategoryId)
if you modify the above route definition to
defaults: new { controller = "Home", action = "AddAndManageProperties", SubCategoryId = UrlParameter.Optional }
Note, if you also want to include the text "BaseCategoryId" and "SubCategoryId" in the route, use
url: "Home/AddAndManageProperties/BaseCategoryId/{BaseCategoryId}/SubCategoryId/{SubCategoryId}",
That question marks are used for query strings, and they are required because this is how data is assigned to parameters your actions expect. You should not try to remove them, but you could use the [FromBody] attribute, not to send the parameters in query string.
Firstly, and most importantly, your routes are in the wrong order and you have multiple possible URLs that result in calling the wrong route. See Why map special routes first before common routes in asp.net mvc for an explanation.
Secondly, routes cannot contain more than one UrlParamter.Optional.
Third, the = sign is only valid within a query string, unless it is encoded. But IMO, you should not use unsafe characters in a URL to avoid all of the headaches that come with them. A better alternative in this case would be to replace the = with a -.
Finally, if you want to truly make the parameters optional, one approach is to provide multiple routes that allow the parameters in certain routes but not others.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "BaseCategoryAndSubCategoryId",
url: "{controller}/{action}/BaseCategoryId-{BaseCategoryId}/SubCategoryId-{SubCategoryId}",
defaults: new { controller = "Home", action = "AddAndManageProperties" }
);
routes.MapRoute(
name: "BaseCategoryIdOnly",
url: "{controller}/{action}/BaseCategoryId-{BaseCategoryId}",
defaults: new { controller = "Home", action = "AddAndManageProperties" }
);
routes.MapRoute(
name: "SubCategoryIdOnly",
url: "{controller}/{action}/SubCategoryId-{SubCategoryId}",
defaults: new { controller = "Home", action = "AddAndManageProperties" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
NOTE: Stephen's answer is also a good alternative to this approach if your parameters are required to be passed in the URL. IMO, it makes more sense to use required parameters if your action method requires both of them in order to function.
But by far the simplest option is to simply use the query string. The parameters can naturally be optional and appended in any order if you do it that way, and you don't need anything more than your Default route.
I haven't used .NET Routing before.
I have a URL: http://myurl.com/Account/Login/?IsIPA=true.
I want to be able to hit this URL with the following: http://myurl.com/IPA
This is the only custom route I want hit.
Can I create a route just for a single URL like this?
My code that isn't working is:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute("IPA", "Account/Login/{IsIPA}", new { controller = "Account", action = "Login", IsIPA = "true" });
}
I get the error:
The constraint entry IsIPA on the route with route template Account/Login/{IsIPA}=True must have a string value or be of a type which implements System.Web.Routing.IRouteConstraint.
Route matching is similar to a switch case statement. The url parameter and any default values and constraints are all considered to determine whether or not it is a match with the incoming URL. If the route matches, it will then create a dictionary of route values based on the configuration. If the route does not match, the next route in the collection is tried until a match is found (or not).
This means the order that routes are specified is important. The default route matches any URL with 0, 1, 2, or 3 segments. Therefore, in most cases you will need to define your custom route before the default route.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "IPA",
url: "IPA",
defaults: new { controller = "Account", action = "Login", IsIPA = "true" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The above configuration will route http://myurl.com/IPA to the Controller named Account and Action method named Login, and pass the additional route key IsIPA. This same URL will be built for the Controller/Action/IsIPA combination because it is the first one that matches in the list.
Note that the original URL http://myurl.com/Account/Login/?IsIPA=true will still work and still route to the same location. This configuration just adds an extra route to that resource.
Without testing it, I think that you want this:
routes.MapRoute("IPA", "Account/Login/{IsIPA}",
new { controller = "Account", action = "Login", IsIPA = "true"});
I have this RegisterRoute function
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("JsActionRoute", JsAction.JsActionRouteHandlerInstance.JsActionRoute);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Where the JsActionRoute is a route like this.
public static readonly Route JsActionRoute = new Route("JsAction",
new JsAction.JsActionHandler());
I want that all links to JsAction/ should be handled by my coustom route.
Now when simply calling #Html.ActionLink, Mvc3 creates a link that is related to JsAction, and I can't understand why.
#Html.ActionLink("About","About") -> JsAction?Action=About&Controller=Index
Routes are evaluated in the same order as they are registered. You could use the RouteLink to explicitly specify a route name:
#Html.RouteLink("About", "Default", new { action = "About" })
If you inverse the order of definitions then links will generate correctly but your custom route will not be hit when requesting the JsAction/ url since there is nothing to disambiguate this url from the default route.
You will have to rethink your routes structure so that there are no such conflicts. You could use constraints. Remember that the default route is very eager and if you don't constrain it, it will often take precedence.
One possibility is to define a controller and action for your custom route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("JsActionRoute", JsAction.JsActionRouteHandlerInstance.JsActionRoute);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
);
}
and then constrain the custom route by specifying a controller and action to be executed:
public static readonly Route JsActionRoute = new Route(
"JsAction",
new RouteValueDictionary(new { action = "JsAction", controller = "Foo" })
new JsAction.JsActionHandler()
);
Another possibility is to inverse the order of definition of your routes and constrain the default route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = "^((?!JsAction).)*$", action = "^((?!JsAction).)*$" }
);
routes.Add("JsActionRoute", JsAction.JsActionRouteHandlerInstance.JsActionRoute);
}