我们继续谈细节。
我们注意到ASP.NETMVC项目都包含一个Global.asax文件,这个文件代码很少,只包括三个方法,即RegisterGlobalFilters、RegisterRoutes、Application_Start。
我们看RegisterRoutes方法的定义(代码段1)。
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
);
}
代码段1中的routes.MapRoute语句是路由映射的具体实现,我们在MVC源码中看其定义(代码段2)。
[SuppressMessage("Microsoft.Design","CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",Justification = "This is not a regular URL as it may contain specialrouting characters.")]
public static Route MapRoute(this RouteCollection routes, string name,string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
Route route = new Route(url, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionaryUncached(defaults),
Constraints = CreateRouteValueDictionaryUncached(constraints),
DataTokens = new RouteValueDictionary()
};
ConstraintValidation.Validate(route);
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens[RouteDataTokenKeys.Namespaces] = namespaces;
}
routes.Add(name, route);
return route;
}
代码段2中route用一个MvcRouteHandler来实例化,即封装了MVC路由处理机制。我们完全可以自定义一个IRouteHandler的实现,例如代码段3:
publicclass MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHandler();
}
}
代码段 3
代码段3中的MyHandler定义见代码段4。
publicclass MyHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.Write("HELLO");
}
}
代码段 4
我们现在把代码段1中的routes.MapRoute这句代码,更换为代码段5。
routes.Add("MyRoute", newRoute("My", new MyRouteHandler()));
代码段 5
好了,现在我们按下F5进行调试,在浏览器地址栏输入http://localhost:48580/My,回车,看见图1了没?浏览器显示我们在代码段4中希望输出的“HELLO”。这里我们自己定义了一个新的路由规则,即当我们访问http://localhost:48580/My的时候,就会调用我们自定义的MyRouteHandler,成就感有木有?有木有?当然,我们自定义的这条路由规则URL只是静态匹配,它非常简单,以至于简单到不能再简单,以至于毫无用途,但我们毕竟通过这个例子,明白了MVC路由系统的大概端倪。当然,代码段5中的静态URL(My)也可以更换成动态URL,比如“{My}/{Love}”,这样当我们访问http://localhost:48580/hello/kitty时,就会调用我们自定义的路由。可见,ASP.NET的路由系统把花括号“{}”中的内容看作一个变量(或者占位符),之间用斜杠“/”分割,并以此来匹配浏览器输入的URL字符串。
图 1
前面我们谈控制器时,没有提到控制器是如何实例化的,下面我们进入这个话题。MVC框架中的controller由控制器工厂进行实例化(代码段6),而系统预置的控制器工厂是DefaultControllerFactory,它实现了IControllerFactory接口。
// Instantiate the controller andcall Execute
factory =ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext,controllerName);
代码段 6
我们看看DefaultControllerFactory的具体实现(代码段7),这段代码中最核心的语句是【Type controllerType =GetControllerType(requestContext, controllerName);[1] IController controller = GetControllerInstance(requestContext,controllerType); [2]】,这两句的功能是:首先根据路由传递过来的Controller名称得到控制器的类型;再根据控制器类型,创建IController的具体实例。
public virtual IControllerCreateController(RequestContext requestContext, string controllerName)
{
if (requestContext == null)
{
throw newArgumentNullException("requestContext");
}
if(String.IsNullOrEmpty(controllerName) &&!requestContext.RouteData.HasDirectRouteMatch())
{
throw newArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
Type controllerType =GetControllerType(requestContext, controllerName);
IController controller =GetControllerInstance(requestContext, controllerType);
return controller;
}
代码段 7
我们跟踪进入语句[1]的具体定义(代码段8),可以看到其中有三句很扎眼的注释【// first search in the current route's namespace collection】、【// then search in the application's default namespace collection】、【// if all else fails, search every namespace】,这些注释清楚的告诉我们MVC框架搜索控制器的顺序。
protected internal virtual TypeGetControllerType(RequestContext requestContext, string controllerName)
{
if (requestContext == null)
{
throw newArgumentNullException("requestContext");
}
if(String.IsNullOrEmpty(controllerName) &&
(requestContext.RouteData ==null || !requestContext.RouteData.HasDirectRouteMatch()))
{
throw newArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
RouteData routeData =requestContext.RouteData;
if (routeData != null &&routeData.HasDirectRouteMatch())
{
returnGetControllerTypeFromDirectRoute(routeData);
}
// first search in the currentroute's namespace collection
object routeNamespacesObj;
Type match;
if(routeData.DataTokens.TryGetValue(RouteDataTokenKeys.Namespaces, outrouteNamespacesObj))
{
IEnumerablerouteNamespaces = routeNamespacesObj as IEnumerable;
if (routeNamespaces != null&& routeNamespaces.Any())
{
HashSetnamespaceHash = new HashSet(routeNamespaces,StringComparer.OrdinalIgnoreCase);
match =GetControllerTypeWithinNamespaces(routeData.Route, controllerName,namespaceHash);
// the UseNamespaceFallbackkey might not exist, in which case its value is implicitly "true"
if (match != null ||false.Equals(routeData.DataTokens[RouteDataTokenKeys.UseNamespaceFallback]))
{
// got a match or theroute requested we stop looking
return match;
}
}
}
// then search in the application'sdefault namespace collection
if(ControllerBuilder.DefaultNamespaces.Count > 0)
{
HashSet namespaceDefaults = newHashSet(ControllerBuilder.DefaultNamespaces,StringComparer.OrdinalIgnoreCase);
match =GetControllerTypeWithinNamespaces(routeData.Route, controllerName,namespaceDefaults);
if (match != null)
{
return match;
}
}
// if all else fails, search everynamespace
returnGetControllerTypeWithinNamespaces(routeData.Route, controllerName, null /* namespaces*/);
}
代码段 8
这三条注释所作用的语句中都用到了GetControllerTypeWithinNamespaces方法,其实现见代码段9。
private TypeGetControllerTypeWithinNamespaces(RouteBase route, string controllerName,HashSet namespaces)
{
// Once the master list ofcontrollers has been created we can quickly index into it
ControllerTypeCache.EnsureInitialized(BuildManager);
ICollectionmatchingTypes = ControllerTypeCache.GetControllerTypes(controllerName,namespaces);
switch (matchingTypes.Count)
{
case 0:
// no matching types
return null;
case 1:
// single matching type
return matchingTypes.First();
default:
// multiple matching types
throwCreateAmbiguousControllerException(route, controllerName, matchingTypes);
}
}
代码段 9