首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 發現之旅:高效能 ASP.NET TreeView 控制項開發 (中)——三段式 HTML 輸出與客戶端 XSLT 引擎

     編輯:關於C#

前言:控制項渲染管線的精細控制

ASP.NET WebControl 渲染流程為 OnLoad → Render → RenderContents。SkyTreeViewControl 在此管線中精心編排三段 HTML 輸出,分別承擔動態載入基礎設施、XSLT 模板與初始化腳本、節點互動邏輯三大職責,並透過 RegisterStartupScript 確保腳本在 </form> 前正確輸出。本文逐段拆解這三段 HTML 的生成邏輯、關鍵 JavaScript 函數與資料流向。

第一段:動態載入基礎設施 (XML Data Island + 回調腳本)

觸發條件

if (this.DynamicLoadChildNodes)
{
    // 輸出第一段 HTML 區塊
}

輸出內容完整解析

<!-- XML 資料島:用於非同步載入子節點 XML 定義 -->
<xml id='SkyTreeViewControlTempXML' 
     onreadystatechange='SkyTreeViewControlDynamicLoadChildNodes()'></xml>

<script language=javascript>
  // 當前正在動態載入的根節點 ID
  var SkyTreeViewControlDyanmicRootNodeID;

  // 動態載入子節點主流程
  function SkyTreeViewControlDynamicLoadChildNodes() {
    var xml = document.getElementById('SkyTreeViewControlTempXML');
    if (!xml) return;
    if (xml.readyState != 'complete') return;  // 尚未載入完成

    // 取得父節點文字元素
    var rootNode = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_text');
    if (!rootNode) return;

    var html = '';
    var errorFlag = false;

    // 取得 XSLT 模板資料島
    var xsl = document.getElementById('SkyTreeViewControlXSLT');
    if (!xsl) { html = '缺失 XSLT 資料'; errorFlag = true; }
    else if (xml.XMLDocument.parseError.errorCode != 0) {
      html = '錯誤: ' + xml.XMLDocument.parseError.reason + '[' + xml.XMLDocument.parseError.url + ']';
      errorFlag = true;
    }
    else if (!xml.XMLDocument.documentElement) {
      html = '未找到根節點'; errorFlag = true;
    }
    else {
      // 關鍵:將父節點 ID 寫入 XML 文件,供 XSLT 模板讀取
      xml.XMLDocument.documentElement.setAttribute('RootID', SkyTreeViewControlDyanmicRootNodeID);
      
      // 執行 XSLT 轉換 → 產生子節點 HTML
      html = xml.XMLDocument.transformNode(xsl.XMLDocument);
    }

    // 移除 Loading 提示
    var loading = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_Loading');
    if (loading) {
      if (errorFlag) { loading.innerHTML = '<br/>' + html; return; }
      loading.insertAdjacentHTML('afterEnd', html);
      loading.parentNode.removeChild(loading);
    }
    else {
      rootNode.insertAdjacentHTML('afterEnd', html);
    }

    // 更新展開/收合圖示狀態
    var expend = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_expend');
    var icon = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_icon');
    var text = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_text');
    var table = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_table');

    if (expend && icon && text && table) {
      // 交換 src 與 SrcBack 實現圖示切換
      var tmp = icon.src; icon.src = icon.getAttribute('SrcBack'); icon.setAttribute('SrcBack', tmp);
      tmp = expend.src; expend.src = expend.getAttribute('SrcBack'); expend.setAttribute('SrcBack', tmp);
    }

    SkyTreeViewControlDyanmicRootNodeID = null;  // 重置
  }
</script>

關鍵設計亮點

技術點實作細節優勢
XML Data Island<xml id=...> + onreadystatechangeIE 原生非同步 XML 載入,無需 XMLHttpRequest 物件
狀態機驅動readyState == 'complete' 才執行轉換避免部分載入導致轉換失敗
RootID 回寫setAttribute('RootID', ...)讓 XSLT 模板知曉父節點 ID,生成正確的子節點 ID 前綴
transformNode()MSXML 核心方法,C++ 實作極快客戶端 XSLT 轉換效能遠超 JavaScript 字串拼接
insertAdjacentHTMLDOM 範圍插入,不破壞現有事件綁定比 innerHTML 更精確、效能更好
SrcBack 雙緩衝交換 src/SrcBack 實現圖示切換無需預載入圖片,零延遲切換

第二段:XSLT 模板資料島與初始化腳本

觸發條件

if (!this.GenerateAtServer || this.DynamicLoadChildNodes)
{
    // 輸出第二段 HTML 區塊
}

輸出內容

<!-- XSLT 模板資料島:從嵌入資源 SkyTreeViewControl.xslt 讀取 -->
<xml id='SkyTreeViewControlXSLT'>
  <xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" version="1.0">
    ... 完整 XSLT 內容 ...
  </xsl:stylesheet>
</xml>

<script language=javascript>
  // 樹狀列表初始化函數
  function RefreshSkyTreeViewControl(id) {
    var xml = document.getElementById(id + '_xml');      // 節點資料 XML 資料島
    var xsl = document.getElementById('SkyTreeViewControlXSLT');  // XSLT 模板
    var container = document.getElementById(id + '_container');  // 顯示容器

    if (!container) { alert('未找到目標'); return; }
    if (!xml) { container.innerText = '缺失 XML 資料'; return; }
    if (!xsl) { container.innerText = '缺失 XSLT 資料'; return; }

    // 執行 XSLT 轉換並填充容器
    var html = xml.XMLDocument.transformNode(xsl.XMLDocument);
    container.innerHTML = html;
  }
</script>

ReadXSLTString():從嵌入資源讀取 XSLT

private static string _cachedXslt = null;

private string ReadXSLTString()
{
    if (_cachedXslt == null)
    {
        foreach (string name in GetType().Assembly.GetManifestResourceNames())
        {
            if (name.EndsWith(".SkyTreeViewControl.xslt"))
            {
                using (Stream stream = GetType().Assembly.GetManifestResourceStream(name))
                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                {
                    _cachedXslt = reader.ReadToEnd();
                }
                return _cachedXslt;
            }
        }
        throw new Exception("未找到嵌入資源 SkyTreeViewControl.xslt");
    }
    return _cachedXslt;
}

第三段:節點互動邏輯

輸出內容

<script language=javascript>
  // 當前高亮節點
  var SkyTreeViewContrlCurrentTreeNode;

  // 展開/收合/高亮統一入口
  function SkyTreeViewContrlExpendNodeByID(strID, bolSelect) {
    var expend = document.getElementById(strID + '_expend');
    var icon = document.getElementById(strID + '_icon');
    var text = document.getElementById(strID + '_text');
    var table = document.getElementById(strID + '_table');

    if (!text) return;

    // 高亮處理
    if (bolSelect) {
      if (SkyTreeViewContrlCurrentTreeNode != text && SkyTreeViewContrlCurrentTreeNode) {
        SkyTreeViewContrlCurrentTreeNode.className = 'SkyTreeViewControl_TreeNode';
      }
      SkyTreeViewContrlCurrentTreeNode = text;
      text.className = 'SkyTreeViewControl_SelectedNode';
    }

    // 展開/收合處理
    if (expend && icon && table) {
      // 交換圖示
      var tmp = icon.src; icon.src = icon.getAttribute('SrcBack'); icon.setAttribute('SrcBack', tmp);
      tmp = expend.src; expend.src = expend.getAttribute('SrcBack'); expend.setAttribute('SrcBack', tmp);
      
      // 切換子節點表格顯示/隱藏
      table.style.display = (table.style.display != 'none') ? 'none' : '';
    }

    // 動態載入觸發
    var dynamicLoad = false;
    if (!table) {  // 無子節點表格 → 可能需動態載入
      var xmlSource = text.getAttribute('XMLSource');
      if (xmlSource && xmlSource.length > 0) {
        text.removeAttribute('XMLSource');  // 防止重複觸發
        
        var xmlIsland = document.getElementById('SkyTreeViewControlTempXML');
        if (xmlIsland) {
          SkyTreeViewControlDyanmicRootNodeID = strID;
          
          // 顯示 Loading
          var loading = document.getElementById(strID + '_Loading');
          if (loading) loading.style.display = '';
          
          // 觸發 XML 載入 → onreadystatechange → 第一段腳本執行
          xmlIsland.src = xmlSource;
          dynamicLoad = true;
        }
      }
    }

    if (!dynamicLoad) {
      // text.scrollIntoView(false);  // 可選:滾動到可視區
    }
  }
</script>

伺服器端渲染邏輯:XML 序列化與 XSLT 轉換分流

1. 節點物件 → XML 字串 (記憶體中)

StringWriter strWriter = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(strWriter);

if (this.IndentXML && !this.GenerateAtServer)
{
    xmlWriter.Indentation = 3;
    xmlWriter.IndentChar = ' ';
    xmlWriter.Formatting = Formatting.Indented;
}

// XML 序列化:SkyTreeNodeList → XML
XmlSerializer serializer = new XmlSerializer(typeof(SkyTreeNodeList));
serializer.Serialize(xmlWriter, this.Nodes);
xmlWriter.Close();

string xml = strWriter.ToString();  // 完整節點 XML

2. 伺服器端生成模式

if (this.GenerateAtServer)
{
    // 載入 XSLT 模板
    string xslt = ReadXSLTString();
    XmlDocument xsltDoc = new XmlDocument(); xsltDoc.LoadXml(xslt);
    XmlDocument nodeDoc = new XmlDocument(); nodeDoc.LoadXml(xml);

    // XSLT 編譯轉換 (NET 2.0+)
    XslCompiledTransform transform = new XslCompiledTransform();
    transform.Load(xsltDoc);

    StringWriter output = new StringWriter();
    transform.Transform(nodeDoc, null, output);

    // 直接寫入回應流
    writer.Write(output.ToString());
    return;
}

3. 客戶端生成模式

// 輸出容器與 XML 資料島
writer.Write($"<span id='{ClientID}_container'>正在載入...</span>");
writer.Write($"<xml id='{ClientID}_xml'>{xml}</xml>");

// 註冊啟動腳本:頁面載入完成後自動初始化
Page.ClientScript.RegisterStartupScript(
    GetType(), 
    "SkyTreeViewControl_Init_" + ClientID,
    $"");

ViewState 持久化:跨 PostBack 保持樹狀結構

protected override object SaveViewState()
{
    ViewState["nodes"] = _nodes;  // SkyTreeNodeList 需 [Serializable]
    return base.SaveViewState();
}

protected override void LoadViewState(object savedState)
{
    _nodes = ViewState["nodes"] as SkyTreeNodeList;
    base.LoadViewState(savedState);
}

關鍵:SkyTreeNode 與 SkyTreeNodeList 必須標記 [Serializable()],否則 ViewState 序列化失敗。

XML Data Island 相容性說明

僅 IE 支援。Firefox/Chrome 需改用 XMLHttpRequest + XSLTProcessor:

// 現代跨瀏覽器實作概念
fetch(xmlSource)
  .then(r => r.text())
  .then(xmlText => {
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xmlText, "text/xml");
    const xsltDoc = parser.parseFromString(xsltText, "text/xml");
    const processor = new XSLTProcessor();
    processor.importStylesheet(xsltDoc);
    const resultDoc = processor.transformToDocument(xmlDoc);
    container.innerHTML = new XMLSerializer().serializeToString(resultDoc);
  });

總結

三段式 HTML 輸出架構實現了關注點分離:

  1. 基礎設施段:XML Data Island + 回調腳本,支援動態載入
  2. 模板與初始化段:XSLT 模板資料島 + RefreshSkyTreeViewControl,支援客戶端渲染
  3. 互動邏輯段:SkyTreeViewContrlExpendNodeByID,統一處理展開/收合/高亮/動態觸發

伺服器端透過 GenerateAtServer 旗標決定是否執行 XSLT 轉換,大數據量走客戶端渲染 (傳輸 XML),小數據量走伺服器端渲染 (傳輸 HTML),完美平衡頻寬與 CPU。這種雙模式渲染 + 按需載入 + 純資料傳輸的設計,是現代前端框架 (SSR/CSR 混合、Suspense、React Server Components) 的早期雛形。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved