1. 程式人生 > >C#進階系列——一步一步封裝自己的HtmlHelper元件:BootstrapHelper

C#進階系列——一步一步封裝自己的HtmlHelper元件:BootstrapHelper

前言:之前學習過很多的Bootstrap元件,博主就在腦海裡構思:是否可以封裝一套自己Bootstrap元件庫呢。再加上看到MVC的Razor語法裡面直接通過後臺方法輸出前端控制元件的方式,於是打算仿照HtmlHelper封裝一套BootstrapHelper,今天只是一個開頭,講述下如何封裝自己的Html元件,以後慢慢完善。

BootstrapHelper系列文章目錄

一、揭開HtmlHelper的“面紗”

經常使用Razor寫法的園友都知道,在cshtml裡面,我們可以通過後臺的方法輸出成前端的html元件,比如我們隨便看兩個例子:

輸出成Html之後

博主的好奇心又來了,它是怎麼做到的呢?於是將 Html

 物件以及 Label() 方法轉到定義

由此可以看出Html物件是HtmlHelper型別的一個例項,而Label()方法則是HtmlHelper型別的一個擴充套件方法,所以就可以直接通過Html.Label()這種方式直接呼叫。不熟悉C#擴充套件方法的園友可以看看http://www.cnblogs.com/landeanfen/p/4632467.html

 既然我們想要封裝自己的HtmlHelper,那麼我們就必須要了解Label()方法裡面是如何實現的,我們偉大的Reflector又派上用場了。我們來反編譯System.Web.MVC.dll看看。找到LabelExtensions這個類

經過一系列的轉到定義,我們找到最終的方法

同樣,我們找到TextBox()最終定義的方法

喲西,原來就是TagBuilder這個一個小東西,讓人覺得神奇得不要不要的。所以有時我們需要敢於反編譯,或許看似高階的背後其實很簡單呢~~

二、BootstrapHelper元件封裝準備

1、定義BootstrapHelper

有了以上的基礎做準備,接下來就是具體的實現了,我們新建了一個空的MVC專案,新增如下檔案。

編譯發現報錯如下

將HtmlHelper轉到定義發現它有兩個建構函式,分別有兩個、三個引數

 那麼,我們的BootstrapHelper也定義兩個建構函式,於是程式碼變成這樣:

namespace Extensions
{
    public class BootstrapHelper : System.Web.Mvc.HtmlHelper
    {
        /// <summary>
        /// 使用指定的檢視上下文和檢視資料容器來初始化 BootstrapHelper 類的新例項。
        /// </summary>
        /// <param name="viewContext">檢視上下文</param>
        /// <param name="viewDataContainer">檢視資料容器</param>
        public BootstrapHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
            : base(viewContext, viewDataContainer)
        { }

        /// <summary>
        /// 使用指定的檢視上下文、檢視資料容器和路由集合來初始化 BootstrapHelper 類的新例項。
        /// </summary>
        /// <param name="viewContext">檢視上下文</param>
        /// <param name="viewDataContainer">檢視資料容器</param>
        /// <param name="routeCollection">路由集合</param>
        public BootstrapHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
            : base(viewContext, viewDataContainer, routeCollection)
        { }
    }
}

這樣通過子類複用父類的建構函式的方式即可解決以上問題。編譯通過!

2、定義LabelExtensions

上面我們研究過HtmlHelper,在HtmlHelper裡面,不同的html元件定義了不同的Extension(擴充套件),下面我們就以最簡單的Label標籤為例定義我們BootstrapHelper裡面的Label標籤。

同樣,在Extensions資料夾裡面我們新建了一個檔案LabelExtensions.cs,用於定義Label標籤的擴充套件,它裡面的基本實現如下:

namespace Extensions
{
    public static class LabelExtensions
    {
        /// <summary>
        /// 通過使用指定的 HTML 幫助器和窗體欄位的名稱,返回Label標籤
        /// </summary>
        /// <param name="html">擴充套件方法例項</param>
        /// <param name="id">標籤的id</param>
        /// <param name="content">標籤的內容</param>
        /// <param name="cssClass">標籤的class樣式</param>
        /// <param name="htmlAttributes">標籤的額外屬性(如果屬性裡面含有“-”,請用“_”代替)</param>
        /// <returns>label標籤的html字串</returns>
        public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, string cssClass, object htmlAttributes)
        {
            //定義標籤的名稱
            TagBuilder tag = new TagBuilder("label");
            //給標籤增加額外的屬性
            IDictionary<string, object> attributes = BootstrapHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
            if (!string.IsNullOrEmpty(id))
            {
                attributes.Add("id", id);
            }
            if (!string.IsNullOrEmpty(cssClass))
            {
                //給標籤增加樣式
                tag.AddCssClass(cssClass);
            }
            //給標籤增加文字
            tag.SetInnerText(content);
            tag.AddCssClass("control-label");
            tag.MergeAttributes(attributes);
            return MvcHtmlString.Create(tag.ToString());
        }
    }
}

我們暫且只定義一個方法,其他的過載我們很好擴充套件,這裡給所有的BootstrapHelper裡面的Label標籤統一添加了“control-label”樣式,當然,如果你的專案裡面的label標籤定義了自己的樣式,那麼這裡改成你需要的樣式即可。以上程式碼都比較基礎,這裡就不一一講解。

3、定義BootstrapWebViewPage

以上定義了BootstrapHelper和LabelExtensions,準備工作是做好了,但是還少一個物件,比如我們在cshtml頁面裡面 @Html.Label("姓名") 這樣寫,Html變數是一個HtmlHelper型別的物件,那麼,如果我們需要使用類似 @Bootstrap.Label() 這種寫法,以此類推,Bootstrap變數應該也是一個BootstrapHelper型別的物件,那麼如果我們要這麼用,必須要先定義一個Bootstrap變數,這個變數到底在哪裡定義呢。於是博主思考,Html變數是定義在哪裡的呢?再次轉到定義

原來是在WebViewPage這個類的子類中,同樣,我們在Extensions資料夾裡面也新建一個WebViewPage的子類BootstrapWebViewPage,實現程式碼如下:

namespace Extensions
{
    public abstract class BootstrapWebViewPage<T> : System.Web.Mvc.WebViewPage<T>
    {
        //在cshtml頁面裡面使用的變數
        public BootstrapHelper Bootstrap { get; set; }

        /// <summary>
        /// 初始化Bootstrap物件
        /// </summary>
        public override void InitHelpers()
        {
            base.InitHelpers();
            Bootstrap = new BootstrapHelper(ViewContext, this);
        }

        public override void Execute()
        {
            //throw new NotImplementedException();
        }
    }
}

至於這裡的泛型,我們以後再來做講解,這裡先不做過多糾結

 4、實踐

 有了以上三步,所有需要的方法和變數都齊全了,貌似已經“萬事俱備只欠東風”了,是不是這樣呢?我們來試一把

編譯,將Index.cshtml頁面關閉重新開啟,發現仍然找不到Bootstrap物件

怎麼回事呢,Html是可以找到的,那Bootstrap變數去哪裡了呢。。。

經過一番查詢資料,發現在View資料夾裡面有一個web.config檔案(之前一直沒怎麼在意這個東西,現在想想裡面還是有學問的哦),裡面有一個節點system.web.webPages.razor下面有一個pages節點,預設是這樣的:

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="BootstrapHelper" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

我們將pages節點的pageBaseType改成我們的WebViewPage

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="Extensions.BootstrapWebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="BootstrapHelper" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

然後編譯,重新開啟Index.cshtml。

OK,可以找到Bootstrap物件了。我們將Index.cshtml裡面寫入如下內容:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div> 
        @Html.Label("姓名")
        @Html.TextBox("a", "Jim")
        
        @Bootstrap.Label(null, "Bootstrap Label標籤", null, null)
    </div>
</body>
</html>

執行看看效果:

 

怎麼還是報錯呢?這個問題應該不難理解,因為在razor裡面使用@呼叫後臺變數和方法的時候也存在名稱空間的概念,這個名稱空間在哪裡引用呢,還是在View資料夾裡面的web.config裡面,在system.web.webPages.razor節點下面存在namespace的節點,我們將自定義的Label()擴充套件方法所在的名稱空間加進去即可。於是配置變成這樣:

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="Extensions.BootstrapWebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="BootstrapHelper" />
        <add namespace="Extensions"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

再次執行

三、BootstrapHelper元件完善

通過上面一系列發現坑、填坑的經歷,一個最最簡單的BootstrapHelper元件已經基本可用。我們將LabelExtensions簡單完善下:

namespace Extensions
{
    public static class LabelExtensions
    {
        public static MvcHtmlString Label(this BootstrapHelper html, string id)
        {
            return Label(html, id, null, null, null);
        }public static MvcHtmlString Label(this BootstrapHelper html, string id, string content)
        {
            return Label(html, id, content, null, null);
        }

        public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, object htmlAttributes)
        {
            return Label(html, id, content, null, htmlAttributes);
        }

        /// <summary>
        /// 通過使用指定的 HTML 幫助器和窗體欄位的名稱,返回Label標籤
        /// </summary>
        /// <param name="html">擴充套件方法例項</param>
        /// <param name="id">標籤的id</param>
        /// <param name="content">標籤的內容</param>
        /// <param name="cssClass">標籤的class樣式</param>
        /// <param name="htmlAttributes">標籤的額外屬性(如果屬性裡面含有“-”,請用“_”代替)</param>
        /// <returns>label標籤的html字串</returns>
        public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, string cssClass, object htmlAttributes)
        {
            //定義標籤的名稱
            TagBuilder tag = new TagBuilder("label");
            //給標籤增加額外的屬性
            IDictionary<string, object> attributes = BootstrapHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
            if (!string.IsNullOrEmpty(id))
            {
                attributes.Add("id", id);
            }
            if (!string.IsNullOrEmpty(cssClass))
            {
                //給標籤增加樣式
                tag.AddCssClass(cssClass);
            }
            //給標籤增加文字
            tag.SetInnerText(content);
            tag.AddCssClass("control-label");
            tag.MergeAttributes(attributes);
            return MvcHtmlString.Create(tag.ToString());
        }
    }
}

呵呵,是不是有模有樣~~可能又有人要說博主“山寨”了,呵呵,不管山寨不山寨,你覺得爽就行。

四、總結

 這篇先到這裡,一路填坑,基本功能總算可用。還有一些需要完善的地方,比如泛型,比如lamada表示式等等,來日方長,博主有時間完善下。還有最基礎的一些表單控制元件,我們都需要封裝,這個估計還有點工作量,只能慢慢來完善了,等完善都一定的程度會開源在git上,希望自己能夠堅持下去!如果你覺得本文對你有幫助,請幫忙推薦下,您的推薦是博主堅持完善的動力。

歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線,否則保留追究法律責任的權利

相關推薦

C#系列——DDD領域驅動設計初探(六)領域服務

前言:之前一直在搭建專案架構的程式碼,有點偏離我們的主題(DDD)了,這篇我們繼續來聊聊DDD裡面另一個比較重要的知識點:領域服務。關於領域服務的使用,書中也介紹得比較晦澀,在此就根據博主自己的理解談談這個知識點的使用。 DDD領域驅動設計初探系列文章: 一、領域服務的引入 在《領域驅動設計:軟體核

C#系列——DDD領域驅動設計初探(四)WCF搭建

前言:前面三篇分享了下DDD裡面的兩個主要特性:聚合和倉儲。領域層的搭建基本完成,當然還涉及到領域事件和領域服務的部分,後面再專案搭建的過程中慢慢引入,博主的思路是先將整個架構走通,然後一步一步來新增相關元素,使架構慢慢變得豐滿。這篇打算分享下應用層的搭建。根據DDD的設計原則,應用層不包含任何領域邏輯,它主

C#系列——DDD領域驅動設計初探(五)AutoMapper使用

前言:前篇搭建了下WCF的程式碼,就提到了DTO的概念,對於為什麼要有這麼一個DTO的物件,上章可能對於這點不太詳盡,在此不厭其煩再來提提它的作用: 從安全上面考慮,領域Model都帶有領域業務,讓Client端引用Domain Model就意味著Client端可以繞過應用層直接完成業務邏輯的呼叫,這樣

C#系列——DDD領域驅動設計初探(三)倉儲Repository(下)

前言:上篇介紹了下倉儲的程式碼架構示例以及簡單分析了倉儲了使用優勢。本章還是繼續來完善下倉儲的設計。上章說了,倉儲的最主要作用的分離領域層和具體的技術架構,使得領域層更加專注領域邏輯。那麼涉及到具體的實現的時候我們應該怎麼做呢,本章就來說說倉儲裡面具體細節方便的知識。 DDD領域驅動設計初探系列文章:

C#系列——DDD領域驅動設計初探(二)倉儲Repository(上)

前言:上篇介紹了DDD設計Demo裡面的聚合劃分以及實體和聚合根的設計,這章繼續來說說DDD裡面最具爭議的話題之一的倉儲Repository,為什麼Repository會有這麼大的爭議,博主認為主要原因無非以下兩點:一是Repository的真實意圖沒有理解清楚,導致設計的紊亂,隨著專案的橫向和縱向擴充套件,

C#系列——DDD領域驅動設計初探(七)Web層的搭建

前言:好久沒更新部落格了,每天被該死的業務纏身,今天正好一個模組完成了,繼續來完善我們的程式碼。之前的六篇完成了領域層、應用層、以及基礎結構層的部分程式碼,這篇打算搭建下UI層的程式碼。 DDD領域驅動設計初探系列文章: 一、UI層介紹 在DDD裡面,UI層的設計也分為BS和CS,本篇還是以Web為

C#系列——封裝自己HtmlHelper元件BootstrapHelper(三附原始碼)

前言:之前的兩篇封裝了一些基礎的表單元件,這篇繼續來封裝幾個基於bootstrap的其他元件。和上篇不同的是,這篇的有幾個元件需要某些js檔案的支援。 BootstrapHelper系列文章目錄 一、NumberBoxExtensions NumberBoxExtensions是一個基於boot

C#系列——封裝自己HtmlHelper元件BootstrapHelper(二)

前言:上篇介紹了下封裝BootstrapHelper的一些基礎知識,這篇繼續來完善下。參考HtmlHelper的方式,這篇博主先來封裝下一些常用的表單元件。關於BootstrapHelper封裝的意義何在,上篇評論裡面已經討論得太多,這裡也不想過多糾結。總之一句話:凡事有得必有失,就看你怎麼去取捨。有興趣的可

C#系列——封裝自己HtmlHelper元件BootstrapHelper

前言:之前學習過很多的Bootstrap元件,博主就在腦海裡構思:是否可以封裝一套自己Bootstrap元件庫呢。再加上看到MVC的Razor語法裡面直接通過後臺方法輸出前端控制元件的方式,於是打算仿照HtmlHelper封裝一套BootstrapHelper,今天只是一個開頭,講述下如何封裝自己的Html元

C#系列——MEF實現設計上的“鬆耦合”(

前言:最近去了趟外地出差,介紹推廣小組開發的框架類產品。推廣物件是本部門在專案上面的同事——1到2年工作經驗的初級程式設計師。在給他們介紹框架時發現很多框架設計層面的知識他們都沒有接觸過,甚至沒聽說過,這下囧了~~於是乎在想該如何跟他們解釋MEF、AOP、倉儲模式等方面的東東。本來 C#基礎系列 應該還有兩篇

C#系列——DDD領域驅動設計初探(聚合

前言:又有差不多半個月沒寫點什麼了,感覺這樣很對不起自己似的。今天看到一篇博文裡面寫道:越是忙人越有時間寫部落格。呵呵,似乎有點道理,博主為了證明自己也是忙人,這不就來學習下DDD這麼一個聽上去高大上的東西。前面介紹了下MEF和AOP的相關知識,後面打算分享Automapper、倉儲模式、WCF等東西的,可是

python系列學習(

map函式 map函式是python的內建的高階函式,接受兩個引數,第一個就是函式f,另一個就是列表list,作用就是f函式作用於list的每一個元素,這裡需要注意的一點是map()函式作用於一個list時,會返回一個新的list,並不會改變原有的list

C#系列——WebApi 異常處理解決方案(轉)

機制 輸出 ges 如果 但是 rom lba slist 解決 出處:http://www.cnblogs.com/landeanfen/p/5363846.html 閱讀目錄 一、使用異常篩選器捕獲所有異常 二、HttpResponseException自

C#系列——WebApi 接口測試工具WebApiTestClient

spa type 區域 all 手動 shee 找到 網絡 打開文件 C#進階系列——WebApi 接口測試工具:WebApiTestClient 前言:這兩天在整WebApi的服務,由於調用方是Android客戶端,Android開發人員也不懂C#語法,API裏

C#系列——WebApi 路由機制剖析你準備好了嗎?

事先 blank path can tex 全局配置 dex 找不到 save 前言:從MVC到WebApi,路由機制一直是伴隨著這些技術的一個重要組成部分。 它可以很簡單:如果你僅僅只需要會用一些簡單的路由,如/Home/Index,那麽你只需要配置一個默認路由就能簡

C#系列——WebApi 跨域問題解決方案CORS

dea ati ice pro target default 異常 測試工具 復雜 前言:上篇總結了下WebApi的接口測試工具的使用,這篇接著來看看WebAPI的另一個常見問題:跨域問題。本篇主要從實例的角度分享下CORS解決跨域問題一些細節。 WebApi系列文章

[轉]C#系列——WebApi 接口返回值不困惑返回值類型詳解

try 接口測試工具 des rep home creat port 調用 學習 本文轉自:http://www.cnblogs.com/landeanfen/p/5501487.html 閱讀目錄 一、void無返回值 二、IHttpActionResult

C#系列——WebApi 身份認證解決方案Basic基礎認證

str 常見 bre 這一 dex ace timeout ticket 結合 閱讀目錄 一、為什麽需要身份認證 二、Basic基礎認證的原理解析 1、常見的認證方式 2、Basic基礎認證原理 三、Basic基礎認證的代碼示例 1、登錄過程 2、/Home/I

C#系列——WebApi 接口參數不再困惑傳參詳解

pub 博客 bapi write ids 簡單 指定 數組 這也 https://www.cnblogs.com/landeanfen/p/5337072.html 閱讀目錄 一、get請求 1、基礎類型參數 2、實體作為參數 3、數組作為參數 4

【8】C++系列(過載)

1、過載規則 c++幾乎可以過載全部的運算子,而且只能夠過載c++已有的運算子。 其中,不能過載的運算子:"." 、 ".*" 、"::"、"?:" 過載之後運算子的優先順序和結合性都不會改變。 運算子過載是針對新型資料的實際需要,對原有運算子進行適當的改造。例如: 使複數的物件