I have hosting and domain like that:
www.EXAMPLE.com
I've created few subdomains like that:
www.PAGE1.EXAMPLE.com
www.PAGE2.EXAMPLE.com
www.PAGE3.EXAMPLE.com
... etc...
All of these subdomains point to one and the same ASP.NET MVC 5 Application.
I want to make system which will load data depending of subdomain.
Example:
I have Article object which could be a Auto Review or Game review or Book Review etc...
I would like to www.auto.example.com load data where type of article is Auto, to www.book.example.com I would like to load data with type Book etc.
There will be many types of the pages.
What is best practise to do that?
The top level domain www.example.com should display something else. It would be main page for the others.
You can do this by writing a custom Route. Here's how (adapted from Is it possible to make an ASP.NET MVC route based on a subdomain?)
public class SubdomainRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var host = httpContext.Request.Url.Host;
var index = host.IndexOf(".");
string[] segments = httpContext.Request.Url.PathAndQuery.Split('/');
if (index < 0)
return null;
var subdomain = host.Substring(0, index);
string controller = (segments.Length > 0) ? segments[0] : "Home";
string action = (segments.Length > 1) ? segments[1] : "Index";
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", controller); //Goes to the relevant Controller class
routeData.Values.Add("action", action); //Goes to the relevant action method on the specified Controller
routeData.Values.Add("subdomain", subdomain); //pass subdomain as argument to action method
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
Add to the route table in Global.asax.cs like this:
routes.Add(new SubdomainRoute());
And your controller method:
public ActionResult Index(string subdomain)
{
//Query your database for the relevant articles based on subdomain
var viewmodel = MyRepository.GetArticles(subdomain);
Return View(viewmodel);
}
This is something I have wanted to do with ASP.NET MVC for a long time, but... This is not a concern that ASP.NET MVC is responsible for. This is a server concern (IIS). What you need to do is allow for wildcard subdomains on your IIS server and point them to your one application.
Then you can do something like this with the HttpContext:
HttpContext.Current.Request.Url.Host // user1.yourwebsite.com
Then you just need to parse that and push it into your ASP.NET MVC app anyway you see fit:
Push it into Session
Update the current route data and push a value in
Etc....
The choice is really up to you.
Note: The downside here is that this makes local development increasingly difficult, so you might want to mock up a way to fake a subdomain in your application.
I tried Paul Taylor answer above is pretty good but that didn't work entirely for me.
I use this implementation of Route class.
Add your custom domain into C:/Windows/System32/drivers/etc/hosts file
127.0.0.1 subdomain.localhost.com
DomainData.cs
public class DomainData
{
public string Protocol { get; set; }
public string HostName { get; set; }
public string Fragment { get; set; }
}
DomainRoute.cs
public class DomainRoute : Route
{
private Regex domainRegex;
private Regex pathRegex;
public string Domain { get; set; }
public DomainRoute(string domain, string url, RouteValueDictionary defaults)
: base(url, defaults, new MvcRouteHandler())
{
Domain = domain;
}
public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler)
{
Domain = domain;
}
public DomainRoute(string domain, string url, object defaults)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
Domain = domain;
}
public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler)
: base(url, new RouteValueDictionary(defaults), routeHandler)
{
Domain = domain;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
// Build regex
domainRegex = CreateRegex(Domain);
pathRegex = CreateRegex(Url);
// Request information
string requestDomain = httpContext.Request.Headers["host"];
if (!string.IsNullOrEmpty(requestDomain))
{
if (requestDomain.IndexOf(":") > 0)
{
requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
}
}
else
{
requestDomain = httpContext.Request.Url.Host;
}
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) +
httpContext.Request.PathInfo;
// Match domain and route
Match domainMatch = domainRegex.Match(requestDomain);
Match pathMatch = pathRegex.Match(requestPath);
// Route data
RouteData data = null;
if (domainMatch.Success && pathMatch.Success && requestDomain.ToLower() != "tg.local" &&
requestDomain.ToLower() != "tg.terrasynq.net" && requestDomain.ToLower() != "www.townsgossip.com" &&
requestDomain.ToLower() != "townsgossip.com")
{
data = new RouteData(this, RouteHandler);
// Add defaults first
if (Defaults != null)
{
foreach (KeyValuePair<string, object> item in Defaults)
{
data.Values[item.Key] = item.Value;
}
}
// Iterate matching domain groups
for (int i = 1; i < domainMatch.Groups.Count; i++)
{
Group group = domainMatch.Groups[i];
if (group.Success)
{
string key = domainRegex.GroupNameFromNumber(i);
if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
{
if (!string.IsNullOrEmpty(group.Value))
{
data.Values[key] = group.Value;
}
}
}
}
// Iterate matching path groups
for (int i = 1; i < pathMatch.Groups.Count; i++)
{
Group group = pathMatch.Groups[i];
if (group.Success)
{
string key = pathRegex.GroupNameFromNumber(i);
if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
{
if (!string.IsNullOrEmpty(group.Value))
{
data.Values[key] = group.Value;
}
}
}
}
}
return data;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return base.GetVirtualPath(requestContext, RemoveDomainTokens(values));
}
public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values)
{
// Build hostname
string hostname = Domain;
foreach (KeyValuePair<string, object> pair in values)
{
hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString());
}
// Return domain data
return new DomainData
{
Protocol = "http",
HostName = hostname,
Fragment = ""
};
}
private Regex CreateRegex(string source)
{
// Perform replacements
source = source.Replace("/", #"\/?");
source = source.Replace(".", #"\.?");
source = source.Replace("-", #"\-?");
source = source.Replace("{", #"(?<");
source = source.Replace("}", #">([a-zA-Z0-9_\-]*))");
return new Regex("^" + source + "$");
}
private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values)
{
var tokenRegex =
new Regex(
#"({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?({[a-zA-Z0-9_\-]*})*\.?\/?");
Match tokenMatch = tokenRegex.Match(Domain);
for (int i = 0; i < tokenMatch.Groups.Count; i++)
{
Group group = tokenMatch.Groups[i];
if (group.Success)
{
string key = group.Value.Replace("{", "").Replace("}", "");
if (values.ContainsKey(key))
values.Remove(key);
}
}
return values;
}
}
Reference: http://www.howtobuildsoftware.com/index.php/how-do/UaR/aspnet-mvc-5-domain-routing-in-mvc5
Related
I'm trying to route to areas based on a subdomain while having the URL not include the Area parameter.
I'd like to be able to go the following routes for example
example1.domain.com/login
example1.domain.com/landingpage
example2.domain.com/login
example2.domain.com/landingpage
I'd like to have each subdomain route to a different Area. I've tried following this post Is it possible to make an ASP.NET MVC route based on a subdomain? which led me to http://benjii.me/2015/02/subdomain-routing-in-asp-net-mvc/. But I can't seem to figure out how to not have the Area parameter in the URL.
How can I get the correct URL schema I'm looking for? {subdomain}.domain.com/{action}/{id}
To use a route in an area, you need to set the DataTokens["area"] parameter to the correct area in addition to doing the other subdomain routing. Here is an example:
SubdomainRoute
public class SubdomainRoute : Route
{
// Passing a null subdomain means the route will match any subdomain
public SubdomainRoute(string subdomain, string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
this.Subdomain = subdomain;
}
public string Subdomain { get; private set; }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
// A subdomain specified as a query parameter takes precedence over the hostname.
string subdomain = httpContext.Request.Params["subdomain"];
if (subdomain == null)
{
string host = httpContext.Request.Headers["Host"];
int index = host.IndexOf('.');
if (index >= 0)
subdomain = host.Substring(0, index);
}
// Check if the subdomain matches this route
if (this.Subdomain != null && !this.Subdomain.Equals(subdomain, StringComparison.OrdinalIgnoreCase))
return null;
var routeData = base.GetRouteData(httpContext);
if (routeData == null) return null; // The route doesn't match - exit early
// Store the subdomain as a datatoken in case it is needed elsewhere in the app
routeData.DataTokens["subdomain"] = subdomain;
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
// Read the current query string and cascade it to the current URL only if it exists
object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"];
if (subdomainParam != null)
values["subdomain"] = subdomainParam;
return base.GetVirtualPath(requestContext, values);
}
}
RouteCollectionExtensions
These extension methods allow you to register routes in the non-area part of your application to work with subdomains.
public static class RouteCollectionExtensions
{
public static SubdomainRoute MapSubdomainRoute(
this RouteCollection routes,
string subdomain,
string url,
object defaults = null,
object constraints = null,
string[] namespaces = null)
{
return MapSubdomainRoute(routes, null, subdomain, url, defaults, constraints, namespaces);
}
public static SubdomainRoute MapSubdomainRoute(
this RouteCollection routes,
string name,
string subdomain,
string url,
object defaults = null,
object constraints = null,
string[] namespaces = null)
{
var route = new SubdomainRoute(subdomain, url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
}
AreaRegistrationContextExtensions
These extension methods allow you to register area routes to work with subdomains.
public static class AreaRegistrationContextExtensions
{
public static SubdomainRoute MapSubdomainRoute(
this AreaRegistrationContext context,
string url,
object defaults = null,
object constraints = null,
string[] namespaces = null)
{
return MapSubdomainRoute(context, null, url, defaults, constraints, namespaces);
}
public static SubdomainRoute MapSubdomainRoute(
this AreaRegistrationContext context,
string name,
string url,
object defaults = null,
object constraints = null,
string[] namespaces = null)
{
if ((namespaces == null) && (context.Namespaces != null))
{
namespaces = context.Namespaces.ToArray<string>();
}
var route = context.Routes.MapSubdomainRoute(name,
context.AreaName, url, defaults, constraints, namespaces);
bool flag = (namespaces == null) || (namespaces.Length == 0);
route.DataTokens["UseNamespaceFallback"] = flag;
route.DataTokens["area"] = context.AreaName;
return route;
}
}
Usage
To get the URL pattern {subdomain}.domain.com/{action}/{id} to use a specific specific area, you just need to register it as part of the AreaRegistration.
public class AppleAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Apple";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapSubdomainRoute(
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
}
NOTE: Your example URL has a limitation that you can only use a single controller per area. controller is a required route value, so you either need to supply it in the URL ({controller}/{action}/{id}) or default it as the above example - the latter case means you can only have 1 controller.
Of course, you will also need to setup the DNS server to use subdomains.
i need to create a sub domain in the web site when ever a new user is registered in my site.his user name wil be used as subdomain in the site
can this be achieved by URLRewriting in asp.net MVC . any one have any idea about it please let me know
"a sample project would be a plus point"
You don't need to do use URLRewriting in MVC, you could extend the existing routing functionality of the framework. Here is a code sample taken from the asp.net forums:
public class SubdomainRoute : Route
{
public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) {}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place.
string subdomain = httpContext.Request.Params["subdomain"]; // A subdomain specified as a query parameter takes precedence over the hostname.
if (subdomain == null) {
string host = httpContext.Request.Headers["Host"];
int index = host.IndexOf('.');
if (index >= 0)
subdomain = host.Substring(0, index);
}
if (subdomain != null)
routeData.Values["subdomain"] = subdomain;
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"];
if (subdomainParam != null)
values["subdomain"] = subdomainParam;
return base.GetVirtualPath(requestContext, values);
}
}
You can also create an extension method for RouteCollection that will make mapping a subdomain route easier.
I'm trying to setup a custom route in MVC to take a URL from another system in the following format:
../ABC/ABC01?Key=123&Group=456
The 01 after the second ABC is a step number this will change and the Key and Group parameters will change. I need to route this to one action in a controller with the step number key and group as paramters. I've attempted the following code however it throws an exception:
Code:
routes.MapRoute(
"OpenCase",
"ABC/ABC{stepNo}?Key={key}&Group={group}",
new {controller = "ABC1", action = "OpenCase"}
);
Exception:
`The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.`
You cannot include the query string in the route. Try with a route like this:
routes.MapRoute("OpenCase", "ABC/ABC{stepNo}",
new { controller = "ABC1", action = "OpenCase" });
Then, on your controller add a method like this:
public class ABC1 : Controller
{
public ActionResult OpenCase(string stepno, string key, string group)
{
// do stuff here
return View();
}
}
ASP.NET MVC will automatically map the query string parameters to the parameters in the method in the controller.
When defining routes, you cannot use a / at the beginning of the route:
routes.MapRoute("OpenCase",
"/ABC/{controller}/{key}/{group}", // Bad. Uses a / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}", // Good. No / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Try this:
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}",
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Then your action should look as follows:
public ActionResult OpenCase(int key, int group)
{
//do stuff here
}
It looks like you're putting together the stepNo and the "ABC" to get a controller that is ABC1. That's why I replaced that section of the URL with {controller}.
Since you also have a route that defines the 'key', and 'group', the above route will also catch your initial URL and send it to the action.
There is no reason to use routing based in querystring in new ASP.NET MVC project. It can be useful for old project that has been converted from classic ASP.NET project and you want to preserve URLs.
One solution can be attribute routing.
Another solution can be in writting custom routing by deriving from RouteBase:
public class MyOldClassicAspRouting : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request.Headers == null) //for unittest
return null;
var queryString = httpContext.Request.QueryString;
//add your logic here based on querystring
RouteData routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", "...");
routeData.Values.Add("action", "...");
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
And register your custom routing class
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.Add(new MyOldClassicAspRouting ());
}
The query string arguments generally are specific of that controller and of that specific application logic.
So it will better if this isn't written in route rules, that are general.
You can embed detection of query string on action argument in the following way.
I think that is better to have one Controller for handling StepNo.
public class ABC : Controller
{
public ActionResult OpenCase(OpenCaseArguments arg)
{
// do stuff here
// use arg.StepNo, arg.Key and arg.Group as You need
return View();
}
}
public class OpenCaseArguments
{
private string _id;
public string id
{
get
{
return _id;
}
set
{
_id = value; // keep original value;
ParseQueryString(value);
}
}
public string StepNo { get; set; }
public string Key { get; set; }
public string Group { get; set; }
private void ParseQueryString(string qs)
{
var n = qs.IndexOf('?');
if (n < 0) return;
StepNo = qs.Substring(0, n); // extract the first part eg. {stepNo}
NameValueCollection parms = HttpUtility.ParseQueryString(qs.Substring(n + 1));
if (parms.Get("Key") != null) Key = parms.Get("Key");
if (parms.Get("Group") != null) Group = parms.Get("Group");
}
}
ModelBinder assign {id} value to the id field of OpenCaseArguments. The set method handle querystring split logic.
And keep routing this way. Note routing get your querystring in id argument.
routes.MapRoute(
"OpenCase",
"ABC/OpenCase/{id}",
new {controller = "ABC", action = "OpenCase"}
);
I have used this method for getting multiple fields key value on controller action.
Hy, in my Global.asax I've this rule:
// Home
routes.MapRoute("Home",
"{lang}/",
new { lang = "ita", controller = "Home", action = "Index" },
new { lang = new LanguageRouteConstraint() }
);
And my LanguageRouteConstraint class:
public class LanguageRouteConstraint : IRouteConstraint
{
#region Membri di IRouteConstraint
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if ((routeDirection == RouteDirection.IncomingRequest) && (parameterName.ToLower(CultureInfo.InvariantCulture) == "lang"))
{
try
{
string lang = Convert.ToString(values[parameterName]);
// Language check on db
Language currLang = new Language().Get(lang);
if (currLang != null)
{
// Here I'd like to "save (in session|querystring|....)" the id
return true;
}
}
catch
{
return false;
}
}
return false;
}
#endregion
}
And my controller
public class HomeController : Controller
{
public ActionResult Index(string lang)
{
// I would get the language ID without interrogating the data base
}
}
In HomeController-->Index method I would get the language ID without interrogating the data base because I have already done in LanguageRouteConstraint.
I'm sorry for my poor English
Thanks in advance.
You could do the following:
In the Match method insert the language ID in the RouteValueDictionary: values["lang"] = theLanguageId;
Turn your action's signature into something like ActionResult Index(int lang)
You can access the current session via httpContext on your language constraint
Language currLang = new Language().Get(lang);
if (currLang != null)
{
httpContext.Session["Lang"] = id
return true;
}
then in your controller you could use a property
public int Language { get return int.Parse(Session["Lang"].ToString()); }
Anyone understand why the following doesn't work?
What I want to do is copy current route data plus whatever I add via an anonymous object into new routedata when forming new links on the view.
For example if I have the parameter "page" as a non route path (i.e. so it overflows the route path and its injected into the method parameter if a querystring is present) e.g.
public ActionResult ChangePage(int? page) { }
and I want the View to know the updated page when building links using helpers. I thought the best way to do this is with the following:
public ActionResult ChangePage(int? page)
{
if(page.HasValue)
RouteData.Values.Add("Page", page);
ViewData.Model = GetData(page.HasValue ? page.Value : 1);
}
Then in the view markup I can render my next, preview, sort, showmore (any links relevant) with this overload:
public static class Helpers
{
public static string ActionLinkFromRouteData(this HtmlHelper helper, string linkText, string actionName, object values)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary();
foreach(var routeValue in helper.ViewContext.RouteData.Values)
{
if(routeValue.Key != "controller" && routeValue.Key != "action")
{
routeValueDictionary[routeValue.Key] = routeValue;
}
}
foreach(var prop in GetProperties(values))
{
routeValueDictionary[prop.Name] = prop.Value;
}
return helper.ActionLink(linkText, actionName, routeValueDictionary;
}
private static IEnumerable<PropertyValue> GetProperties(object o)
{
if (o != null) {
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(o);
foreach (PropertyDescriptor prop in props) {
object val = prop.GetValue(o);
if (val != null) {
yield return new PropertyValue { Name = prop.Name, Value = val };
}
}
}
}
private sealed class PropertyValue
{
public string Name { get; set; }
public object Value { get; set; }
}
}
I have posted the code only to illustrate the point. This doesn't work and doesn't feel right... Pointers?
Pass the page info into ViewData?
PagedResultsInfo (or something) sounds like a class you could write too... we do.