ASP.NET MVC动态二级域名及DNS(泛解析配置)

动态二级域名的实现:

应用场景:目前产品要实现SaaS功能,因为工作需要实现二级域名:www.{CompanyUrl}.xxx.com

假设产品主域名入口为:www.xxx.com

当a公司租户登录时:www.a.xxx.com

当b公司租户登录时: www.b.xxx.com

首先想到的是对Url的重写:(网上有关于UrlRewrite的实现。在ASP.NET中这也是常用的手法。)

Route简介:ASP.NET路由可以不用映射到网站特定文件的URL.由于该 URL 不必映射到文件,因此可以使用对用户操作进行描述因而更易于被用户理解的 URL。.NET Framework 3.5 SP1已经包含了ASP.NET Routing引擎。现在微软已经在ASP.NET WebForms 4.0中增加了对Routing引擎更好的支持,它使用表达式构造器进行双向Routing。

MVC 应用程序中的典型 URL 模式——来自MSDN

MVC 应用程序中用于路由的 URL 模式通常包括 {controller} 和 {action} 占位符。

当收到请求时,会将其路由到 UrlRoutingModule 对象,然后路由到 MvcHandler HTTP 处理程序。 MvcHandler HTTP 处理程序通过向 URL 中的控制器值添加后缀“Controller”以确定将处理请求的控制器的类型名称,来确定要调用的控制器。URL 中的操作值确定要调用的操作方法。

MVC项目中添加路由,Global.asax 文件默认的MVC 路由的代码。

默认配置:

View Code
 1         public static void RegisterRoutes(RouteCollection routes)
 2         {
 3             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 4 
 5             routes.MapRoute(
 6                 "Default", // Route name
 7                 "{controller}/{action}/{id}", // URL with parameters
 8                 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
 9             );
10 
11         }
12         protected void Application_Start()
13         {
14             AreaRegistration.RegisterAllAreas();
15 
16             RegisterGlobalFilters(GlobalFilters.Filters);
17             RegisterRoutes(RouteTable.Routes);
18         }

 

涉及类参考

说明
Route 表示 Web 窗体或 MVC 应用程序中的路由。
RouteBase 用作表示 ASP.NET 路由的所有类的基类。
RouteTable 存储应用程序的路由。
RouteData 包含所请求路由的值。
RequestContext 包含有关对应于路由的 HTTP 请求的信息。
RouteValueDictionary 提供用于存储路由 ConstraintsDefaultsDataTokens 对象的方法。
VirtualPathData 提供用于从路由信息生成 URL 的方法。

因为目前采用的是ASP.NET MVC 3进而可以利用扩展Route的方式实现。

首先定义DomainData、DomainRoute类

代码如下:

DomainRoute类:

View Code
  1     public class DomainRoute : Route
  2     {
  3         private Regex domainRegex;
  4         private Regex pathRegex;
  5 
  6         public string Domain { get; set; }
  7 
  8         public DomainRoute(string domain, string url, RouteValueDictionary defaults)
  9             : base(url, defaults, new MvcRouteHandler())
 10         {
 11             Domain = domain;
 12         }
 13 
 14         public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
 15             : base(url, defaults, routeHandler)
 16         {
 17             Domain = domain;
 18         }
 19 
 20         public DomainRoute(string domain, string url, object defaults)
 21             : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
 22         {
 23             Domain = domain;
 24         }
 25 
 26         public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler)
 27             : base(url, new RouteValueDictionary(defaults), routeHandler)
 28         {
 29             Domain = domain;
 30         }
 31 
 32         public override RouteData GetRouteData(HttpContextBase httpContext)
 33         {
 34             // 构造 regex
 35             domainRegex = CreateRegex(Domain);
 36             pathRegex = CreateRegex(Url);
 37 
 38             // 请求信息
 39             string requestDomain = httpContext.Request.Headers["host"];
 40             if (!string.IsNullOrEmpty(requestDomain))
 41             {
 42                 if (requestDomain.IndexOf(":") > 0)
 43                 {
 44                     requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
 45                 }
 46             }
 47             else
 48             {
 49                 requestDomain = httpContext.Request.Url.Host;
 50             }
 51             string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
 52 
 53             // 匹配域名和路由
 54             Match domainMatch = domainRegex.Match(requestDomain);
 55             Match pathMatch = pathRegex.Match(requestPath);
 56 
 57             // 路由数据
 58             RouteData data = null;
 59             if (domainMatch.Success && pathMatch.Success)
 60             {
 61                 data = new RouteData(this, RouteHandler);
 62 
 63                 // 添加默认选项
 64                 if (Defaults != null)
 65                 {
 66                     foreach (KeyValuePair<string, object> item in Defaults)
 67                     {
 68                         data.Values[item.Key] = item.Value;
 69                     }
 70                 }
 71 
 72                 // 匹配域名路由
 73                 for (int i = 1; i < domainMatch.Groups.Count; i++)
 74                 {
 75                     Group group = domainMatch.Groups[i];
 76                     if (group.Success)
 77                     {
 78                         string key = domainRegex.GroupNameFromNumber(i);
 79 
 80                         if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
 81                         {
 82                             if (!string.IsNullOrEmpty(group.Value))
 83                             {
 84                                 data.Values[key] = group.Value;
 85                             }
 86                         }
 87                     }
 88                 }
 89 
 90                 // 匹配域名路径
 91                 for (int i = 1; i < pathMatch.Groups.Count; i++)
 92                 {
 93                     Group group = pathMatch.Groups[i];
 94                     if (group.Success)
 95                     {
 96                         string key = pathRegex.GroupNameFromNumber(i);
 97 
 98                         if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
 99                         {
100                             if (!string.IsNullOrEmpty(group.Value))
101                             {
102                                 data.Values[key] = group.Value;
103                             }
104                         }
105                     }
106                 }
107             }
108 
109             return data;
110         }
111 
112         public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
113         {
114             return base.GetVirtualPath(requestContext, RemoveDomainTokens(values));
115         }
116 
117         public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values)
118         {
119             // 获得主机名
120             string hostname = Domain;
121             foreach (KeyValuePair<string, object> pair in values)
122             {
123                 hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString());
124             }
125 
126             // Return 域名数据
127             return new DomainData
128             {
129                 Protocol = "http",
130                 HostName = hostname,
131                 Fragment = ""
132             };
133         }
134 
135         private Regex CreateRegex(string source)
136         {
137             // 替换
138             source = source.Replace("/", @"\/?");
139             source = source.Replace(".", @"\.?");
140             source = source.Replace("-", @"\-?");
141             source = source.Replace("{", @"(?<");
142             source = source.Replace("}", @">([a-zA-Z0-9_]*))");
143 
144             return new Regex("^" + source + "$");
145         }
146 
147         private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values)
148         {
149             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_]*})*-?\.?\/?");
150             Match tokenMatch = tokenRegex.Match(Domain);
151             for (int i = 0; i < tokenMatch.Groups.Count; i++)
152             {
153                 Group group = tokenMatch.Groups[i];
154                 if (group.Success)
155                 {
156                     string key = group.Value.Replace("{", "").Replace("}", "");
157                     if (values.ContainsKey(key))
158                         values.Remove(key);
159                 }
160             }
161 
162             return values;
163         }
164     }
165     public class DomainData
166     {
167         public string Protocol { get; set; }
168         public string HostName { get; set; }
169         public string Fragment { get; set; }
170     }

DomainData 类:

View Code
1 public class DomainData
2     {
3         public string Protocol { get; set; }
4         public string HostName { get; set; }
5         public string Fragment { get; set; }
6     }

 

然后在Global中配置路由

   1:  routes.Add("DomainRoute", new DomainRoute( 
   2:  "www.{companyUrl}.wenthink.com",     // Domain with parameters 
   3:  "{controller}/{action}/{id}",    // URL with parameters 
   4:  new { companyUrl= "", controller = "Home", action = "Login", id = "" }  // Parameter defaults 
   5:  ));

 

效果图:

a 公司用户登录时:

QQ截图20130410014512 QQ截图20130410014425

b公司用户登录时:

QQ截图20130410014602 QQ截图20130410014709

当用户尝试错误时:可自定义提示页面

QQ截图20130410014804

基础支持:域名支持泛解析

然后要做的是:配置DNS服务,也就是让你的域名支持泛解析 眨眼 (Windows Server 才会有,其他的Windows系统可以尝试修改Host文件,便于测试)

Step By Step

1 2
3 4
5 6
7 8
9 10
反向查询区域11 创建反向区域12
13 14
绑定指针  
15 16

完成上面的操作,基本可以实现DNS的泛解析了.当然如果没有绑定域名的话,只能修改Host文件来进行操作;

本机测试的情况下需要把Host文件中添加当前IP地址 所映射的域名,如本文中的wenthink.com

备注:以上解决了域名访问的问题,但是会存在Session跨域访问的丢失的现象.本例中采用了CrossDomainCookie的方式解决.当然不是唯一的解决方案. 方法很简单,这里就不多说明了,需要的可以自己Google一下 :-)

参考资料:http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx

            http://www.cnblogs.com/cyq1162/archive/2010/10/15/1851773.html

以上为个人学习摘要,如有错误,欢迎指正!!

下期:ASP.NET管道机制及IIS的工作原理浅析.

上个草图先 :-P

posted @ 2013-04-10 13:36 wenthink 阅读(...) 评论(...) 编辑 收藏