MVC Routing Parameter Precedence - asp.net-mvc

I came across a scenario where I had the default MVC Route setup. Like So.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then navigating to a url as such
domain/controller/action/1234
On this page I was then navigating to the same page but with different parameters after a certain event. Like so.
var id = $(this).data("id");
var url = "#Url.Action("action", "controller", new { area = ""})";
var params = $.param({id: id, vrnsearch: true});
var fullUrl = url += "?" + params;
window.location.href = fullUrl;
I noticed that the url was still keeping the same id in the url and attaching parameters like so.
domain/controller/action/1234?id=4321&vrnsearch=true
Now my question is, is there a way to determine a precedence over if it should use the value for id from the url or from the parameter.
I have actually found a work around/fix for my issue by using the below, which removes the id from the url and just uses parameters.
#Url.Action("action","controller", new {id = "", area = ""})
However was just curious if there is a precedence in parameters vs url routing.

The query string has nothing at all to do with routing (at least, not unless you customize routing to consider it).
The values that are passed to the ModelBinder and to your action method are done so by Value Providers. You can control the order of precedence by changing the order in which their corresponding ValueProviderFactory is registered in the static ValueProviderFactories.Factories property.
As you can see, the default configuration is to first use the RouteDataValueProviderFactory and if it returns no value it will try the QueryStringValueProviderFactory. If you change the order of the factories, the order of precedence changes.
ValueProviderFactories.Factories.RemoveAt(3);
ValueProviderFactories.Factories.Insert(4, new RouteDataValueProviderFactory());

Related

Best way to give each customer a custom vanity URL

I allow each customer to choose their own URL, by baking their own custom code into the url as the first segment after the domain like this (https://www.[mydomain].com/customercode) and then learn who they are from that URL. This is working fine by learning who they are using MVC routing and session. The problem is that once the customer begins their session using their custom URL, the URL changes and they no longer see their name in the URL. i.e. they start the session as https://www.[mydomain].com/customercode, then the URL changes to something like https://www.[mydomain].com/account/login after the first re-direct.
Is there a way for me to preserve that customer info segment of the URL and just consider it as part of 'the base url'?
In the past I have written extensions such as CustomActionLink and CustomAction functions to override standard url rendering but that would mean that I need to use those functions all over my site. I am wondering if I manipulate or dictate every url rendered by my site, so that the customer name always appears in their url bar.
For reference, here is my routing code
// https://scheduler.[theservicedomain].com/TheCustomersName
routes.MapRoute(
name: "Scheduler",
url: "{practiceRouteCode}",
defaults: new { controller = "Scheduler", action = "Index" }
);
// https://scheduler.theservicedomain].com/scheduler/selectservice
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Scheduler", action = "Index" },
namespaces: new[] { "Scheduler.Controllers" }
);
//https://scheduler.theservicedomain].com/TheCustomersName/scheduler/selectdate
routes.MapRoute(
name: "VanityURL",
url: "{practiceRouteCode}/{controller}/{action}",
defaults: new { controller = "Scheduler", action = "Index" },
namespaces: new[] { "Scheduler.Controllers" }
);
The customer is always required to begin the session with the first or third route, then we know who they are. But as of now the customer information (practiceRouteCode) is dropped and the rest of the session is on the second (Default) route.

Asp.Net MVC localizing routes with default language

I need to provide language specific routes for my Asp.Net MVC application. The language should be part of the Url Path (http://myapp/en/Blog) and when it is ommitted the default language have to be used.
http://myapp/en/Blog -> Blog in the English version
http://myapp/Blog -> Blog in the Default (portuguese) Language version
To address this issue I created two Routes bellow:
routes.MapRoute(
name: "Default.lang",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { lang = #"^[a-zA-Z]{2}$" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The routes are working as expected but I'm getting weird results when I try to use the Url.RouteUrl method to get alternative language urls.
Example 1 - Path: /
url.Action("Index", "Blog") // returns "/Blog" that is OK
url.Action("Index", "Blog", new { lang = "en" }); // returns "/en/Blog" that is also OK
Example 2 - Path: /en
url.Action("Index", "Home") // returns "/en/Blog" (??????????) Not OK
url.Action("Index", "Home", new { lang = "en" }); // returns "/en/Blog" that is OK
As you can see I get a wrong result when I access the url http://myappurl/en and try to use the Url.Action method without pass any route value (same result with Url.RouteUrl)
Does anyone knows what is wrong with my routes?
[EDIT] I'm not sure if the issue is related to the route because I've tested the routes using "en" as first route's constraint and I got the same result.
After some digging inside System.Web.Mvc and System.Web.Routing source code I found that this behavior is expected. I presume that it is designed to correctly work in applications running inside Virtual Directories.
This can be confirmed in this answer that I found when was researching if someone else had the same problem with virtual path and route resolution.
Workaround
Use named route resolution with Url.RouteUrl method that has and different implementation and works as expected.
Example:
var blogDefaultUrl = url.RouteUrl("Default", new {action = "Index", controller = "Blog"});
var blogLangageSpecifictUrl = url.RouteUrl("Default.lang", new { action = "Index", controller = "Blog", lang = language });
I was avoiding to use named routes because my application design is a little bit more complicated than I demonstrated above. By this reason I have to discover at run time the route that matches the Request.Url and from that point I call the RouteUrl to get the alternative languages url to the content.

MVC routing with optional first parameter

I am trying to get a route like:
{lang:optional}/{controller}/{action}/{id:optional}
With "controller constraints" idea from this article: (MVC Routing Constraint on Controller Names), the above route works very well, when {lang} value is presented or not.
However I have a problem to match this route:
routeName: testRoute
url: {lang:optional}/list/{something:optional}
{controller = "product", action = "index"}
for the above route, the {lang} value must be presented, otherwise it does not work.
I have two workarounds to work it out.
The first way is to set two routes for the above:
The very standard one:
routeName: testRoute
url: /list/{something}
{controller = "product", action = "index"}
and another very standard one:
routeName: testRouteLang
url: {lang:not_optional}/list/{something:optional}
{controller = "product", action = "index", lang="de"}
I am wondering if there is a way to combine the two standard routes into one single route.
The second workaround is to use subdomain name, such as
http://example.com/list (default to English)
http://de.example.com/list (de)
But I really do not like the subdomain idea for the SEO reasons (maybe I am wrong on this point).
My goal is to remove the default "en-us" in the URL. I like this
http://www.example.com/list/something (default as English)
I do not want to force "en-us" in the url
http://www.example.com/en-us/list/something
The "lang" should only be presented in the Url if the current culture is not English:
http://www.example.com/de
http://www.example.com/fr/list/something
Thanks.
Finally I found an very easy and DRY solution. The core thing is to use the HttpContext.Current.RewritePath to inject the default "en", while this "en" will not be shown in the URL.
protected void Application_BeginRequest()
{
var rawUrl = HttpContext.Current.Request.RawUrl;
var segments = HttpContext.Current.Request.Url.Segments;
var segment1 = segments.Count() >= 2 ? segments[1] : string.Empty;
if (IsSomethingThatIWantToHandle("are,you,js,script,css,jpg,png,and,so,on?")
&& !LittleHelper.DoIHaveValidLangAlready(segment1))
{
HttpContext.Current.RewritePath("/en" + rawUrl);
}
}
When generate URL, if lang is null/empty, the URL will be have a double //. I just need a little helper to remove the extra "/".
When define a route, a trick is that the area name must be added to the DataTokens, otherwise the view cannot be correctly located, if areas are presented in the project.
routes.MapRoute(
"good name",
"{lang}/some-cool-stuff/{id}/{slug}",
defaults: new { area = "bigarea", controller = "bigcontroller", action = "tinyaction",
lang = UrlParameter.Optional, id = UrlParameter.Optional, slug = UrlParameter.Optional }
, constraints: new { lang = new CultureConstraint() }
).DataTokens.Add("area", "bigarea");
The CultureConstraint is very straightforward -- just verify whether it is a valid culture name. The namespace constraints is not necessary. However if the route table is big and complicated then the controller constraints, or even action constraints is very necessary otherwise duplicated routes will be an issue.
My default route in my project now is:
url: "{lang}/{area}/{controller}/{action}/{id}/{slug}",
and it works beautifully as I want.

MVC routes.MapRoute name property

I'm brand new to MVC so please bear with me as I'm only on the second page of the MS Tutorial (see last code example). For the HelloWorldController the following MapRoute is added:
routes.MapRoute(
name: "Hello",
url: "{controller}/{action}/{name}/{id}");
I'm just wondering, is it purely the pattern matching that does the work and the name "Hello" is just for my own reference? If so, are there not naming conventions that should be followed saying the MapRoute should be called HelloWorldWelcome, where welcome is a method inside the HelloWorldController.cs (see above link). Or am i being pedantic?
The route name is also used by the UrlHelper class. For example:
var url = Url.Route("Hello", new
{
controller = "SomeController",
action = "SomeAction",
name = "charlie",
id = 123
});
This will generate a matching URL.
This feature is much more useful when you use Attribute Routing. For example, if on some controller you have an action:
[RoutePrefix("api/phonebook")]
public class PhonebookController
{
[HttpGet("contact/{id}", Name = "GetContact")]
public Contact GetContact(int id)
{
...
}
}
In other code you could use Url.Route("GetContact", new { id = 7 }) to generate the URL /api/phonebook/contact/7.
Please refer to details on ASP.NET MVC Routing Overview
Name attribute is for callign a route from your views or controller with route name.
From ActionLink your can use a routename:
Html.RouteLink("link_text", "route_name", route_parameters)
The question seems to be not so clearly answered (how the "Hello" route is choosen by the "HelloWorld" controller?), but as an Asp.Net MV5 begginer, I can see that the route is selected by default according to the match between the router url property and the URL parameters.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "ImageScan", action = "ScanImage", id = UrlParameter.Optional },
namespaces: new[] { "WebApplication3.Controllers" }
);
I am finding error :
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Views/ImageScan/ScanImage.cshtml

Can .net mvc routing cause JavaScript errors?

I am having a lot of trouble using routing infrastructure of asp.net mvc2. I have following routes registered in my global.asax file
routes.MapRoute(
"strict",
"{controller}.mvc/{docid}/{action}/{id}",
new { action = "Index", id = "", docid = "" },
new { docid = #"\d+"}
);
routes.MapRoute(
"default",
"{controller}.mvc/{action}/{id}",
new { action = "Index", id = "" },
new { docConstraint = new DocumentConstraint() }
);
The problem is with first route ("strict"). Three kind of urls can match first route. mycontroller/23/myaction, mycontroller/23/myaction/12 or mycontroller/23/mvaction/stringid. If I try to use this route without specifying value of id everything works fine for example:
Html.ActionLink("Link text", "ActionName", new{docid = 23});
Everything goes well, but if I use links like:
Html.ActionLink("Link text", "ActionName", new{docid = 23, id = 223})
This will produce url currentcontroller.mvc/23/ActionName/223 that is absolutely correct but when it loads the page it gives a JavaScript error in jquery1.4.2.min.js file.
This is strange: if I change id to someid =223 it will reflect in query string and there will be no JS error.
Edit: I have done some further debugging and found when both id and docid are mentioned in route values one thing is ignored in global.asax that is the ignore path.
routes.RouteExistingFiles = false;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.ignoreRoute is totally bypassed and I can see names of JS files in route value dictionary while debugging in my controller.
it gives javascript error in
jquery1.4.2.min.js file
The most likely cause for this is that something you are displaying on the page is different and you are performing an action that is causing the error. Can you supply enough of a sample from the rendered page to show what you are using jQuery for?
If we drag scripts from solution explorer to site.master it results in following output
<script type="text/javscript" src="../../scripts/jquery.min.js"></script>
The leading dots (..) are creating the problem. Putting source path in url.content or using /scripts instead of ../../scripts will solve the problem because these leading periods are forcing them to match some route in global.asax.

Resources