How to get the TLD with MapRoute? - asp.net-mvc

I have many host names on IIS that point to the same ASP.NET MVC app.
www.domain.com
www.domain.co.uk
www.domain.net
...
How can I get the top level domain (es. ".com") when I set a map route like the following?
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
' MapRoute takes the following parameters, in order:
' (1) Route name
' (2) URL with parameters
' (3) Parameter defaults
routes.MapRoute( _
"Default", _
"{controller}/{action}/{id}", _
New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional} _
)
End Sub

You need to create a custom route. Just inherit Route, override GetRouteData and include routing information which is for the TLD.
http://msdn.microsoft.com/en-us/library/system.web.routing.route.getroutedata.aspx
Example
The route:
public class RouteWithTld : Route
{
public RouteWithTld(string url, IRouteHandler routeHandler) : base(url, routeHandler)
{
}
public RouteWithTld(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler)
{
}
public RouteWithTld(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler)
{
}
public RouteWithTld(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var baseData = base.GetRouteData(httpContext);
int pos = httpContext.Request.Url.Host.LastIndexOf('.');
string tld;
if (pos == -1)
{
if (httpContext.Request.Url.Host == "localhost")
tld = "com";
else
{
throw new InvalidOperationException("You need to handle this case.");
}
}
else
tld = httpContext.Request.Url.Host.Substring(pos + 1);
baseData.Values.Add("tld", tld);
return baseData;
}
}
Mapping it 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 } // Parameter defaults
);
* */
var defaults =
new RouteValueDictionary(new {controller = "Home", action = "Index", id = UrlParameter.Optional});
routes.Add(new RouteWithTld("{controller}/{action}/{id}", defaults, new MvcRouteHandler()));
}
And accessing the parameter in the controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC. TLD: " + RouteData.Values["tld"];
return View();
}
public ActionResult About()
{
return View();
}
}

Related

How to make optional parameter url in Mvc

Dont't vote Negative if cant solve the prob because i know what you answer thats why iam here at the end.
Controller
[Route("{Name}")]
public ActionResult Details(int? id, string Name)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Menu menu = db.Menus.Find(id);
if (menu == null)
{
return HttpNotFound();
}
return View(menu);
}
Views
#Html.ActionLink("Details", "Details", new { id = item.Id, Name = item.MenuName })
Route.config.cs
route.MapMvcAttributeRoutes();
Output:
how to get output like this
localhost:2345/Blog instead of localhost:2345/Details/id=1?Name=Blog
You can't because the url in the RouteConfig has a specific format:
{controller}/{action}/{id}
To get the url you want, you may create public ActionResult Blog()
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "NoScript",
url : "noscript",
defaults : new { controller = "Home", action = "EnableJavaScript"}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",//the specific format
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Assuming you have a UserController with the following methods
// match http://..../arrivaler
public ActionResult Index(string username)
{
// displays the home page for a user
}
// match http://..../arrivaler/Photos
public ActionResult Photos(string username)
{
// displays a users photos
}
Route.config.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "User",
url: "{username}",
defaults: new { controller = "User", action = "Index" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "UserPhotos",
url: "{username}/Photos",
defaults: new { controller = "User", action = "Photos" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Test", action = "Index", id = UrlParameter.Optional }
);
}
public class UserNameConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
List<string> users = new List<string>() { "Bryan", "Stephen" };
// Get the username from the url
var username = values["username"].ToString().ToLower();
// Check for a match (assumes case insensitive)
return users.Any(x => x.ToLower() == username);
}
}
}
If the url is .../Arrivaler, it will match the User route and you will execute the Index() method in UserController (and the value of username will be "Arrivaler")
If the url is .../Arrivaler/Photos, it will match the UserPhotos route and you will execute the Photos() method in UserController (and the value of username will be "Arrivaler")
Note that the the sample code above hard codes the users, but in reality you will call a service that returns a collection containing the valid user names. To avoid hitting the database each request, you should consider using MemoryCache to cache the collection. The code would first check if it exists, and if not populate it, then check if the collection contains the username. You would also need to ensure that the cache was invalidated if a new user was added.

MVC Custom Routing Subdomain

I'm trying to build a "Tenant" Subdomain route that attaches to a MVC Area. In this case I have an Area called "Tenant" which has two controllers; Public and Admin. My custom Route is used to grab the Subdomain if it matches then route them to the proper Controller-Action-Area.
The base of this project came from the following
http://www.matrichard.com/post/asp.net-mvc-5-routing-with-subdomain
The problem I'm having is in the custom Subdomain Route. When I hit the Public/Index Route, the routeData is returning null and I see the following error. Although if the route is /admin it returns the correct routeData.
Server Error in '/' Application.
The matched route does not include a 'controller' route value, which is required.
It also seems to be always matching using RouteDebugger tool, is this a clue to my problem?
Examples Routes:
controller=Public action=Index, area=Tenant
http://tenant1.mydomain.com:8080/
http://tenant1.mydomain.com:8080/logon
controller=Admin action=Index, area=Tenant
http://tenant1.mydomain.com:8080/admin
http://tenant1.mydomain.com:8080/admin/edit
--
SubdomainRouteP.cs
public class SubdomainRouteP : Route
{
public string Domain { get; set; }
public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults): this(domain, url, defaults, new MvcRouteHandler())
{
}
public SubdomainRouteP(string domain, string url, object defaults): this(domain, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public SubdomainRouteP(string domain, string url, object defaults, IRouteHandler routeHandler): this(domain, url, new RouteValueDictionary(defaults), routeHandler)
{
}
public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
{
this.Domain = domain;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//
// routeData object returns null in some cases
//
var routeData = base.GetRouteData(httpContext);
var subdomain = httpContext.Request.Url.Host.Split('.').First();
string[] blacklist = { "www", "mydomain", "localhost" };
// This will ignore anything that is not a client tenant prefix
if (blacklist.Contains(subdomain))
{
return null; // Continue to the next route
}
// Why is this NULL?
if (routeData == null)
{
routeData = new RouteData(this, new MvcRouteHandler());
}
routeData.DataTokens["Area"] = "Tenant";
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
routeData.Values.Add("subdomain", subdomain);
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return routeData;
}
}
RouteConfig.cs
routes.Add("Admin_Subdomain", new SubdomainRouteP(
"{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
"admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));
routes.Add("Public_Subdomain", new SubdomainRouteP(
"{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
"{controller}/{action}/{id}",
new { controller = "Public", action = "Index", id = UrlParameter.Optional }));
// This is the MVC default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
The Url below gives me the following results from RouteDebugger. During test 1 and 2 the route still matches /admin.
Failed Test 1: http://tenant.mydomain.com/
Failed Test 2: http://tenant.mydomain.com/logon
Successful 3: http://tenant.mydomain.com/admin
Matches Url Defaults
True admin/{action}/{id} controller = Admin, action = Index
True {controller}/{action}/{id} controller = Public, action = Index
The post that you linked to has a bug: When a constraint or the URL does not match, the base.GetRouteData method will return null. In this case, adding the subdomain name to the route dictionary will obviously throw an exception. There should be a null guard clause before that line.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null)
{
routeData.Values.Add("client", httpContext.Request.Url.Host.Split('.').First());
}
return routeData;
}
As should be the case with your route. You need to ensure you return null in the case where the base class returns null (which indicates either the URL or a constraint did not match, and we need to skip processing this route).
Also, I am not sure if it makes any difference than adding the data directly to the DataTokens, but the MVC framework has an IRouteWithArea that can be implemented to configure the Area the route applies to.
public class SubdomainRouteP : Route, IRouteWithArea
{
public string Area { get; private set; }
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults): this(area, url, defaults, new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults): this(area, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults, IRouteHandler routeHandler): this(area, url, new RouteValueDictionary(defaults), routeHandler)
{
}
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
{
this.Area = area;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
// This will ignore anything where the URL or a constraint doesn't match
// in the call to base.GetRouteData().
if (routeData != null)
{
var subdomain = httpContext.Request.Url.Host.Split('.').First();
string[] blacklist = { "www", "mydomain", "localhost" };
// This will ignore anything that is not a client tenant prefix
if (blacklist.Contains(subdomain))
{
return null; // Continue to the next route
}
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
routeData.Values.Add("subdomain", subdomain);
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return routeData;
}
}
I can't figure out what you are trying to do with the domain parameter. The URL will most likely return something for domain. So, it seems like you should have a constraint in the first "{controller}/{action}/{id}" route or you will never have a case that will pass through to the default route. Or, you could use an explicit segment in the URL so you can differentiate it (the same way you did with your admin route).
routes.Add("Admin_Subdomain", new SubdomainRouteP(
"Tenant",
"admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));
routes.Add("Public_Subdomain", new SubdomainRouteP(
"Tenant",
"public/{action}/{id}",
new { controller = "Public", action = "Index", id = UrlParameter.Optional }));
// This is the MVC default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Another option would be to add another constructor parameter to pass in an explicit list of valid domains to check against.

Best way to format a query string in an asp.net mvc url?

I've noticed that if you sent a query string routevalue through asp.net mvc you end up with all whitespaces urlencoded into "%20". What is the best way of overriding this formatting as I would like whitespace to be converted into the "+" sign?
I was thinking of perhaps using a custom Route object or a class that derives from IRouteHandler but would appreciate any advice you might have.
You could try writing a custom Route:
public class CustomRoute : Route
{
public CustomRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler)
{ }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var path = base.GetVirtualPath(requestContext, values);
if (path != null)
{
path.VirtualPath = path.VirtualPath.Replace("%20", "+");
}
return path;
}
}
And register it like this:
routes.Add(
new CustomRoute(
"{controller}/{action}/{id}",
new RouteValueDictionary(new {
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}),
new MvcRouteHandler()
)
);

how to pass querystring in friendly url in asp.net mvc

I have the following action. I can hit this with
/basket/address?addressId=123
However i wonder how i can hit it with
/basket/address/123
public ActionResult Address(int addressId)
{
return RedirectToAction("Index");
}
my routes
routes.MapRoute(
"Default", // Route name
"{controller}.aspx/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Change:
public ActionResult Address(int addressId)
to
public ActionResult Address(int id)

How can I have lowercase routes in ASP.NET MVC?

How can I have lowercase, plus underscore if possible, routes in ASP.NET MVC? So that I would have /dinners/details/2 call DinnersController.Details(2) and, if possible, /dinners/more_details/2 call DinnersController.MoreDetails(2)?
All this while still using patterns like {controller}/{action}/{id}.
With System.Web.Routing 4.5 you may implement this straightforward by setting LowercaseUrls property of RouteCollection:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Also assuming you are doing this for SEO reasons you want to redirect incoming urls to lowercase (as said in many of the links off this article).
protected void Application_BeginRequest(object sender, EventArgs e)
{
//You don't want to redirect on posts, or images/css/js
bool isGet = HttpContext.Current.Request.RequestType.ToLowerInvariant().Contains("get");
if (isGet && !HttpContext.Current.Request.Url.AbsolutePath.Contains("."))
{
//You don't want to modify URL encoded chars (ex.: %C3%8D that's code to Í accented I) to lowercase, than you need do decode the URL
string urlLowercase = Request.Url.Scheme + "://" + HttpUtility.UrlDecode(HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);
//You want to consider accented chars in uppercase check
if (Regex.IsMatch(urlLowercase, #"[A-Z]") || Regex.IsMatch(urlLowercase, #"[ÀÈÌÒÙÁÉÍÓÚÂÊÎÔÛÃÕÄËÏÖÜÝÑ]"))
{
//You don't want to change casing on query strings
urlLowercase = urlLowercase.ToLower() + HttpContext.Current.Request.Url.Query;
Response.Clear();
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", urlLowercase);
Response.End();
}
}
}
These two tutorials helped when I wanted to do the same thing and work well:
http://www.coderjournal.com/2008/03/force-mvc-route-url-lowercase/
http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/
EDIT: For projects with areas, you need to modify the GetVirtualPath() method:
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var lowerCaseValues = new RouteValueDictionary();
foreach (var v in values)
{
switch (v.Key.ToUpperInvariant())
{
case "ACTION":
case "AREA":
case "CONTROLLER":
lowerCaseValues.Add(v.Key, ((string)v.Value).ToLowerInvariant());
break;
default:
lowerCaseValues.Add(v.Key.ToLowerInvariant(), v.Value);
break;
}
}
return base.GetVirtualPath(requestContext, lowerCaseValues);
}
If you happened to use ASP.NET Core, you probably should have a look at this:
Add the following line to the ConfigureServices method of the Startup class.
services.AddRouting(options => options.LowercaseUrls = true);
If you are using the UrlHelper to generate the link, you can simply specify the name of the action and controller as lowercase:
itemDelete.NavigateUrl = Url.Action("delete", "photos", new { key = item.Key });
Results in: /media/photos/delete/64 (even though my controller and action are pascal case).
I found this at Nick Berardi’s Coder Journal, but it did not have information on how to implement the LowercaseRoute class. Hence reposting here with additional information.
First extend the Route class to LowercaseRoute
public class LowercaseRoute : Route
{
public LowercaseRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
: base(url, defaults, constraints, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler) { }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData path = base.GetVirtualPath(requestContext, values);
if (path != null)
path.VirtualPath = path.VirtualPath.ToLowerInvariant();
return path;
}
}
Then modify the RegisterRoutes method of Global.asax.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new LowercaseRoute("{controller}/{action}/{id}",
new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
new MvcRouteHandler()));
//routes.MapRoute(
// "Default", // Route name
// "{controller}/{action}/{id}", // URL with parameters
// new { controller = "Home", action = "Index", id = "" } // Parameter defaults
//);
}
I would however like to know a way to use routes.MapRoute...
You can continue use the MapRoute syntax by adding this class as an extension to RouteCollection:
public static class RouteCollectionExtension
{
public static Route MapRouteLowerCase(this RouteCollection routes, string name, string url, object defaults)
{
return routes.MapRouteLowerCase(name, url, defaults, null);
}
public static Route MapRouteLowerCase(this RouteCollection routes, string name, string url, object defaults, object constraints)
{
Route route = new LowercaseRoute(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};
routes.Add(name, route);
return route;
}
}
Now you can use in your application's startup "MapRouteLowerCase" instead of "MapRoute":
public void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Url shortcuts
routes.MapRouteLowerCase("Home", "", new { controller = "Home", action = "Index" });
routes.MapRouteLowerCase("Login", "login", new { controller = "Account", action = "Login" });
routes.MapRouteLowerCase("Logout", "logout", new { controller = "Account", action = "Logout" });
routes.MapRouteLowerCase(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
This actually has two answers:
You can already do this: the route engine does case-insensitive comparison. If you type a lower-case route, it will be routed to the appropriate controller and action.
If you are using controls that generate route links (ActionLink, RouteLink, etc.) they will produce mixed-case links unless you override this default behavior.
You're on your own for the underscores, though...
Could you use the ActionName attribute?
[ActionName("more_details")]
public ActionResult MoreDetails(int? page)
{
}
I don't think case matters. More_Details, more_DETAILS, mOrE_DeTaILs in the URL all take you to the same Controller Action.

Resources