MVC3生成纯静态后如何不再走路由直接访问静态页面

时间:2020-12-03 13:05:57

问题描述:高访问量类型的电子商务网站,需要将一些不是经常变化的页面生成静态页面,然后普通用户就可以直接访问这些静态页面而不用再访问需要连接数据库的动态页面。那么ASP.NET MVC3中如何做到这一点呢?

要解决这个问题,我们需要先了解ASP.NET应用程序的生命周期,先看下面作者整理的一张图片:

MVC3生成纯静态后如何不再走路由直接访问静态页面

从图中我们可以清楚的看到:通用IIS访问应用程序时,每次的单个页面URL访问时,都会先经过HttpApplication 管线处理请求,走过BeginRequest 事件之后才会去走路由访问具体的Controller和Action,最后结束的时候会请求EndRequest事件。下面用一张图来表示这个顺序:

MVC3生成纯静态后如何不再走路由直接访问静态页面

注意图中标示的红色部分就是我们要实现的部分,实现如下:

1 新建MyHandler.cs

<span class="kwrd">public</span> <span class="kwrd">class</span> MyHandler:IHttpModule
    {
        <span class="kwrd">public</span> <span class="kwrd">void</span> Init(HttpApplication application)
        {
            application.BeginRequest +=
                (<span class="kwrd">new</span> EventHandler(<span class="kwrd">this</span>.Application_BeginRequest));
            application.EndRequest +=
                (<span class="kwrd">new</span> EventHandler(<span class="kwrd">this</span>.Application_EndRequest));
        }
<span class="kwrd">private</span> <span class="kwrd">void</span> Application_BeginRequest(Object source,
        EventArgs e)
        {
            <span class="rem">// Create HttpApplication and HttpContext objects to access</span>
            <span class="rem">// request and response properties.</span>
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            <span class="kwrd">string</span> filePath = context.Request.FilePath;
            <span class="kwrd">string</span> fileExtension =
                VirtualPathUtility.GetExtension(filePath);
            <span class="kwrd">if</span> (fileExtension.Equals(<span class="str">".html"</span>))
            {
                context.Response.WriteFile(context.Server.MapPath(filePath));//直接走静态页面
//此处可以加入缓存,条件也可以根据需要来自己定义
                <span class="rem">context.Response.End();</span>
            }

        }
        <span class="kwrd">private</span> <span class="kwrd">void</span> Application_EndRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            <span class="kwrd">string</span> filePath = context.Request.FilePath;
            <span class="kwrd">string</span> fileExtension =
                VirtualPathUtility.GetExtension(filePath);
            <span class="kwrd">if</span> (fileExtension.Equals(<span class="str">".html"</span>))
            {
                context.Response.Write(<span class="str">"<hr><h1><font color=red>"</span> +
                    <span class="str">"HelloWorldModule: End of Request</font></h1>"</span>);
            }
        }

        <span class="kwrd">public</span> <span class="kwrd">void</span> Dispose() { }
    }

 

2. web.config中加入以下代码,才会运行自定义的管道处理类

   1:  <httpModules>
   2:        <add name="MvcTest.MyHandler" type="MvcTest.MyHandler"/>
   3:      </httpModules>

运行一下自己的代码,看看效果你就全明白了!

补充:根据@小尾鱼的提示,如果直接在自己的项目文件下生产了和URL中一样的目录文件,比如访问:yourdomin.com/product/1.html,你的项目文件夹下真的存在product/1.html这个路径,那么IIS会直接去请求这个静态页面,如果项目中使用了自定义的管道处理程序,那么这个静态页仍然会走我们的自定义管道处理程序,我们可以在这里通过缓存来实现要不要重新成长静态页或删除过期产品的静态页,如果不使用此方法,只能去写执行计划,定时跑这些静态文件了,修改Application_BeginRequest

   1:  private void Application_BeginRequest(Object source,
   2:          EventArgs e)
   3:          {
   4:              // Create HttpApplication and HttpContext objects to access
   5:              // request and response properties.
   6:              HttpApplication application = (HttpApplication)source;
   7:              HttpContext context = application.Context;
   8:              string filePath = context.Request.FilePath;
   9:              string fileExtension =
  10:                  VirtualPathUtility.GetExtension(filePath);
  11:              if (fileExtension.Equals(".html"))
  12:              {
  13:                  //判断缓存是否存在,不存在加入缓存,调用生成静态的类和方法
  14:                  //产品过期,移除静态文件,302重定向
  15:                  if (System.IO.File.Exists(context.Server.MapPath(filePath)))
  16:                  {
  17:                      context.Response.WriteFile(context.Server.MapPath(filePath));
  18:                      context.Response.End();
  19:                  }
  20:                  
  21:                  
  22:              }

思路大体如此。

ASP.NET MVC3的伪静态实现

 

最近使用asp.net MVC3开发B2C电子商务系统,为了SEO的优化工作,需要通过路由实现伪静态URL,后续再根据需要生成真正的静态页面,不直接走路由访问具体的页面。现在开始研究第一步,如何定义自己的路由规则,达到伪静态的功能需求。

基本实现原理如下图:

 MVC3生成纯静态后如何不再走路由直接访问静态页面

首先,关于命名空间。

路由的功能是为了让所有Asp.net网站开发都可以使用,所以dll并没有在MVC中,而是在System.Web中的System.web.Routing。

现在我们为了我们实际的需求,实现MVC3中的自定义路由功能(继承RouteBase,重写RouteData和VirtualPathData)。

下面的例子实现以下目的:输入一个youdomin.com/product/123.html,执行TestController中Index.

第一步:实现TestRoute类

 1 RouteData 每次访问URL都会从此入口

通过httpContext.Request.AppRelativeCurrentExecutionFilePath 获取我们访问的url地址,根据地址进行分析:是不是符合我们的规则,符合我们规则我们就走特定的Controller和Action。代码如下:

public class TestRoute:RouteBase
{
private string[] urls;
        public TestRoute(params string[]targetUrls) {
            urls = targetUrls;
        }
public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            RouteData result = null;
            string requestedURL =
                httpContext.Request.AppRelativeCurrentExecutionFilePath+httpContext.Request.PathInfo;
            requestedURL = requestedURL.Substring(2).Trim('/');

            if (requestedURL.Contains(urls.ToArray().GetValue(0).ToString()))
            {
                result = new RouteData(this, new MvcRouteHandler());
                result.Values.Add("controller", "Test");
                result.Values.Add("action", "Index");
                result.Values.Add("p", requestedURL);
            }
            return result;
        }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return null;
        }
}

 上面例子中,我们根据判断Url中是否符合某个特定的值来特定执行特定Controller和特定Action,没有就返回null。

第二步,在Global.aspx中注册我们自己的路与规则:

 

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.Add(new TestRoute("product"));
                        routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }

 

 注意上述代码红色部分,Application_Start() 中注册一个路由规则,RegisterRoutes(RouteTable.Routes)然后在RegisterRoutes方法中加入如下代码:

routes.Add(new TestRoute("product"));

 注明:TestRoute 是上面我们自己定义的路由,实现RouteBase的类。

 第三步:新建第一步中测试用的Controller

 

public class TestController:Controller
    {
        public ActionResult Index(string p)
        {
            ViewData["t"] =p;
            return View("");
        }
    }

第四步:新建一个视图

 

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title></title>
</head>
<body>
    <div>
<!--此处会显示你输入的URL地址-->
     @ViewData["t"].ToString()
    </div>
</body>
</html>

 

第五步,直接输入URl测试

 比如:http://127.0.0.1/product/1.html

后续补充:

主要内容:如何让前台列表展示页显示以上5步中实现的伪静态URL?

经过实际验证,发现RouteBase中VirtualPathData的实现就能解决以上问题。.net Route其实已经实现了这个双向解析的问题,通过输入URL,从RouteData进入,根据自己的路由规则进行解析到相对应的Controller和Action,然后在使用URL.Action的地方从VirtualPathData解析出符合路由规则的URL地址,具体代码如下:

?
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
         {
             if (values[ "controller" ].ToString().Contains( "Test" ))
             {
                 return new VirtualPathData( this , "product/" + values[ "p" ] + ".html" );
             }
             else
             return null ;
         }

 可以替换第一步中TestRoute类中的GetVirtualPath方法,查看实际效果。