软件下载 | 资讯教程 | 最近更新 | 下载排行 | 一键转帖 | 发布投稿
您的位置:最火下载站 > 网络编程 > ASP教程 > 配合域名实现URL Routing

配合域名实现URL Routing

  经常有朋友问我,如何对域名作URL Routing,他们可能希望根据域名(或自域名)来获得一些值,最终影响Controller,Action或某些参数的选择。之前我只是简单地说“扩展一下ASP.NET Routing吧”,而现在由于自己也正好需要使用这个功能,便实现了一个扩展。使用下来,效果不错。

  ASP.NET Routing已经实现了针对Path的匹配和构造,而如今我们是希望在这个基础上提供额外的Domain支持,而扩展的结果依旧是对URL的Routing支持。这种增加职责而不改变其外观的需求让我想到了装饰器模式。也就是说,如果我们的目标是构造一个RouteDomain,那么它可能就是这样的:

public class DomainRoute : RouteBase
{
private DomainParser m_domainParser;

public RouteBase InnerRoute { get; private set; }

public string Pattern { get; private set; }

public DomainRoute(RouteBase innerRoute, string pattern)
{
this.InnerRoute = innerRoute;
this.Pattern = pattern;
this.m_domainParser = new DomainParser(pattern);
}

public override RouteData GetRouteData(HttpContextBase httpContext)
{
...
}

public override VirtualPathData GetVirtualPath(
RequestContext requestContext,
RouteValueDictionary values)
{
...
}
}  DomainRoute会封装一个内部Route对象,将匹配或构造Path的任务交给这个内部对象“之余”,再把对Domain的处理工作交给DomainParser进行,而DomainRoute的主要逻辑,实际上便是将上两者进行组合。如GetRouteData方法:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
// match domain
var domainValues = this.m_domainParser.Match(httpContext.Request.Url);
if (domainValues == null) return null;

// match path
var routeData = this.InnerRoute.GetRouteData(httpContext);
if (routeData == null) return null;

// merge
routeData.Values.CopyFrom(domainValues);
routeData.Route = this;

return routeData;
}
  GetRouteData的功能是匹配URL,分三步走,第一步是匹配Domain,第二步是使用内部Route匹配Path,然后通过常用辅助方法中的CopyFrom方法,把一个字典中的所有数据复制到RouteData中并返回即可。可见,由于我们把任务进行了细小地拆分,每个类的职责均非常简单,可以进行独立的单元测试,因此代码也可以显得非常简单易懂。

  ASP.NET Routing的功能是构造URL和构造URL,因此我们还需要实现一个GetVirtualPath方法:

public override VirtualPathData GetVirtualPath(
RequestContext requestContext,
RouteValueDictionary values)
{
// bind domain
var domain = this.m_domainParser.Bind(requestContext.RouteData.Values, values);
if (domain == null) return null;

// bind path
var innerValues = new RouteValueDictionary();
innerValues.CopyFrom(values).RemoveKeys(this.m_domainParser.Segments);
var pathData = this.InnerRoute.GetVirtualPath(requestContext, innerValues);
if (pathData == null) return null;

// merge
pathData.Route = this;
pathData.VirtualPath = Merge(requestContext.HttpContext, domain, pathData.VirtualPath);

return pathData;
}

private static string Merge(HttpContextBase context, string domain, string path)
{
var domainWithSlash = domain + "/";
var ignoreDomain = context.Request.Url.ToString().StartsWith(domainWithSlash);
return ignoreDomain ? path : domainWithSlash + path;
}
  与GetRouteData的逻辑类似,GetVirtualPath方法首先根据所得的Route Value组装出Domain,再使用内部Route对象构造一个Path,并将其合并(Merge)起来。略有不同的是,再调用内部Route对象之前,必须去除所有用于Domain的部分(Segment),否则这些会出现在URL的QueryString部分中。在合并Domain和Path的时候,也有些许逻辑。Merge方法会判断当前请求与目标的Domain,如果两者相同,则会返回一个相对路径,省略URL前完整的域名。

  方便起见,我们也可以使用一个扩展方法来辅助DomainRoute的构造:

public static class RouteExtensions
{
public static DomainRoute WithDomain(this RouteBase route, string pattern)
{
return new DomainRoute(route, pattern);
}
}
  最后还是进行几个单元测试吧。首先,我们可以捕获整个URL中的数据(关于MockHelper请参考这里):

[Fact]
public void Capture_Request_Scheme()
{
Mock<HttpRequestWrapper> mockRequest;
var mockContext = MockHelper.MockRequest("http://jeffz.space.cnblogs.com/posts/2009", out mockRequest);

var route = new Route("{section}/{data}", null);
var domainRoute = route.WithDomain("{scheme}://{user}.{area}.{*domain}");

var routeData = domainRoute.GetRouteData(mockContext.Object);
Assert.Equal("http", routeData.Values["scheme"]);
Assert.Equal("space", routeData.Values["area"]);
Assert.Equal("cnblogs.com", routeData.Values["domain"]);
Assert.Equal("jeffz", routeData.Values["user"]);
Assert.Equal("posts", routeData.Values["section"]);
Assert.Equal("2009", routeData.Values["data"]);
}
  其次,对于无法匹配的URL,也能够返回null:

[Fact]
public void Specified_Request_Scheme()
{
Mock<HttpRequestWrapper> mockRequest;
var mockContext = MockHelper.MockRequest("http://space.cnblogs.com/Home", out mockRequest);

var sslRoute = new Route("{controller}", null).WithDomain("https://{sub_domain}.{*domain}");
var sslData = sslRoute.GetRouteData(mockContext.Object);
Assert.Null(sslData);
}
  最后,我们也可以成功地构造整段URL:

[Fact]
public void Build_Url()
{
Mock<HttpRequestWrapper> mockRequest;
var mockContext = MockHelper.MockRequest("http://wiki.cnblogs.com/Home/Index", out mockRequest);

var route = new Route("{controller}/{action}", null).WithDomain("{scheme}://{area}.{*domain}");
var routeData = route.GetRouteData(mockContext.Object);
var requestContext = new RequestContext(mockContext.Object, routeData);

Assert.Equal("http", routeData.Values["scheme"]);
Assert.Equal("wiki", routeData.Values["area"]);
Assert.Equal("cnblogs.com", routeData.Values["domain"]);
Assert.Equal("Home", routeData.Values["controller"]);
Assert.Equal("Index", routeData.Values["action"]);

// same domain
var values = new RouteValueDictionary(new { controller = "Account", action = "List" });
var pathData = route.GetVirtualPath(requestContext, values);
Assert.Equal("Account/List", pathData.VirtualPath);

// different domain
var spaceRoute = new Route("{controller}/{action}", null).WithDomain("http://{user}.{area}.{*domain}");
var spaceHash = new { controller = "Account", action = "List", area = "space", user = "jeffz" };
var spaceValues = new RouteValueDictionary(spaceHash);
var spacePathData = spaceRoute.GetVirtualPath(requestContext, spaceValues);
Assert.Equal("http://jeffz.space.cnblogs.com/Account/List", spacePathData.VirtualPath);
}
  整个DomainRoute类就这样完成了,除了单元测试外,总共也就60多行代码,但已经实现了我们所需要的常用功能。当然,目前还不支持“端口”,如果您需要的话,也可以修改代码,让其为您所用。

  不过,虽然DomainRoute已经准备好了,但是在视图中“构造”URL时的辅助方法还需要一些额外的实现。这个下次再说吧。

  如果您有什么其他的想法或建议也请提出,我们可以一起讨论一下。

原文:http://www.cnblogs.com/JeffreyZhao/archive/2009/08/25/url-routing-with-domain.html

    相关阅读
    栏目导航
    推荐软件