MVC实现动态二级域名
前段时间,一个朋友问我ASP.NET MVC下实现动态二级域名的问题。跟他聊了一些解决方案,这里也总结一下,以供参考。
相信大家都发现类似58同城这样的网站,成都的网址是cd.58.com 上海的是sh.58.com类似的上千个网站,其实没有那么多个网站,域名前面那部分就是泛域名解析,相当于是传递一个参数,所有的域名实际上访问的都是一个网站,仅仅是传递了不一样的参数显示不一样的内容。
比如网站主域名入口为:www.58.com
当成都的用户登录时,解析到:cd.58.com
当上海的用户登录时,则解析到:sh.58.com
首先想到的是对Url的重写:(这在ASP.NET中也是常用的手法。网上有关于UrlRewrite的实现,这里不再重复。)
还有就是MVC 应用程序中的典型URL模式,这里只讨论MVC应用程序URL模式下的动态二级域名实现,测试实例下载。
1.定义DomainData、DomainRoute类
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) { // 构造 regex domainRegex = CreateRegex(Domain); pathRegex = CreateRegex(Url); // 请求信息 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 domainMatch = domainRegex.Match(requestDomain); Match pathMatch = pathRegex.Match(requestPath); // 路由数据 RouteData data = null; if (domainMatch.Success && pathMatch.Success) { data = new RouteData(this, RouteHandler); // 添加默认选项 if (Defaults != null) { foreach (KeyValuePair<string, object> item in Defaults) { data.Values[item.Key] = item.Value; } } // 匹配域名路由 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; } } } } // 匹配域名路径 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) { // 获得主机名 string hostname = Domain; foreach (KeyValuePair<string, object> pair in values) { hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString()); } // Return 域名数据 return new DomainData { Protocol = "http", HostName = hostname, Fragment = "" }; } private Regex CreateRegex(string source) { // 替换 source = source.Replace("/", @"\/?"); source = source.Replace(".", @"\.?"); source = source.Replace("-", @"\-?"); source = source.Replace("{", @"(?<"); source = source.Replace("}", @">([a-zA-Z0-9_]*))"); return new Regex("^" + source + "$", RegexOptions.IgnoreCase); } private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values) { Regex 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; } } public class DomainData { public string Protocol { get; set; } public string HostName { get; set; } public string Fragment { get; set; } }
2.修改RouteConfig,增加如下代码
routes.Add( "DomainRoute", new DomainRoute( "{CityNameUrl}.weiz.com", "{controller}/{action}/{id}", new { CityNameUrl = "", controller = "City", action = "Index", id = "" } ));
3.增加CityController控制类
public class CityController : Controller { public ActionResult Index() { var cityName = RouteData.Values["CityNameUrl"]; ViewBag.CityName = cityName; return View(); } }
4.发布网站,并修改相关配置
方式一:修改host,我们通过修改host文件,来实现对二级域名的,只能通过一个一个增加解析如:
#host文件
127.0.0.1 www.weiz.com
127.0.0.1 a.weiz.com
127.0.0.1 b.weiz.com
127.0.0.1 c.weiz.com
方式二:增加泛域名解析,配置DNS服务,也就是让你的域名支持泛解析 (Windows Server 才会有,其他的Windows系统只能修改尝试修改Host文件,便于测试) 请看我的另一篇文章《域名泛解析设置》
5. 效果
需要注意:如果你的服务器上有多个站点,则主站不要绑定主机头。其他二级域名的子系统,需要绑定主机头。
作者:章为忠
如有问题,可以微信:18618243664 联系我,非常感谢。
关注我的微信公众号,获取相关的 源代码及视频资料。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?