ASP.NET MVC Route Contraints with {ID}-{Slug} Format - asp.net

I have a route like following, ideally I would like it to match:
domain.com/layout/1-slug-is-the-name-of-the-page
routes.MapRoute(
"Layout", // Route name
"layout/{id}-{slug}", // URL with parameters
new { controller = "Home", action = "Index"}, new {id = #"\d+$"}
);
But when I hit the url, I am keep on getting this exception:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Index(Int32)' in ....
The above route will match the following though:
domain.com/layout/1-slug or domain.com/layout/1-slug_permalink
Seems like the hyphen that separates the ID from the Slug is causing issues.

As the first step of processing, the Routing module performs pattern matching of the incoming URL against the declared route. This pattern matching is eager (so the id gets all hyphens up to the last one, which marks the beginning of the slug parameter). Constraints (like "\d+") run after pattern matching. So what's tripping you up is that the eager pattern matching is setting id to an invalid value, then it's failing the constraint, which causes the overall route not to match, so the pipeline moves on trying to match the incoming request to the next route in the collection.
The best (e.g. easiest to understand, non-trickery) way to work around this is to match the entire segment as an idAndSlug parameter, then use a proper regex within the controller to split this string back out into its id and slug constituents.
Alternatively, consider using the slash, as suggested by mxmissile.

Related

ASP.NET Attribute Routing - Url.Action not working

This code works fine:
[RouteArea("Main", AreaPrefix = "Hello")]
[RoutePrefix("{orgCode}")]
public class ResponseController : BaseController {
[Route("Save/{formCode}/{responseId}")]
public ActionResult Save(string formCode, int responseId, string questionCode){}
}
and Url.Action("Save", "Response") produces, for example, /Hello/org123/Save/form/123
However, if the Route attribute is changed and another segment added:
[Route("Save/{formCode}/{responseId}/{questionCode}")]
then Url.Action("Save", "Response") produces an empty string.
Is there a limit to how many sections can be defined in the route?
Is there a limit to how many sections can be defined in the route?
No.
But MVC only can build URLs using route values that it knows about. This can be a combination of route values that are passed to the Url.Action() method and values that are in the current request (usually passed through the current URL).
When the framework tries to determine which route to use, it selects the first route that matches all of the route values (controller, action, and anything else) and matches all of the (optional) constraints.
The route will not match if all of the conditions below are true for any of the parameters:
Have no defaults
Are not marked UrlParameter.Optional (NOTE: Optional parameters may not have any non-optional parameters to the right of them)
Are not present in the current request
Are not explicitly passed as route values to Url.Action() (or other method that calls UrlHelper to generate a URL)
Have constraint rules that do not match
In short, a route only matches if all of the required parameters are provided and constraints are satisfied.
So, apparently there is no questionCode in the current context, and since it is required to build the URL, you get an empty string. Most likely you need to pass it explicitly.
Url.Action("Save", "Response", new { questionCode = "123" })
You should also be careful to always explicitly pass other parameters if there may be cases where they are not present in the URL.

Passing parameters in redirect view Spring MVC

I am not able to get how to pass a parameter along with a main url from a controller. I tried like this:
return new ModelAndView(new RedirectView("home?var=ss", true));
But I am getting null value for var . What is the correct way?
The documentation says:
By default all model attributes are considered to be exposed as URI
template variables in the redirect URL. Of the remaining attributes
those that are primitive types or collections/arrays of primitive
types are automatically appended as query parameters.
So you don't have anything to do except making sure that you have a model attribute named var, with the value ss.

ASP.net Routing - using database queries in order to determine the physical file, and add extra route data from query results

My question is regarding Page Routing in an ASP.net (VB) Web Forms website.
I need to route to 2 .aspx pages in multiple ways e.g.
routes.MapPageRoute("SEO", "{Title}/{Id}", "~/PageA.aspx")
routes.MapPageRoute("Catalogue", "Issue{IssueNumber}-{PageNumber}", "~/PageA.aspx")
but I need to implement some logic involving database queries (LINQ to SQL) on both routes e.g.
Route 1) Check a bit field, if false then physical file = PageA.aspx, true then PageB.aspx
Route 2) Lookup IssueNumber and PageNumber, retrieve PageId and add to RouteData, set physical file = PageA.aspx
I think the best way of doing this, is to implement an IRouteHandler class but I've not been able to determine:
Where to write the database queries in such class
How to set the physical file in the class
Where/how to add a new value to the route data i.e. PageId
Where to check that Id and Number fields are actually integers (constraints?)
I can't find any useful VB.net documentation, any suggestions?
If not I'm going to have to resort to an intermediate .aspx page i.e. Transfer.aspx and then do the database queries and then store return values in session variables and then do a Server.Transfer(PageA.aspx), but this seems like an old-fashioned and inelegant way of doing it. Please help!
Instead of writing your own IRouteHandler I'd suggest implementing your Route class. Override GetRouteData and setup the RouteData object that you return according to your needs.
Where to write the database queries in such class
As mentioned above, GetRouteData is the place you are looking for.
How to set the physical file in the class
On the RouteData object you return, set RouteHandler to a new PageRouteHandler instance. You can pass the physical path to PageRouteHandler's constructor.
Where/how to add a new value to the route data i.e. PageId
Use the Values property of the RouteData object.
Where to check that Id and Number fields are actually integers (constraints?)
This should be done with route constraints. The sixth parameter to MapPageRoute for example is a RouteValueDictionary with contraints. To simple check that a parameter is an integer, use a regular expression, like so:
routes.MapPageRoute("RouteName", _
"product/{id}", "~/Products.aspx", _
True, New RouteValueDictionary(), _
New RouteValueDictionary() From { {"id", "\d+"} })
See the "\d+" at the end? This is the regular expression that the id parameter needs to match.
If you need more complex constraints you can do that as well, see e.g. http://stephenwalther.com/blog/archive/2008/08/07/asp-net-mvc-tip-30-create-custom-route-constraints.aspx

Asp.NET MVC Route Performance

I have an application with a couple thousand routes.
This is because for each product we use a custom URL, as opposed to the textbook /product/id.
With this many urls, the performance is unacceptable in the router.
I am trying to find ways to improve it, but I am drawing a blank.
I have about 20 regex routes and about 3 thousand unique url routes.
Any Ideas?
Sorry for being so open ended, but I am not sure where to start.
If your urls are all on the format yoursite.com/{url}, you can still store all the three thousand urls in the database, and create a custom controller factory which uses the {url} parameter to look the correct information up in the database and assign correct controller, action and any parameters you're using.
There are lots of posts on google on how to implement the controller factory.
I imagine you'll also want some parsing of the existing routes to put them all in the database - this can probably be done by iterating over the RouteTable after you instantiate your application (i.e. after RegisterRoutes() is called).
I would get rid of the 3 thousand unique url routes and replace them with a generic route that is the last catch-all route. Something like:
routes.MapRoute("productRoute", "{category}/{manufacturer}/{productTitle}", new { controller = "Products", action = "Index", category = UrlParameter.Optional, manufacturer = UrlParameter.Optional, productTitle = UrlParameter.Optional });
You could also then add a custom IRouteConstraint to validate that the category exists (but just make sure it doesn't hit your database every time or performance will degrade).

Can I discover which named route matches a URL when unit testing ASP.NET MVC routes?

I'm using nUnit to test the routing of my MVC2 project.
When I register my routes in Global.asax.cs, I give each route a unique name and specify its RouteData, eg:
routes.MapRoute(
"ShowRecord",
"{controller}/{id}",
new { action = "Show" },
new { id = #"^\d+$" }
);
In my unit tests I then invoke RegisterRoutes on a RouteCollection object, and inspect the resulting RouteValueDictionary against each url that I want to test. I use a mocked HttpContext for this, and everything works fine.
However, what I'd really like to know is, which named route(s) matched the supplied URL? Once my unit test has obtained the RouteData object corresponding to the URL under test, can I discover specifically which route was matched? Either by name (eg "ShowRecord" in the example above), or by its index in the RouteCollection object?
Unfortunately, you cannot do this. See How do I get Route name from RouteData? for more context.
What you could do is add the route name to the route's DataTokens dictionary. DataTokens are used to mark a route in some way that's significant to you and your application. They don't affect anything with regarding to route matching and url generation.
The purpose of the route name is to provide a lookup key to the routing engine for looking up routes. It acts much like an index in SQL Server.

Resources