zoukankan      html  css  js  c++  java
  • 004_URL 路由

    定制路由系统

       路由系统是灵活可配置的,当然还可以通过下面这两种方式定制路由系统,来满足其他需求。

    1、  通过创建自定义的RouteBase实现;

    2、  通过创建自定义路由处理程序实现。

    创建自定义的RouteBase实现

       创建自定义的RouteBase实现,需要实现一个RouteBase的派生类,而这需要实现以下两个方法:

    • GetRouteData(HttpContextBase httpContext):这是入站URL进行匹配的工作机制。框架依次对RouteTable.Routes的每个条目调用这个方法,直到其中之一返回一个非空值。
    • GetVirtualPath(RequestContext requestContext,RouteValueDictionary values):这是出站URL生成的工作机制。框架依次对RouteTable.Routes的每一个条目调用这个方法,直到其中之一返回一个非空值。

       为了演示这种自定义方式,这里创建了一个RouteBase的派生类。我们假设这样的一个需求环境:需要把一个现有的应用程序迁移到MVC框架,但不论出于什么原因,我们需要兼容之前的URL,那就可以通过这种方式来实现,当然可以通过规则的路由系统来处理——这里不对这种方式进行讨论。

             首先,创建一个处理旧式路由请求的控制器,将其命名为:LegacyController,如:

    using System.Web.Mvc;
    
    namespace UrlsAndRoutes.Controllers
    {
        /// <summary>
        /// 用以处理旧式 URL 请求的控制器
        /// </summary>
        public class LegacyController : Controller
        {
    
            public ActionResult GetLegacyURL(string legacyURL)
            {
                // 应用程序迁移到 MVC 之前,请求是针对文件的,因此,实际上是需要在这里处理被请求的文件。但这里
                // 只简单说明一下自定义 RouteBase 的实现原理,所以,此处仅在视图中显示这个 URL。
                return View((object)legacyURL);
            }
    
        }
    }

             上面代码对View方法中的参数做了转换,如果不转换,则C#编译器会误认为要将参数作为要指定渲染的视图的名称的字符串(View方法的一个重载版本的实现)。下面是这个动作方法的视图GetLegacyURL.cshtml:

    @model string
    @{
        ViewBag.Title = "GetLegacyURL";
        Layout = null;
    }
    
    <h2>GetLegacyURL</h2>
    
    The URL requested was:@Model

    1、对输入URL进行路由

    在Infrastructure文件夹中创建一个LegacyRoute类,其内容如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace UrlsAndRoutes.Infrastructure
    {
        public class LegacyRoute : RouteBase
        {
            private string[] urls;
            public LegacyRoute(params string[] targetUrls)
            {
    
            }
    
            public override RouteData GetRouteData(HttpContextBase httpContext)
            {
                RouteData result = null;
    
                string requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
                if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
                {
                    result = new RouteData(this, new MvcRouteHandler());
                    result.Values.Add("controller", "Legacy");
                    result.Values.Add("action", "GetLegacyURL");
                    result.Values.Add("legacyURL", "requestedURL");
                }
                return result;
            }
    
            public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
            {
                return null;
            }
    
        }
    }

       注册一条路由,以使其使用新建的这个RouteBase派生类:

            public static void RegisterRoutes(RouteCollection routes)
            {
                // 注册自定义的 RouteBase 实现
                routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
    
            }

    2、生成输出URL

             在LegacyRoute中实现GetVirtualPath方法以使其能够支持输出URL的生成。如: 

            public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
            {
                VirtualPathData result = null;
    
                if (values.ContainsKey("legactURL") && urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))
                {
                    // 如果存在一个匹配,将会创建一个 VirtualPathData 对象,在其中传递一个对当前对象的引用和出站 URL。由于路由系统已经预先将
                    // 字符“/”附加到了这个URL,因此,必须从生成的 URL 上删除这个前导字符。
                    result = new VirtualPathData(this, new UrlHelper(requestContext).Content((string)values["legacyURL"]).Substring(1));
                }
    
                return null;
            }

       在ActionName.cshtml视图中添加下面这段代码,以使其能礼仪自定义路由生成输出URL:

        <div>
            @* 经由自定义路由生成一个输出 URL *@
            This is a URL:
            @Html.ActionLink("Click me", "GetLegacyURL", new { legacyURL = "~/articles/Windows_3.1_Overview.html" })
        </div>

             上面代码将产生一个这样的a元素:

    <a href=”/articles/Windows_3.1_Overview.html”>Click me</a>

             用legacyURL属性创建的匿名类型被转换到了含有同名键的RouteValueDictionary类中。

    创建自定义路由处理程序

       路由已经依赖这个MvcRouteHandler了,因为MvcRouteHandler把路由系统连接到了MVC框架。但通过实现IRouteHandler接口,路由系统仍允许自定义自己的路由处理程序,如下面的示例:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Routing;
    
    namespace UrlsAndRoutes.Infrastructure
    {
        public class CustomRouteHandler : IRouteHandler
        {
            public IHttpHandler GetHttpHandler(RequestContext requestContext)
            {
                return new CustomHttpHandler();
            }
    
        }
    
        public class CustomHttpHandler : IHttpHandler
        {
            public bool IsReusable
            {
                get { return false; }
            }
    
            public void ProcessRequest(HttpContext context)
            {
                context.Response.Write("Hello");
            }
        }
    }

             IRouteHandler接口的目的是提供生成IHttpHandler接口的实现,且由它负责对请求进行处理。在该接口的MVC实现中,主要负责这几项工作:查找控制器、调用动作方法、渲染视图,并将结果写入到响应中。当然,这里的实现要简单的多,此处仅将单词“Hello”写到客户端,且只是文本形式。要想得到最终效果,需要在RouteConfig.cs文件中注册这个自定义处理程序:

            public static void RegisterRoutes(RouteCollection routes)
            {
                // 注册自定义路由处理程序
                routes.Add(new Route("SayHello", new CustomRouteHandler()));
    
            }

    使用区域

       MVC框架支持将Web应用程序组织成一些区域(Area),每个区域代表应用程序的一个功能端,如管理、结算、客户支持等等。这使得代码的管理很有用,尤其是大型项目,如果对所有控制器、视图和模型只使用一组文件夹,那将会是很难于管理的。

    创建区域

       可以直接对项目右键,选择“添加”->“区域”进行添加。还可以在当前的区域中创建其他区域。在刚刚的操作之后,项目中将会出现如下这样的区域文件夹结构:

                           

             通过Areas/Admin文件夹,可以看出这是一个小型的MVC项目。其中有“Controllers”、“Models”和“Views”的文件夹。前两个是空的,但“Views”文件夹含有一个“Shared”文件夹和一个Web.config视图引擎配置文件(这里暂不对视图引擎进行讨论)。

             另外,这里还多了一个AdminAreaRegistration.cs文件,如:

    using System.Web.Mvc;
    
    namespace UrlsAndRoutes.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 }
                );
            }
        }
    }

             从清单中可以看出,该类中的RegisterArea方法注册了一个URL模式为Admin/{controller}/{action}/{id}的路由。当然,也可以在该方法中定义该区域专用的其他路由。

    注意:如果要给路由赋名,必须确保这些名称在整个应用程序而不仅仅是某一区域中是唯一的。

       由于在Global.asax的Application_Start方法中已经对路由的注册进行过处理,因此,不需要在开发的过程中采取其他措施来确保该注册方法会被调用: 

        public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
    
                WebApiConfig.Register(GlobalConfiguration.Configuration);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }

             上面代码中对静态方法AreaRegistration.RegisterAllAreas的调用,会导致MVC框架对应用程序的所有类进行遍历,找出派生于AreaRegistration的所有类,并调用这些类上的RegisterArea方法。

    注意:不用修改Application_Start方法中与路由相关的语句顺序。如果在AreaRegistration.RegisterAllAreas之前调用RegisterRoutes,那么会在区域路由之前定义路由。由于路由系统是按顺序评估的,这意味着对区域控制器的请求有可能会用不正确的路由进行匹配。

    注:AreaRegistrationContext类中的MapRoute方法会自动把注册的路由限制到包含该区域控制器的命名空间。也就是说,当某区域创建控制器时,必须把它放在其默认的命名空间中;否则,路由系统将无法找到它。

    填充区域

       在上一节“创建区域”一节中,已经知道在一个区域中可以创建控制器、视图以及模型等。下面,通过创建一个名为HomeController的控制器类,来演示应用程序中区域之间的分离:

       在下图中的Controllers文件夹中右键添加一个空的控制器:HomeController

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace UrlsAndRoutes.Areas.Admin.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
    
        }
    }

             为了演示,对该控制器中Index动作方法右击,并添加相应的视图,添加后的视图将在:Areas/Admin/View/Home路径中。

     

    视图内容如下:

    @{
        ViewBag.Title = "Index";
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-with" />
        <title>Index</title>
    </head>
    <body>
        <div>
            <h2>Admin Area Index</h2>
        </div>
    </body>
    </html>

             从上面介绍可以看出,在一个区域内的工作方式与在一个MVC项目主区中工作是相当类似的。在项目中创建某个项的工作流也是相同的。其效果如下(导航路径:Admin/Home/Index):

     

    解析不明确的控制器问题

       区域可能不像它们所展示的那样是自包含的。当一个区域被注册时,所定义的任何路由都被限制到与这个区域关联的命名空间之中。这也是能够请求/Admin/Home/Index,并得到WorkingWithAreas.Admin.Controllers命名空间中HomeController类的原因。

       然而,在RouteConfig.cs的RegisterRoutes方法中定义的路由却不受类似的限制。作为提醒,这里给出了示例应用程序此时的路由配置: 

            public static void RegisterRoutes(RouteCollection routes)
            { 
                routes.Add(new Route("SayHello", new CustomRouteHandler()));
    
                routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
    
                routes.MapRoute("MyRoute", "{controller}/{action}");
                routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
            }

        名为“MyRoute”的路由把来自浏览器的输入URL转换为Home控制器上的Index动作。这时会收到一个错误的消息,因为没有为这条路由设置命名空间的约束,所以MVC框架会看到两个HomeController类。为了解决这一问题,需要在所有可能导致冲突的路由中,将主控制器命名空间列为优先,如:

            public static void RegisterRoutes(RouteCollection routes)
            { 
                routes.Add(new Route("SayHello", new CustomRouteHandler()));
    
                routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
    
                routes.MapRoute("MyRoute", "{controller}/{action}",null,new[] {“UrlsAndRoutes.Controllers”});
                routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" }, new[] {“UrlsAndRoutes.Controllers”});
            }

             上面代码中加粗部分将项目控制器作为了优先。当然也可以对某个区域中的控制器实现优先。

    生成对区域动作的链接

       对与同一区域中的动作,无需采取特殊的步骤来创建指向这些动作的链接。MVC框架会检测当前请求涉及的特定区域,然后出站URL生成将只在该区域定义的路由中查找一个匹配。如将下面代码添加到Admin区域的视图。

    @Html.ActionLink("Click me", "About")

       会生成以下HTML:

    <a href=”/Admin/Home/About”>Click me</a>

             为了对不同区域中的动作或根本无区域的动作创建一条链接,必须创建一个名为“area”的变量,并用它指定区域名,如:

    @Html.ActionLink("Click me to go to another area", "Index", new { area = "Support" })

             因此,area被保留为片段变量名。假设创建了名为Support的区域,并有对应的标准路由定义,则将生成如HTML:

    <a href=”/Support/Home”>Click me to go to another area</a>

    如果想链接到顶级控制器(/Controllers文件夹中的一个控制器)上的一个动作,那么应该把area指定为空字符串,如:

        @Html.ActionLink("Click me to go to another area", "Index", new {area = ""})

    URL方案最佳做法

    1、  使URL整洁和人性化

       下面摘抄一些生成友好URL的简单的纲要:

    • 设计URL来描述它们的内容,而不是应用程序的实现细节。使用/Articles/AnnualReport,而不是使用/Website_v2/CachedContentServer/FromCache/AnnualReport。
    • 尽可能采用内容标题而不是ID号,使用/Articles/AnnualReport,而不是/Articles/2392。如果必须使用一个ID号(以区别具有同样标题的条目或避免通过标题查找一个条目时,需要多余的数据库查询步骤),那么两者都有(如:/Articles/2392/AnnualReport)。这需要多打一些字符,但更要意义,并会改善搜索引擎排列。
    • 不用对HTML页面使用文件扩展名(如,.aspx或.mvc),但对特殊文件类型要用扩展名(如,.jpg、.pdf、.zip等)。如果是适当的设置了MIME类型,Web浏览器不会在意文件的扩展名,但人们却希望对PDF文件用.pdf扩展名。
    • 创建一种层次感(如:/Products/Menswear/Shirts/Red),这样,容易让人猜出父目录的URL。
    • 不区分大小写。ASP.NET路由系统默认是不区分大小写的。
    • 避免使用符合、代码和字符序列。需要用单词分隔符时,可以使用短横(如:/my-great-article)。下划线是不友好的,而URL编码的空格是奇特的(/my+great+article)或令人讨厌的(/my%20great%20article)。
    • 不用修改URL。打破链接等于失去商务。当确实需要修改URL时,通过永久重定向(301)尽可能长时间的继续支持旧式的URL方案。
    • 具有一致性。在整个应用程序中采用一种URL格式。URL应简短、易于输入、可剪辑(人性化可剪辑),且持久稳定,而且它们应该形象化网站结构。

    2、GET和POST:选用正确的一个

       一般来说,GET请求应该被用于所有只读信息检索,而POST请求应该被用于各种修改应用程序状态的操作。用标准的术语说,GET请求用于安全交互(除信息检索外无其他影响),而POST请求用于不安全交互(作出决定或修改某些东西)。GET请求是可设定地址的——所有信息都包含在URL中,因此它可以设为书签并链接到这些地址。(这些约定是由全球互联网联盟(W3C)在http://www.w3.org/Products/rfc2616/rfc2616-sec9.html上设定的)

  • 相关阅读:
    适配器模式(16)
    状态模式(15)
    用反射技术替换工厂种的switch分支(14)
    2017年目标与规划
    抽象工厂模式(13)
    观察者模式(12)
    建造者模式(11)
    TCP 可靠传输与流量控制的实现
    TCP报文段的首部格式
    TCP可靠传输的工作原理
  • 原文地址:https://www.cnblogs.com/KeSaga/p/5550263.html
Copyright © 2011-2022 走看看