学习ASP.NET MVC5框架揭秘笔记-ASP.NET路由(十一)

3.对现有物理文件的路由

在成功注册路由的情况下,如果我们按照传统的方式访问一个现存的物理文件,在请求地址满足某个Route的路由规则,ASP.NET是否还能正常实施路由呢?我们不妨通过实例来测试一下。为了让针对某个物理文件的访问地址也满足注册路由对象的路由模板采用的URL模式,我们需要按照如下的方式在进行路由注册时将表示约束的参数设置为Null。

public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } };
            var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
            RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}","~/weather.aspx", false, defaults, null, dataTokens);
        }
    }

当通过传统的方式来访问存放于跟目录下的Weather.aspx页面时,页面上并没有参数,而如果发生了路由,基于页面的RouteData的各项属性都不可能为空。

如果请求URL对应着一个现存的物理文件的路径,ASP.NET会不会总是自动忽略路由呢?实则不然,不对现有文件实施路由仅仅是默认采用的行为而已,是否对现有文件实施路由取决于代表全局路由表的RouteCollection对象的RouteExistingFiles属性(该属性默认情况下为False)。

我们可以将此属性设置为True使ASP.NET路由系统忽略现有物理文件的存在,让它总是按照注册的路由表进行路由表进行路由。为了演示这种情况,我们对Global.asax文件作了如下改动,在进行路由注册之前将RouteTable的Routes属性代表的RouteCollection对象的RouteExistingFiles属性设置为True。

public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.RouteExistingFiles = true;
            var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } };
            var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
            RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}","~/weather.aspx", false, defaults, null, dataTokens);
        }
    }

依旧是针对Weather.aspx页面的访问却得到了不一样的结果。在这里里面的参数值就都是默认值了。

通过上面的介绍我们知道,作为路由对象集合的RouteCollection和RouteBase均具有一个布尔类型的RouteExistingFiles属性,用以控制是否对现有物理文件实施路由,他们的默认值分别是False和True。由上面的实例演示我们知道,当请求与RouteCollection的某个Route对象的路由规则相匹配的情况下,RouteCollection本身的RouteExistingFiles属性会改变GetRouteData的值,使该方法只有在RouteExistingFiles属性值为False的情况下才会返回一个RouteData对象,否则直接返回Null。

我们现在需要讨论的是另一个问题:Route对象自身的RouteExistingFiles属性值对于自身的GetRouteData方法及它所在的RouteCollection对象的GetRouteData又会造成什么样的影响呢?

对于一个Route对象来说,他自身的GetRouteData方法不受其RouteExistingFiles属性值的影响,也就是说GetRouteData能否返回一个具体的RouteData对象完全取决于定义的路由规则与请求相匹配。如果一个RouteCollection包含唯一的Route对象,那么它的GetRouteData方法只有同时满足如下3个条件才能返回一个具体的RouteData对象。

1.RouteCollection自身的RouteExistingFiles属性为True。

2.Route对象的RouteExistingFiles也为True。

3.Route对象的路由规则与请求相匹配。

也就是说Route自身的RouteExistingFiles属性对于自身的路由没有影响,该属性最终是给RouteCollection使用的。

我们创建一个空的ASP.NET应用,并在添加的默认Web页面Default.aspx的后台文件中定义如下一个GetRouteData方法。该方法根据指定的参数返回一个RouteData对象,其中枚举类型的参数routeOrCollection决定返回的RouteData是调用Route对象的GetRouteData方法生成的还是调用RouteCollection的GetRouteData方法生成的,参数routeExistingFiles4Collection和routeExistingFiles4Route则分别控制着RouteCollection和Route对象的RouteExistingFiles属性。

public partial class Default : System.Web.UI.Page
    {
        public enum RouteOrRouteCollection
        {
            Route,
            RouteCollection
        }

        public RouteData GetRouteData(RouteOrRouteCollection routeOrCollection,
        bool routeExistingFiles4Collection, bool routeExistingFiles4Route)
        {
            Route route = new Route("{areaCode}/{days}", new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } }, null);
            route.RouteExistingFiles = routeExistingFiles4Route;
            HttpContextBase context = CreateHttpContext();

            if (routeOrCollection == RouteOrRouteCollection.Route)
            {
                return route.GetRouteData(context);
            }

            RouteCollection routes = new RouteCollection();
            routes.Add(route);
            routes.RouteExistingFiles = routeExistingFiles4Collection;
            return routes.GetRouteData(context);
        }

        private static HttpContextBase CreateHttpContext()
        {
            HttpRequest request = new HttpRequest("~/weather.aspx", "http://localhost:3721/weather.aspx", null);
            HttpResponse response = new HttpResponse(new StringWriter());
            HttpContext context = new HttpContext(request, response);
            HttpContextBase contextWrapper = new HttpContextWrapper(context);
            return contextWrapper;
        }
    }

在上面定义的这个GetRouteData方法中创建的Route对象分别采用我们熟悉的路由模板“{areaCode}/{days}”,并且两个变量均有默认值。如果需要返回Route对象自身生成的RouteData对象,我们直接调用其GetRouteData方法,否则创建一个仅仅包含该Route对象的RouteCollection对象并调用其GetRouteData方法。调用GetRouteData方法传人的参数是我们手工创建的HttpContextWrapper对象,它的请求URL(“http://localhost:3721/weather.aspx”)与Route对象创建的路由模板想匹配。

由于需要验证针对现有物理文件的路由,所以我们在应用的根目录创建一个名为Weather.aspx的空页面,同时将应用发布的端口设置为3721。然后我们在Default.aspx页面的主体部分定义如下的HTML,它会将RouteCollection和Route的RouteExistingFiles属性在不同组合下调用各自GetRouteData方法的返回值通过表格的形式呈现出来(具体来说,如果返回值不为空则输出“RouteData”,否则输出“Null”)。

<form id="form1" runat="server">
    <table class="table table-bordered">
        <thead>
          <tr>
            <th>RouteCollection.RouteExistingFiles</th>
            <th colspan="2">True</th>
            <th colspan="2">False</th>
          </tr>
          <tr>
            <th>Route.RouteExistingFiles</th>
            <th>True</th>
            <th>False</th>
            <th>True</th>
            <th>False</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Route.GetRouteData()</td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.Route,true,true) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.Route,true,false) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.Route,false,true) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.Route,false,false) == null ? "Null":"RouteData" %></td>
          </tr>
          <tr>
            <td>RouteCollection.GetRouteData()</td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.RouteCollection,true,true) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.RouteCollection,true,false) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.RouteCollection,false,true) == null ? "Null":"RouteData" %></td>
            <td><%=this.GetRouteData(RouteOrRouteCollection.RouteCollection,false,false) == null ? "Null":"RouteData" %></td>
          </tr>
        </tbody>
  </table>
</form>

运行程序后我们会发现Route对象的GetRouteData方法不会受自身RouteExistingFiles属性影响。在请求与路由规则匹配的情况下,RouteCollection只有在自身RouteExistingFiles属性和Route对象的RouteExistingFiles属性同时为True的情况下才会返回一个具体的RouteData对象。

4.注册路由忽略地址

RouteTable的静态属性Routes返回的RouteCollection对象代表针对整个应用的全局路由表。如果我们将该对象的RouteExistingFiles属性设置为True,ASP.NET路由系统将会对所有抵达的请求实施路由,但这同样会带来一些问题。

举个简单的例子,一个Web应用往往涉及很多静态文件,比如文本类型的JavaScript或者CSS文件和图片。如果一个Web应用寄宿于IIS下,对于Classic模式下的IIS 7.x及之前的版本,针对这些静态文件请求直接由IIS来响应,并不会进入ASP.NET的管道,所以由ASP,NET提供的路由机制并不会针对他们的访问造成任何影响。

但是对于Integrated模式下的IIS7.5,如果采用ASP.NET集成管道,所有类型的请求都将进入ASP.NET管道。这种情况下,如果允许路由系统路由现有物理文件,针对某个静态文件的请求就有可能被重定向到其他地方,这意味着我们将不能正常访问这些静态文件。除了采用基于Integrated模式下的IIS作为Web服务器,在采用Visual Studio提供的ASP.NET Development Server及IIS Express(IIS Express和IIS功能上面基本相同)的情况下这种问题依然存在。

我们就用上面的实例来演示这个问题。为了能让ASP.NET管道能够接管所有类型的访问请求,我们需要在web.config中添加如下一段配置。

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

我们在“/Content/”目录下放置一个名为bootstrap.css的CSS文件来控制页面显示的样式,并在允许针对现有物理文件路由的情况下(RouteTable.Routes.RouteExistingFiles = True)通过浏览器来访问这个CSS文件。由于CSS文件的路径(/content/bootstrap.css)与注册的路由模板({areacode}/{days})是匹配的,所以对应的请求自然就被路由到Weather.aspx页面了。

这是一个不得不解决的问题,因为它使我们无法正常地在页面中引用JavaScript和CSS文件。我们可以通过调用RouteCollection的Ignore方法来注册一些需要让路由系统忽略的URL。从前面给出的关于RouteCollection的定义中我们可以看到它具有两个Ignore方法重载,除了指定与需要忽略的URL相匹配的路由模板之外,还可以对相关的变量定义约束正则表达式。为了让路由系统忽略针对CSS文件的请求,我们可以按照如下的方式在Global.sapx中调用RouteTable的Routes属性的Ignore方法。值得一提的是,这样的方法调用应该放在路由注册之前,否则起不到任何作用。

public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.RouteExistingFiles = true;
            RouteTable.Routes.Ignore("content/{filename}.css/{*pathInfo}");
            var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } };
            var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
            RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}","~/weather.aspx", false, defaults, null, dataTokens);
        }
    }

5.直接添加路由对象

我们调用RouteCollection对象的MapPageRoute方法进行路由注册的本质就是在路由表中添加Route对象,所以我们完全可以调用Add方法添加一个手工创建的Route对象。这两种路由注册方式是完全等效的。如果需要添加一个继承自RouteBase的自定义路由对象,我们不得不采用手工添加的方式。

时间: 2024-12-15 01:54:17

学习ASP.NET MVC5框架揭秘笔记-ASP.NET路由(十一)的相关文章

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(四)

Action的执行 作为Controller的基类ControllerBase,它的Execute方法主要作用在于执行目标Action方法.如果目标Action方法返回一个ActionResult对象,它还需要执行该对象来对当前请求予以响应.在ASP.NET MVC框架中,两者的执行是通过一个叫做ActionInvoker的对象来完成的. 1.ActionInvoker 我们同样为ActionInvoker定义了一个借口IActionInvoker.如下所示.该接口定义了唯一的方法InvokeA

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(一)

ASP.NET MVC是如何运行的 ASP.NET由于采用了管道式设计,所以具有很好的扩展性,整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的.通过上面对ASP.NET管道设计的介绍我们知道,ASP.NET 的扩展点主要体现在HttpModule和HttpHandler这两个核心组件之上,整个ASP.NET MVC框架就是通过自定义的HttpModule和HttpHandler建立起来的. 接下来我们通过自定义组件来模拟ASP.NET MVC的运行原理. 1.4.1建立在迷你版

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(五)

2.AreaRegistration的缓存 Area的注册(主要是基于Area的路由映射注册)通过具体的AreaRegistration来完成.在应用启动的时候,ASP.NET MVC会遍历通过调用BuildManager的静态方法GetReferencedAssemblies得到的程序集列表,并从中找到所有AreaRegistration类型.如果一个应用涉及太多的程序集,则这个过程可能会耗费很多时间.为了提高性能,ASP.NET MVC会对解析出来的所有AreaRegistration类型列

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(三)

Controller的激活 ASP.NET MVC的路由系统通过注册的路由表对当前HTTP请求实施路由解析,从而得到一个用于封装路由数据的RouteData对象,这个过程是通过自定义的UrlRoutingModule对HttpApplication的PostResolveRequestCache事件进行注册实现的.由于得到的RouteData对象中已经包含了目标Controller的名称,我们需要根据该名称激活对应的Controller对象. 1.MvcRouteHandler 通过前面的介绍我

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(五)

完整的流程 对于我们创建的这个迷你版的ASP.NET MVC框架来说,虽然很多细节被直接忽略掉,但是它基本上能够展现整个ASP.NET MVC框架的全貌,支持这个开发框架的核心对象可以说一个不少.接下来我们对通过这个模拟框架展现出来的ASP.NET MVC针对请求的处理流程作一个简单的概括. 由于UrlRoutingModule这个HttpModule被注册到Web应用中,所以对于每个抵达的请求来说,当代表当前应用的HttpApplication对象的PostResolveRequestCach

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(二)

路由 对于一个ASP.NET MVC应用来说,针对HTTP请求的处理实现在目标Controller类型的某个Action,每个HTTP请求不在像ASP.NET Web Forms应用一样是针对一个物理文件,而是针对某个Controller的某个Action方法.目标Controller和Action的名称由HTTP请求的URL来决定,当ASP.NET MVC接收到抵达的请求后,其首要任务就是通过当前HTTP请求解析得到目标Controller和Action的名称,这个过程是通过ASP.NET M

学习ASP.NET MVC5框架揭秘笔记-ASP.NET路由(二)

实例演示:通过路由实现请求地址与.aspx页面的映射 我们创建一个简单的ASP.NET Web Forms应用,并采用一套独立于.aspx文件路径的URL来访问对应的Web页面,两者之间的映射通过路由来实现,我们依然沿用员工管理的场景. 首先我们将员工的所有信息(ID.姓名.性别.出生日期和所在部门)定义在如下所示的Employee类型中,然后定义一个EmployeeRepository类型来维护员工列表的数据.简单起见,员工列表通过静态字段employees表示.EmployeeReposit

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(二)

2.2.2 路由注册 ASP.NET MVC通过调用代表全局路由表的RouteCollection对象的扩展方法MapRoute进行路由注册.我们来进行一个简单的实例演示.我们依然沿用之前关于获取天气信息的路由模板,看看通过这种方式注册的Route对象针对匹配的请求将返回怎样一个RouteData对象. 我们创建一个空的ASP.NET Web程序,并手动添加"System.Web.Mvc.dll"和"System.Web.WebPages.Razor.dll"的引用

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(三)

2.2.3缺省URL参数 当通过VisualStudio的ASP.NET MVC项目模板创建一个Web应用后,它会为我们注册如下一个模板为"{controller}/{action}/{id}"的默认Route对象.3个路由模板均有相应额默认值.但是变量名为id的默认值为URLParameter.Optional.按照字面的意思,我们将其称为可缺省URL参数.那么将路由变量的默认值进行如此设置与设置一个具体的默认值有什么区别呢? routes.MapRoute( name: "