ASP.NET MVC Route Default values - asp.net-mvc-2

i defined two routes in global.asax like below
context.MapRoute("HomeRedirect", "",
new
{
controller = "Home",
action = "redirect"
});
context.MapRoute("UrlResolver", "{culture}/some",
new
{
culture = "en-gb",
controller = "someController",
action = "someAction"
},
new
{
culture = new CultureRouteConstraint()
});
according to above definition, when user request mysite.com/ redirect action of HomeController should be called and in that:
public class HomeController : Controller
{
public ActionResult Redirect()
{
return RedirectToRoute("UrlResolver");
}
}
i want to redirect user to second defined route on above, so also i specified default values for that and some Constraint for each of those. but when RedirectToRoute("UrlResolver") turns, no default values passed to routeConstraints on second route and No route in the route table matches the supplied values shows.
update
my CultureRouteConstraint:
public class CultureRouteConstraint : IRouteConstraint
{
bool IRouteConstraint.Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
try
{
var parameter = values[parameterName] as string;
return (someCondition(parameter));
}
catch
{
return false;
}
}
}
now values parameter haven't culture key/value, but route parameter have that.

The actual route specified for your second rule is "{culture}/some", so your redirect would have to be, for example, to "/EN-us/some" rather than to the name of the rule.

The implementation of HomeController.Redirect() doesn't seem to add any additional value, so why include the route named "HomeRedirect" at all?
How about just deleting it and letting your route named "UrlResolver" handle the requests. You can configure the defaults as you like.
You might add a "catch all" route below the "UrlResolver" route to catch cases where the CultureRouteConstraint() doesn't match the given URL.

Related

#Url.Action getting ?Length=2 appended

I have this at the top of each of several translations of the "Terms of Use" page:
<li>English</li>
<li>Deutsch</li>
<li>Français</li>
<li>Italiano</li>
<li>Nederlands</li>
<li>Maygar</li>
<li>Español</li>
<li>简体中文</li>
<li>European Português</li>
<li>Português</li>
This is the action that should handle the clicks:
public class TermsController : Controller
{
public ActionResult Index(string id)
{
switch (id)
{
case "de":
return View("de");
case "fr":
return View("fr");
case "it":
return View("it");
case "nl":
return View("nl");
case "hu":
return View("hu");
case "es":
return View("es");
case "zh":
return View("zh");
case "pt":
return View("pt");
case "pt-pt":
return View("pt-pt");
default:
return View();
}
}
and these are my routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Terms",
"{controller}/{id}",
new { controller = "Terms", action = "Index" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
routes.MapRoute(
"ThankYou",
"{controller}/{action}/{email}/{id}"
);
}
From the main (i.e., English) Terms page, the first (i.e., English) link looks correct:
http://localhost:65391/Terms/
Why do the other (i.e., foreign) generated URLs look like this?
http://localhost:65391/Terms/?Length=2
Also, oddly, if I manually type in
http://localhost:65391/Terms/de
for example, and go to the Terms page in German, then the first hyperlink (i.e., back to the English Terms page) looks like this:
http://localhost:65391/Terms/de
Go here to see the actual site:
http://inrix.com/traffic/terms
You are using an overload of the Url.Action which treats the third argument as the routeValues object.
From the MSDN:
routeValues
Type: System.Object
An object that contains the parameters
for a route. The parameters are retrieved through reflection by
examining the properties of the object. The object is typically
created by using object initializer syntax.
So you have passed strings "de", "fr" as the third argument so MVC have taken its properties and made key value pairs: that is where the Length=2 is coming, because the string class has one property Length and the value is 2 for your strings.
You can fix this easily with passing an anonymous object wrapping your strings:
<li>English</li>
<li>Deutsch</li>
<li>Français</li>
...
Notes:
your annonymous object property name id should match your route segment name id and controller parameter name id
you need to expicilty pass new { id = "" } in the default case otherwise MVC will use the already given route values. This is what you have seen in the http://localhost:65391/Terms/de case. So the English link became http://localhost:65391/Terms/de because MVC already found the id value in the URL which was de and automatically reused it.
Last Note the correct spelling is Magyar and not Maygar

Alter MVC Routing with dynamic prefix while maintaining backwards url compatibility

As it is not, I have a site where you must come in on a single url and a cookie is set to track which customer you are affiliated with. I want to change this so that certain controllers only use a url like this:
/{friendlyName}/{controller}/{index}/{id}
that friendly name is unique and lets me select the correct customer without using the cookie kludge.
I have controllers: Home, Redirect that I do not want the friendly name part of (and possibly more).
I have a few others that fit this category that I would like to move into their own areas. How can I not include the areas as valid friendly names? For instance, I have a controller that services up content in an iframe called Framed. currently, a url for this looks like /Framed/action/id. I could put this in an area called Framed with a controller the same name as the action, and I should still be able to maintain the same url.
For the controller Error I want the friendly name to be optional
I have other controllers that I want the friendly name to be required: SignIn, SignOut, Account
Once I have the routing, the problem is altering the code so that my redirects maintain the friendlyurl. Any ideas on how to do that?
My problem is just coming up with a good plan of attack on how to change the routing of my site. I must maintain backwards compatibility of some of the urls - namely anything I don't want the friendly url part of, including the controllers I discussed slitting into their own areas. I'm looking for any good suggests on how to lay this out and go about altering the changes.
To accomplish your objectives, you will need a combination of routes and RouteConstraints. Also, you will need to enforce rules that a friendlyName is unique, and is different from the names of any controllers or areas.
The following routes should be sufficient in RegisterRoutes() in Global.asax.cs:
routes.MapRoute(
"WithFriendlyName",
"{friendlyName}/{controller}/{index}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { friendlyName = new MustBeFriendlyName() }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = new MustNotRequireFriendlyName() }
);
The RouteConstraints should look something like this:
using System;
using System.Web;
using System.Web.Routing;
namespace Examples.Extensions
{
public class MustBeFriendlyName : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if this is a valid friendlyName
// MUST BE CERTAIN friendlyName DOES NOT MATCH ANY
// CONTROLLER NAMES OR AREA NAMES
var _db = new DbContext();
return _db.FriendlyNames.FirstOrDefault(x => x.FriendlyName.ToLowerInvariant() ==
values[parameterName].ToString().ToLowerInvariant()) != null;
}
}
public class MustNotRequireFriendlyName : IRouteConstraint
{
private const string controllersRequiringFriendlyNames =
"SignIn~SignOut~Account";
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if this controller does NOT require a friendlyName
return controllersRequiringFriendlyNames.ToLowerInvariant()
.Contains(values[parameterName].ToString().ToLowerInvariant());
}
}
}
This should get you started.
As far as the URLs generated by your redirects, if the routing is set up correctly, the URL generation should follow, so that the only changes you are likely to need are those to insure {friendlyName} is being passed.
You probably will have to add some additional routes and constraints as you get further into your changes.
Just wanted to add to this, as the optional prefix been biting me for the past couple of days. While I want to use the solution provided by #counsellorben, I also needed to be able to address the routes by the same name, which is impossible when using 2 routes.
It took me some headscratching, but finally the solution actually seemed very simple. I just needed to create an intermediate aggregate route:
public class AggregateRoute : RouteBase
{
private readonly RouteBase[] _routes;
public AggregateRoute(params RouteBase[] routes)
{
_routes = routes;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData routeData = null;
foreach (var route in _routes)
{
routeData = route.GetRouteData(httpContext);
if (routeData != null) break;
}
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData virtualPath = null;
foreach (var route in _routes)
{
virtualPath = route.GetVirtualPath(requestContext, values);
if (virtualPath != null) break;
}
return virtualPath;
}
}
This allows me to do:
routes.Add(
"RouteName",
new AggregateRoute(
new Route("{path}", new MvcRouteHandler()),
new Route("{prefix}/{path}", new MvcRouteHandler())
)
);
Which enables resolving either route by the same name, which is impossible when adding both routes separately:
Url.RouteLink("RouteName", new RouteValueDictionary{
new{path="some-path"}});
Url.RouteLink("RouteName", new RouteValueDictionary{
new{path="some-prefix/some-path"}});

How to setup an asp.net mvc route with two optional parameters, one being a paramarray value?

I want to make the following asp.net mvc routes:
http://somedomain.com/user/search/500?Users=1,2,3,4
http://somedomain.com/user/search/500
http://somedomain.com/user/search?Users=1,2,3,4
http://somedomain.com/user/search
User would match to the controller, search would match to the action method. The optional parameter 500 would match to you guessed it an optional parameter in the action method. The optional querystring of Users would match to an optional array parameter in the action method.
What would be the best way going about setting these up? A custom ActionFilterAttribute? Two different action methods? Multiple route entries in my routescollection?
Any info would be greatly appreciated.
I would define the following route:
routes.MapRoute(
"Default",
"{controller}/{action}/{someparam}",
new { controller = "Users", action = "Search", id = UrlParameter.Optional }
);
and then write a custom model binder for a string array:
public class StringArrayModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value != null)
{
return value.AttemptedValue.Split(',');
}
return base.BindModel(controllerContext, bindingContext);
}
}
finally I would have the controller action defined like this:
public ActionResult Search(
[ModelBinder(typeof(StringArrayModelBinder))] string[] users,
string someparam
)
{
...
}
and if you wanted this custom model binder to apply to all actions that have an array of string as action argument you could declare it in Application_Start:
ModelBinders.Binders.Add(typeof(string[]), new StringArrayModelBinder());
and then your controller action will simply become:
public ActionResult Search(string[] users, string someparam)
{
...
}
I ended up creating a custom actionfilterattribute that took the querystring Users from the request and converted it into a list of longs which i then placed into an actionparameter. The parameter for 500 was simply set to optional in both the route and the actionmethod.

How can host name be included in MVC2 route mapping?

I've got 3 domain names all pointed at the same MVC2 application. What I've got now is the homecontroller acting as a traffic cop and redirecting to controllers and views for the specific host name. But I don't like the URI result this causes...
ex:
www.webhost1.com/webhost1/imagegallery
www.webhost2.com/webhost2/imagegallery
I'd prefer to have:
www.webhost1.com/imagegallery
Is there a way to define the routes in global.asax that would include the host name in the routing evaluation so that the URI looks less redundant?
Aha!, it took a bit of fiddling (and a peak in and old book) but I think I've solved it.
You need to create a custom route constraint.
This is the one I made quickly:
public class hostnameConstraint : IRouteConstraint
{
protected string _hostname;
public hostnameConstraint (string hostname)
{
_hostname = hostname;
}
bool IRouteConstraint.Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (httpContext.Request.Url.Host == _hostname)
return true;
return false;
}
}
Then you simply add it to your routes and specify which hostname you want the route to apply to. like so:
routes.MapRoute(
"ImageGallery", "{controller}/{action}",
new { controller = "Home", action = "Index"},
new { hostname = new hostnameConstraint("webhost1.com") }
);
routes.MapRoute(
"ImageGallery", "{controller}/{action}",
new { controller = "Home", action = "Index"},
new { hostname = new hostnameConstraint("webhost2.com") }
);
and so on and so forth. I don't know how your routes are layed out, but the point is that now you can have seperate routes for the hostnames. Which should enable you to do what you're after.

Can my MVC2 app specify route constraints on Query String parameters?

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.
For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.
I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "OtherController", action = "OtherAction" },
new RouteValueDictionary { { "args", "vendor" } });
Can I write a route that takes the query string into account? If not, do you have any other ideas?
Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.
I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".
QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.
As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:
public class QueryStringConstraint : IRouteConstraint
{
private readonly Regex _regex;
public QueryStringConstraint(string regex)
{
_regex = new Regex(regex, RegexOptions.IgnoreCase);
}
public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// check whether the paramname is in the QS collection
if(httpContext.Request.QueryString.AllKeys.Contains(parameterName))
{
// validate on the given regex
return _regex.Match(httpContext.Request.QueryString[parameterName]).Success;
}
// or return false
return false;
}
}
Now you can use this in your routes:
routes.MapRoute("object-contact",
"{aanbod}",
/* ... */,
new { pagina = new QueryStringConstraint("some|constraint") });
You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:
public ActionResult Foo(string id, string script, string vendorname)
{
// the id parameter will be bound from the default route token
// script and vendorname parameters will be bound from the request string
...
}
UPDATE:
If you don't know the name of the query string parameters that will be passed you could loop through them:
foreach (string key in Request.QueryString.Keys)
{
string value = Request.QueryString[key];
}
This post is old, but couldn't you write a route before your default route
this would only catch routes with "vendor" in the args
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "VendorController", action = "OtherAction" },
new {args=#".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args
);
And This would catch the rest
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "DisplayController", action = "OtherAction" }
);
Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.
I tried this out and it worked for me.