频道栏目
读书频道 > web开发 > .NET > ASP.NET MVC 4框架揭秘
3.1.3 ControllerBuilder
2012-12-22 10:00:42     我来说两句
收藏   我要投稿

本文所属图书 > ASP.NET MVC 4框架揭秘

针对最新版本的ASP.NET MVC 4,深入剖析底层框架从请求接收到响应回复的整个处理流程(包括URL路由、Controller的激活、Model元数据的解析、Model的绑定、Model的验证、Action的执行、View的呈现和ASP.NET We...  立即去当当网订购

用于激活Controller对象的ControllerFactory最终通过System.Web.Mvc.ControllerBuilder注册到ASP.NET MVC应用中。如下面的代码所示,ControllerBuilder定义了一个静态只读属性Current返回当前ControllerBuilder对象,这是针对整个Web应用的全局对象。两个SetControllerFactory方法重载用于注册ControllerFactory的类型或者实例,而GetControllerFactory方法返回一个具体的ControllerFactory对象。
public class ControllerBuilder
{
    public IControllerFactory GetControllerFactory();

    public void SetControllerFactory(Type controllerFactoryType);
    public void SetControllerFactory(IControllerFactory controllerFactory); 

    public HashSet<string>   DefaultNamespaces { get; }
    public static ControllerBuilder  Current { get; }
}

具体来说,如果我们注册的是ControllerFactory的类型,那么GetControllerFactory在执行的时候会通过对注册类型的反射(调用Activator的静态方法CreateInstance)来创建具体的ControllerFactory(系统不会对创建的Controller进行缓存)。如果注册的是一个具体的ControllerFactory对象,该对象直接从GetControllerFactory返回。

通过第2章“URL路由”的介绍我们知道,被ASP.NET路由系统进行拦截处理后会生成一个用于封装路由信息的RouteData对象,而目标Controller的名称就包含在通过该RouteData的Values属性表示的RouteValueDictionary对象中,对应的Key为“controller”。而在默认的情况下,这个作为路由数据的名称只能帮助我们解析出Controller的类型名称,如果在不同的命名空间下定义了多个同名的Controller类,会导致激活系统无法确定具体的Controller的类型从而抛出异常。

为了解决这个问题,我们必须为定义了同名Controller类型的命名空间设置不同的优先级。具体来说有两种提升命名空间优先级的方式。第一种方式就是在调用RouteCollection如下所示的扩展方法MapRoute时指定一个命名空间的列表。通过第2章“URL路由”的介绍我们知道,通过这种方式指定的命名空间列表会保存在Route对象的DataTokens属性表示的RouteValueDictionary字典中,对应的Key为“Namespaces”。
public static class RouteCollectionExtensions
{
    //其他成员
    public static Route MapRoute(this RouteCollection routes, string name,
        string url, string[] namespaces);   
    public static Route MapRoute(this RouteCollection routes, string name,
        string url, object defaults, string[] namespaces);   
    public static Route MapRoute(this RouteCollection routes, string name,
        string url, object defaults, object constraints, string[] namespaces);
}

另一种提升命名空间优先级的方式就是将其添加到当前的ControllerBuilder中的默认命名空间列表中。从上面的给出的ControllerBuilder的定义可以看出,它具有一个HashSet<string>类型的只读属性DefaultNamespaces代表了这么一个默认命名空间列表。对于这两种不同的命名空间优先级提升方式,前者(通过路由注册)指定命名空间具有更高的优先级。

实例演示:如何提升命名空间的优先级(S303,S304,S305)

为了让读者对如何提升命名空间优先级有一个深刻的印象,我们来进行一个简单的实例演示。在一个ASP.NET MVC应用创建两个同名的HomeController类,如下面的代码片段所示,这两个HomeController类分别定义在命名空间Artech.MvcApp和Artech.MvcApp. Controllers之中,而Index操作返回的是一个将Controller类型全名作为内容的System.Web. Mvc.ContentResult对象。
namespace Artech.MvcApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return this.Content(this.GetType().FullName);
        }
    }
}

namespace Artech.MvcApp
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return this.Content(this.GetType().FullName);
        }
    }
}

现在我们直接运行该Web应用。由于具有多个Controller与注册的路由规则相匹配,这会导致Controller激活系统无法确定哪个类型的Controller应该被选用,所以会出现如图3-3所示的错误。(S303)


 

目前定义了HomeController的两个命名空间具有相同的优先级,现在将其中一个定义在当前ControllerBuilder的默认命名空间列表中以提升匹配优先级。如下面的代码片段所示,在Global.asax 的Application_Start方法中,将命名空间“Artech.MvcApp.Controllers”添加到当前ControllerBuilder的DefaultNamespaces属性所示的命名空间列表中。
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        //其他操作
        ControllerBuilder.Current.DefaultNamespaces
            .Add("Artech.MvcApp.Controllers");
    }
}

对于同时匹配注册的路由规则的两个HomeController来说,由于“Artech.MvcApp.Controllers”命名空间具有更高的匹配优先级,所有定义其中的HomeController会被选用,这可以通过如图3-4所示的运行结果看出来。(S304)


 

为了检验在路由注册时指定的命名空间和作为当前ControllerBuilder的命名空间哪个具有更高匹配优先级,修改定义在“App_Start/RouteConfig.cs”中的路由注册代码,如下面的代码片段所示,在调用RouteTable的静态属性Routes的MapRoute方法进行路由注册的时候指定了命名空间(“Artech.MvcApp”)。
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        //其他操作
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index",
                               id = UrlParameter.Optional },
            namespaces: new string[] { "Artech.MvcApp" }
        );
    }
}

再次运行我们的程序会在浏览器中得到如图3-5所示的结果,从中可以看出定义在命名空间“Artech.MvcApp”中的HomeController被最终选用,可见较之作为当前ControllerBuilder的默认命名空间,在路由注册过程中执行的命名空间具有更高的匹配优先级,前者可以视为后者的一种后备。(S305)


 

在路由注册时指定的命名空间比当前ControllerBuilder的默认命名空间具有更高的匹配优先级,但是对于这两个集合中的所有命名空间却具有相同的匹配优先级。换句话说,用于辅助解析Controller类型的命名空间分为三个梯队,分别简称为路由命名空间、ConrollerBuilder命名空间和Controller类型命名空间。如果前一个梯队不能正确解析出目标Controller的类型,则后一个梯队的命名空间将作为后备,反之,如果根据某个梯队的命名空间进行解析得到多个匹配的Controller类型,会直接抛出异常。

针对Area的路由对象的命名空间

针对某个Area的路由映射是通过相应的AreaRegistration进行注册的,具体来说是在AreaRegistration的RegisterArea方法中调用AreaRegistrationContext对象的MapRoute方法进行注册的。如果在调用MapRoute方法中指定了表示命名空间的字符串,它将自动作为注册的路由对象的命名空间,否则会将AreaRegistration的命名空间加上“.*”后缀得到的字符串作为路由对象的命名空间。

这里所说的“路由对象的命名空间”存在于Route对象的DataTokens属性表示的RouteValueDictionary对象中,对应的Key为“Namespaces”,Value就是一个包含字符串数组的命名空间列表。通过第2章“URL路由”的介绍,Route对象的DataTokens属性包含的变量会转移到由它生成的RouteData的同名属性中。

除此之外,在调用AreaRegistrationContext的MapRoute方法时还会在注册Route对象的DataTokens属性中添加一个Key为“UseNamespaceFallback”的条目,它表示是否采用后备命名空间对Controller类型进行解析。如果注册的路由对象具有命名空间(调用MapRoute方法时指定了命名空间或者对应的AreaRegistration类型定义在某个命名空间下),该条目的值为False,否则为True。该条目同样反映在通过该Route对象生成的RouteData对象的DataTokens属性中。

在解析Controller真实类型的过程中,会先使用RouteData包含的命名空间。如果解析失败,则通过由RouteData的DataTokens属性得到的这个名为“UseNamespaceFallback”的变量值来判断是否使用“后备”命名空间进行解析。具体来说,如果该值为True或者不存在,则先通过当前ControllerBuilder的命名空间解析,如果失败则忽略命名空间直接采用类型名称进行匹配,否则会因找不到匹配的Controller而直接抛出异常。

我们通过具体的例子来说明这个问题。在一个ASP.NET MVC应用中通过Area添加向导创建一个名称为Admin的Area,此时IDE会默认为我们添加了如下一个AdminAreaRegistration类型。
NamespaceMvcApp.Areas.Admin
{
    public class AdminAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get{return "Admin";}
        }
        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Admin_default",
                "Admin/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

AdminAreaRegistration类型定义在命名空间MvcApp.Areas.Admin中。现在我们在该Area中添加如下一个HomeController,在默认的Action方法Index中,我们从当前RouteData的DataTokens中提取这个名为“UseNamespaceFallback”的变量值,并将它和解析出来的Controller类型名称写入当前HttpResponse而最终呈现在客户端浏览器中。在默认情况下,添加的HomeController类型被定义在MvcApp.Areas.Admin.Controllers命名空间下,现在我们刻意将命名空间改为MvcApp.Areas.Controllers。
namespaceMvcApp.Areas.Controllers
{
    public class HomeController : Controller
    {
        public void Index()
        {
            Response.Write(string.Format("UseNamespaceFallback: {0}<br/>",
                RouteData.DataTokens["UseNamespaceFallback"]));
            Response.Write(string.Format("Controller Type: {0}<br/>",
                this.GetType().FullName));
        }
    }
}

现在我们在浏览器中通过匹配的URL(/Admin/Home/Index)来访问Area为Admin的HomeController的Index操作,会得到如图3-6所示的HTTP状态为“404,Not Found”的错误。这就是因为在对Controller类型进行解析的时候是严格按照对应的AreaRegistration所在的命名空间来进行的,很显然在这个范围内是不可能找得到对应的Controller类型的。(S306)


 

但是如果我们去掉AdminAreaRegistration的命名空间,那么将会导致路由变量UseNamespaceFallback的值变为True,这会促使Controller激活系统选择“后备”的命名空间。由于整个Web应用中仅仅定义了唯一匹配的MvcApp.Areas.Controllers.HomeController,很显然这个Controller会被激活,如图3-7所示的程序运行结果也说明了这一点。(S307)


3

您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:3.1.2 ControllerFactory
下一篇:3.1.4 Controller的激活与URL路由
相关文章
图文推荐
排行
热门
最新书评
文章
下载
读书
特别推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站