程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 在視圖中使用遞歸生成樹狀結構

在視圖中使用遞歸生成樹狀結構

編輯:.NET實例教程

在開發過程中往往會有一個需求,就是將一個樹狀的數據結構在視圖中表示出來。例如最傳統的多級分類,系統中有一系列根分類,每個分類中又帶有一些子分類,而我們的目標便是在頁面上生成一個由ul和li嵌套組成的Html結構。這個問題看似簡單,但是如何讓實現變的輕松、易於使用也是一個值得討論的問題。這次就來談談這部分的情況。

  實現目標

  首先來明確一下實現目標。例如我們有一個Category對象,表示一個類別:

public class Category 
{ 
  public string Name { get; set; } 
 
  public List<Category> Children { get; set; } 
}

  然後我們准備一個嵌套的數據結構:

public ActionResult CategorIEs() 
{ 
  var model = new List<Category> 
  { 
    new Category 
    { 
      Name = "Category 1", 
      Children = new List<Category> 
      { 
        new Category 
        { 
          Name = "Category 1 - 1", 
          Children = new List<Category>() 
        }, 
        new Category 
        { 
          Name = "Category 1 - 2", 
          Children = new List<Category>() 
        }, 
      } 
    }, 
    new Category 
    { 
      Name = "Category 2", 
      Children = new List<Category> 
      { 
        new Category 
        { 
          Name = "Category 2 - 1", 
          Children = new List<Category>() 
        }, 
        new Category 
        { 
          Name = "Category 2 - 2", 
          Children = new List<Category>() 
        }, 
      } 
    }, 
  }; 
 
  return VIEw(model); 
} 

自然還會有一個Model類型為List<Category>的視圖:

<%@ Page Language="C#" Inherits="System.Web.Mvc.VIEwPage<List<Category>>" %> 
 
...

  而我們的目標,便是要在視圖中顯示出這樣的Html:

<ul> 
  <li>Category 1 
    <ul> 
      <li>Category 1 - 1 </li> 
      <li>Category 1 - 2 </li> 
    </ul> 
  </li> 
  <li>Category 2 
    <ul> 
      <li>Category 2 - 1 </li> 
      <li>Category 2 - 2 </li> 
    </ul> 
  </li> 
</ul> 

  那麼我們又該怎麼做呢?

  使用局部視圖

  如果在平時讓我們處理這種數據結構,很明顯會使用遞歸。但是,在視圖模板中表示遞歸是非常困難的,因此我們會借助局部視圖。例如:

<%@ Control Language="C#" Inherits="VIEwUserControl<List<Category>>" %> 
 
<% if (Model.Count > 0) { %> 
  <ul> 
    <% foreach (var cat in Model) { %> 
   
      <li> 
        <%= Html.Encode(cat.Name) %> 
        <% Html.RenderPartial("CategoryTree", cat.Children); %> 
      </li> 
   
    <% } %> 
  </ul> 
<% } %> 
這個局部視圖的作用便是生成我們想要的HTML片段。在局部視圖內部還會調用自身來生成下一級的Html。在主視圖中生成局部視圖也很容易:

<% Html.RenderPartial("CategoryTree", Model); %>

  這就實現了遞歸,也是實現這一功能最易於理解的方式。只可惜這種做法比較麻煩,需要定義額外的局部視圖。這種局部視圖往往只是為一個主視圖服務的,它會和主視圖的前後環境相關,分離開去在維護上就會有些不便了。

  在頁面中定義委托

  我們知道,在運行時ASP.Net頁面會被編譯為一個類,而其中的各種標記,或內嵌的代碼都會被作為一個方法裡定義或執行的局部變量。如果說我們要在一個方法內“定義”另一個方法,自然只能是使用匿名方法的特性來構造一個委托了。這個委托為了可以“遞歸”調用,就必須這麼寫:

<% Action<L
ist<Category>> renderCategorIEs = null; // 先設為null %> 
<% renderCategories = (categorIEs) => { // 再定義 %> 
 
  <% if (categorIEs.Count > 0) { %> 
    <ul> 
      <% foreach (var cat in categorIEs) { %> 
     
        <li> 
          <%= Html.Encode(cat.Name) %> 
          <% renderCategorIEs(cat.Children); %> 
        </li> 
     
      <% } %> 
    </ul> 
  <% } %> 
 
<% }; %> 
 
<% renderCategorIEs(Model); // 最後再調用,即生成Html %> 
這段代碼的確可以生成HTML,但是我不喜歡。我不喜歡的原因倒不是因為這是我眼中的“偽遞歸”,而是因為這在頁面將“定義”與“執行”分開了。事實上,在我們看到Html標記及邏輯控制的地方並沒有在“同時”生成內容,這只是在“定義”。生成內容的時機其實是在最後對 renderCategorIEs委托的調用,這容易造成一定誤導,因為最後的“生成”可能會遺漏,而定義和生成之間可能會插入一些其他內容。

  這種做法的優勢,就是在於不用額外分離出一個局部視圖,它直接寫在主視圖中,易於維護,也相對易於理解。

  使用Lambda表達式構建遞歸方法

  “定義”與“執行”分離的一個重要原因,還是因為Lambda表達式無法定義遞歸函數。否則,我們就可以直接定義一個遞歸執行的委托,並在最後跟上Invoke或直接調用即可。

  因此,其實這裡就正是使用Lambda表達式編寫遞歸函數的用武之地。例如,我們補充一個類似的Fix方法:

public static class HtmlExtensions 
{ 
  public static Action<T> Fix<T>(this HtmlHelper helper, Func<Action<T>, Action<T>> f) 
  { 
    return x => f(Fix(helper, f))(x); 
  } 
} 

  於是在視圖中便可以:

<% Html.Fix<List<Category>>(render => categorIEs => { %> 
 
  <% if (categorIEs.Count > 0) { %> 
    <ul> 
      <% foreach (var cat in categorIEs) { %> 
     
        <li> 
          <%= Html.Encode(cat.Name) %> 
          <% render(cat.Children); %> 
        </li> 
     
      <% } %> 
    </ul> 
  <% } %> 
 
<% }).Invoke(Model); %> 
不過嚴格說來,它還是“定義”與“執行”分離的,只是我們現在可以把它們寫在一塊兒。此外,Fix方法對於模板中的Html生成實在沒有什麼意義。

  提供一個Render方法輔助遞歸

  Fix方法對頁面生成沒有什麼作用,不過如果有一個可以輔助遞歸的Render方法便有意義多了:

public static class HtmlExtensions 
{ 
  private static Action<T> Fix<T>(Func<Action<T>, Action<T>> f) 
  { 
    return x => f(Fix(f))(x); 
  } 
 
  public static void Render<T>(this HtmlHelper helper, T model, Func<Action<T>, Action<T>> f) 
  { 
    Fix(f)(model); 
  } 
} 

  於是,我們在頁面中就可以這麼寫:

<% Html.Render(Model, render => categorIEs => { %> 
 
  <% if (categorIEs.Count > 0) { %> 
    <ul> 
      <% foreach (var cat in categorIEs) { %> 
     
        <li> 
          <%= Html.Encode(cat.Name) %> 
          <% render(cat.Children); %> 
        </li> 
     
      <% } %> 
    </ul> 
  <% } %> 
 
<% }); %> 

  您是否覺得這麼做難以理解?我不這麼認為,因為從語法上來說,這種Html生成方式是很簡單的
<% Html.Render(參數, 用於遞歸的方法 => 當前參數 => { %> 
 
  ... 
 
  <% 遞歸調用 %> 
 
  ... 
 
<% }); %> 

  至於背後的原理?關心那些做什
麼。

  性能

  可惜,根據性能比較,使用Fix構造遞歸的做法,比使用SelfApplicable的做法要慢上許多。雖然我認為這裡不會是性能的關鍵,但如果您實在覺得無法接受的話,也可以利用SelfApplicable來構造遞歸的Html呈現方式。其輔助方法為:

public delegate void SelfApplicable<T>(SelfApplicable<T> self, T arg); 
 
public static class HtmlExtensions 
{ 
  public static void Render<T>(this HtmlHelper helper, T model, SelfApplicable<T> f) 
  { 
    f(f, model); 
  } 
} 

  於是在視圖中:

<% Html.Render(Model, (render, categorIEs) => { %> 
 
  <% if (categorIEs.Count > 0) { %> 
    <ul> 
      <% foreach (var cat in categorIEs) { %> 
     
        <li> 
          <%= Html.Encode(cat.Name) %> 
          <% render(render, cat.Children); %> 
        </li> 
     
      <% } %> 
    </ul> 
  <% } %> 
 
<% }); %> 


  同樣,我們只要記住這麼做的“語法”就可以了。

  總結

  相比之下,我喜歡最後兩種做法。因為他們直接構造了“HTML生成”的功能,且“內置”了遞歸。如果使用一個額外的局部視圖,雖然“樸素”但使用較為麻煩。使用“偽遞歸”的方式,從概念上看這不太像是在生成Html,程序構造的痕跡(先聲明,再定義,最後調用)過於明顯了。

  您喜歡哪種做法呢?如果您遇到了我這樣的需求,您會怎麼做呢?

  最後我想進行一個小調查:您滿意WebForm的頁面作為視圖模板引擎嗎?您平時最喜歡使用什麼視圖模板引擎,為什麼呢?

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved