一步一步實戰擴展 ASP.NET Route,實現小寫 URL、個性化 URL


介紹

不知道大家在使用 ASP.NET MVC 時有沒有一些擴展要求,反正我是有很多。在使用 MVC 這幾年(PS:我是從 1.0 開始學,2.0、3.0 開發至今),我深深地覺得 MVC 的擴展性真是太好了,幾乎你大部分的“合理”需求,用 MVC 都能實現。好了,廢話不多說了,今天我就實戰演示如何擴展 ASP.NET Route,希望能幫助到你。

小寫 URL

我想很多朋友和我一樣,使用 ASP.NET MVC 時都想要小寫的 URL。一般除非你在開發時手動把 Controller、Action 的名字建成小寫,或者在 Action 方法上標記 ActionNameAttribute,否則如果不經過擴展,很難實現完全的小寫 URL。把 Controller、Action 的名字建成小寫不符合 C# 編碼規范,當然如果你忽略這個規范,我也沒有辦法。另一方面,在 Action 方法上標記 ActionNameAttribute 工作量又太大,又不利於統一維護。至於為什么要小寫的 URL,我想是一種習慣吧,仔細觀察各大網站,你就會發現,小寫的 URL 是主流。


個性 URL

實現完小寫 URL 后,我還有一個特殊的 URL 規則要求,就是生成 A 元素的 URL 時,把 Action 的名字中的下划線(_)替換成減號(-),並且服務器端 ASP.NET MVC 能處理這個請求。打一個比如:

public class HomeController : Controller
{
    /// <summary>
    /// 添加產品信息,可以通過訪問 URL:/home/add-product-information
    /// </summary>
    /// <returns></returns>
    public ActionResult Add_Product_Information()
    {
        return View();
    } 
}

上面的例子,大家注意到了嗎,我希望使用 @Url.Action("Add_Product_Information") 能生成 /home/add-Product-Information 格式的 URL,並且還能訪問。很多朋友可能想到了,以為通過簡單的在 Global.asax 中配置路由可以實現,經過本人的實測,是無法實現的,因為配置路由時,比如: "{controller}/{action}/{id}" 這里的 {action} 是作為一個整體,而無法再把它拆分。像這種就只能通過擴展 Route 來實現了。 作者(音樂讓我說)博客地址:http://music.cnblogs.com

開始實戰


1. 建立一個 LowercaseRoute,繼承 System.Web.Routing.Route 類

在這個 LowercaseRoute 類里,我們需要做的是添加和 Route 類相似的構造函數,並且重寫 GetVirtualPath 方法。關鍵代碼如下:

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
    VirtualPathData virtualPath = base.GetVirtualPath(requestContext, values); // 由 MVC 默認生成的 URL,比如:/Home/Add_Product_Information
    if (virtualPath != null)
    {
        string virtualPathValue = virtualPath.VirtualPath; // 獲取比如:/Home/Add_Product_Information
        int length = virtualPathValue.LastIndexOf("?");
        if (length == 0) return virtualPath;
        if (length > 0)
        {
            string url1 = virtualPathValue.Substring(0, length).ToLowerInvariant(); // 把 controller 和 action 小寫
            string url2 = virtualPathValue.Substring(length); // 保留 ? 后的參數
            virtualPath.VirtualPath = url1 + url2; // 連接,比如:/home/add-product-information
            return virtualPath;
        }
        virtualPath.VirtualPath = virtualPath.VirtualPath.ToLowerInvariant();
    }
    return virtualPath;
}

以上這個類會在當你在 View、Controller 中調用 Url.Action("Add_Product_Information")、Html.ActionLink("Add_Product_Information") 時調用。


2. 建立一個 UnderlineRoute,繼承 LowercaseRoute 類

上面那個 LowercaseRoute 類僅僅是實現 URL 小寫的需求,而現在這個 UnderlineRoute 才是在小寫 URL 的基礎上再實現個性化的 URL 需求(PS:就是本文開頭介紹的,要把 Action 的名稱中帶有下划線的替換成減號)。作者(音樂讓我說)博客地址:http://music.cnblogs.com

首先,依舊添加和 LowercaseRoute 類相似的構造函數,然后重寫 GetVirtualPath 方法。完成代碼如下:

public class UnderlineRoute : LowercaseRoute
{
    public const string UNDERLINE_STRING = "_";
    public const string SUBTRACTION_SIGN = "-";

    public UnderlineRoute(string url, IRouteHandler routeHandler)
        : base(url, routeHandler)
    {
    }

    public UnderlineRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        : base(url, defaults, routeHandler)
    {
    }

    public UnderlineRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
        : base(url, defaults, constraints, routeHandler)
    {
    }

    public UnderlineRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
        : base(url, defaults, constraints, dataTokens, routeHandler)
    {
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        string action = (string)values["action"];
        if(action != null)
        {
            if (action.Contains(UNDERLINE_STRING))
            {
                values["action"] = action.Replace(UNDERLINE_STRING, SUBTRACTION_SIGN);
            }
        }
        // 以上的這小塊代碼就是生成 URL 時,把 RequestContext 中的 RouteData 中的 action 參數給替換,
        // 把 action 的名字中帶有下划線(_)的替換成減號(-)
        return base.GetVirtualPath(requestContext, values);
    }

    /// <summary>
    /// 替換成下划線
    /// </summary>
    /// <param name="requestContext"></param>
    public static void ReplaceUnderlineToSubtractionSignInRouteData(RequestContext requestContext)
    {
        if (requestContext == null)
        {
            throw new ArgumentNullException("requestContext");
        }
        // 以上的這小塊代碼就是當處理請求時,把 RequestContext 中的 RouteData 中的 action 參數給替換回來。
        // 把請求的路由中的 action 參數給替換回來,把 action 的名字中帶有減號(-)的替換成下划線(_)
        string actionName = (string)requestContext.RouteData.Values["action"];
        if (actionName != null)
        {
            if (actionName.Contains(UnderlineRoute.SUBTRACTION_SIGN))
            {
                requestContext.RouteData.Values["action"] = actionName.Replace(UnderlineRoute.SUBTRACTION_SIGN, UnderlineRoute.UNDERLINE_STRING);
            }
        }
    }
} 

3. 建立相應的 RouteHandler。

如果您建立的項目是 MVC,則建立 UnderlineMvcRouteHandler 類,繼承 MvcRouteHandler 類。(PS:WebForms 可以忽略)

如果您建立的項目是傳統的 WebForm,則建立 UnderlinePageRouteHandler 類,繼承 PageRouteHandler 類。(PS:MVC 可以忽略)

代碼如下:

public class UnderlineMvcRouteHandler : MvcRouteHandler
{
    public UnderlineMvcRouteHandler() :base()
    {
    }

    public UnderlineMvcRouteHandler(IControllerFactory controllerFactory): base(controllerFactory)
    {
    }

    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        UnderlineRoute.ReplaceUnderlineToSubtractionSignInRouteData(requestContext); // 調用 UnderlineRoute 類處理
        return base.GetHttpHandler(requestContext);
    }
}



public class UnderlinePageRouteHandler : PageRouteHandler
{
    public UnderlinePageRouteHandler(string virtualPath) : base(virtualPath)
    {
    }

    public UnderlinePageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess) : base(virtualPath, checkPhysicalUrlAccess)
    {
    }

    public override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        UnderlineRoute.ReplaceUnderlineToSubtractionSignInRouteData(requestContext); // 調用 UnderlineRoute 類處理
        return base.GetHttpHandler(requestContext);
    }
}

4. 擴展 RouteCollection 類的 MapRoute 方法。

在配置路由時,默認是調用 MapRoute 方法,而這個方法默認是由 MvcRouteHandler(PS: WebForm 默認是 PageRouteHandler),所以為了讓我們配置的路由默認由 UnderlineMvcRouteHandler
(WebForm 默認是 UnderlinePageRouteHandler)來處理,我們再添加幾個和 MapRoute 相似的方法。

對於 MVC 項目,我們添加幾個名叫 MapRouteUnderline 的重載。

public static Route MapRouteUnderline(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");
    }
    // 注意:
    //       1. 這里使用的 Route 是 UnderlineRoute
    //       2. 這里使用的 RouteHandler 是 UnderlineMvcRouteHandler
    UnderlineRoute item = new UnderlineRoute(url, new UnderlineMvcRouteHandler())
    {
        Defaults = new RouteValueDictionary(defaults),
        Constraints = new RouteValueDictionary(constraints),
        DataTokens = new RouteValueDictionary(namespaces)
    };
    if (namespaces != null && namespaces.Length > 0)
    {
        item.DataTokens["Namespaces"] = namespaces;
    }
    routes.Add(name, item);
    return item;
}

對於 WebForms 項目,我們添加幾個名叫 MapRouteUnderline 的重載。  

public static Route MapPageRouteUnderline(this RouteCollection routes, 
    string routeName, 
    string routeUrl, 
    string physicalFile, 
    bool checkPhysicalUrlAccess, 
    RouteValueDictionary defaults, 
    RouteValueDictionary constraints, 
    RouteValueDictionary dataTokens)
{
    if (routes == null)
    {
        throw new ArgumentNullException("routes");
    }
    if (routeUrl == null)
    {
        throw new ArgumentNullException("routeUrl");
    }
    // 注意:
    //       1. 這里使用的 Route 是 UnderlineRoute
    //       2. 這里使用的 RouteHandler 是 UnderlinePageRouteHandler
    UnderlineRoute item = new UnderlineRoute(routeUrl, 
        defaults, 
        constraints, 
        dataTokens, 
        new UnderlinePageRouteHandler(physicalFile, checkPhysicalUrlAccess)
    );
    routes.Add(routeName, item);
    return item;
}

5. 在 Global.asax 中配置路由

這一步是關鍵,要讓我們上面自定義的 Route 和 RouteHandler 生效,就必須在配置路由時,調用我們自定義的 MapRouteUnderline 和 MapPageRouteUnderline 方法。

//作者(音樂讓我說)博客地址:http://music.cnblogs.com
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    // 請注意:這里調用的是 MapPageRouteUnderline 擴展方法
    routes.MapPageRouteUnderline("adminDefault",
        "{controller}/{action}",
        "~/admin/{action}.aspx",
        true, null, new RouteValueDictionary(new { controller = "admin" }));

    // 請注意:這里調用的是 MapRouteUnderline 擴展方法
    routes.MapRouteUnderline(
        "Default", // 路由名稱
        "{controller}/{action}/{id}", // 帶有參數的 URL
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值
    );
}

截止到這里,關鍵代碼都已經給出了,接下來就是檢驗我們的代碼。

6. 建立測試用的 Action

在 HomeController 中建立一個名叫 Add_Product_Information 的 Action 方法。並建立 cshtml 視圖,視圖里面隨便敲幾句話就可以了。

//作者(音樂讓我說)博客地址:http://music.cnblogs.com
public class HomeController : Controller
{
    /// <summary>
    /// 添加產品信息,可以通過訪問 URL:/home/add-product-information
    /// </summary>
    /// <returns></returns>
    public ActionResult Add_Product_Information()
    {
        return View();
    } 
}

7. 在項目的根目錄建立一個名叫 admin 的文件夾。


再在 admin 文件夾下建立 hello.aspx、add_product_information.aspx 至於里面的內容,隨便敲幾句話就可以了。


8. 在視圖中測試,生成 URL

<ul id="menu">
    <li>@Html.ActionLink("主頁", "Index", "Home")</li>
    <li>@Html.ActionLink("關於", "About", "Home")</li>
    <li>@Html.ActionLink("添加產品信息", "Add_Product_Information", "Home")</li>
    <li>
        @Html.ActionLink("后台 - 歡迎頁", "Hello", "Admin")
    </li>
    <li>
        @Html.ActionLink("后台 - 添加產品信息", "Add_Product_Information", "Admin")
    </li>
</ul>

9. 運行

解決方案截圖如下:

以上 MVC 生成的鏈接都小寫了,並且把下划線(_)也替換成了減號(-)。運行時,也正常的呈現出來!


本文只是起一個穿針引線的作用,無法要求讀者也有這樣的要求,僅僅為了演示,希望和大家一起進步,如有不妥,請多多包涵,並歡迎指出!

作者(音樂讓我說)博客地址:http://music.cnblogs.com  

示例代碼下載:點我下載

如果您覺得本文不錯,麻煩點一下“推薦”,謝謝!

轉載請注明,謝謝!

謝謝瀏覽!

謝謝瀏覽!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM