[ASP.net WebForm] 使用AjaxControlToolkit的CascadingDropDown Extenders實現Ajax連動下拉選單 Sample Code

How to use AjaxControlToolkit's CascadingDropDown Extenders Sample Code

前言

連動下拉選單我想有經驗的Developer應該都會做,我從.Net 2一路寫到.Net 4.7

ASP.net WebForm 連動下拉選單一般作法為DropDownList控制項設定AutoPostBack="True"和OnSelectedIndexChanged事件

然後在第一個DropDownList的OnSelectedIndexChanged事件去改寫第二個DropDownList控制項的Items

如果想要非同步局部刷新的話,就利用<asp:UpdatePanel />控制項在.aspx把所有要連動的DropDownList控制項包覆起來(有些書籍也是這麼教)

目前為止我的印象是這樣↑XD

但最近遇到舊專案ASP.net WebForm從.Net 2升級到.Net 4.7,其中使用到的元件RadAjax.Net2.dll為Telerik Rad Ajax for ASP.NET AJAX第三方廠商開發類似微軟AjaxControlToolKit的控制項

引用該RadAjax.Net2.dll的Web Form專案一升級到.Net 4 的話,會遇到此篇網友的問題,輸出的字串被HtmlEncode

其實是有個治標不治本的解法:在頁面的DOM全部都載入後( $(document).ready()事件裡 ),使用jQuery把被HtmlEncode的字串透過.attr()方法取代成正確的字串

會說此方法治標不治本是因為

1.頁面第一次載入,在瀏覽器開發者工具的console仍會出錯(雖然後續有透過jQuery 硬把控制項的Attribute改寫掉)

2.控制項雖然可以成功發出Ajax Request,但Telerik Rad Ajax自動產生的原始碼有一段會判斷目前的瀏覽器是否為Netscape

非Netscape的話,Ajax request就會setRequestHeader的Content-Length屬性,所以在Chrome執行下又會產生另一問題:Refused to set unsafe header "Content-Length",導致Ajax Request失敗。

綜合上述幾點來說採用jQuery改寫掉控制項Attribute屬性的解法,最終只能給IE正常使用。

不小心廢話太多,Telerik Rad Ajax for ASP.NET AJAX控制項,反正很少人在工作上碰到(因為購買要花的錢不少XD)

以下直接進入正題

實作

前言有提到,使用非同步局部刷新實現DropDownList連動下拉選單雖然可以把DropDownList控制項通通丟進UpdatePanel裡

但其實此做法的網頁效能是很差的,因為我曾經在畫面上瘋狂使用UpdatePanel包覆一堆控制項,導致網站回應速度過慢,以前踩過此地雷後,現在我能不使用UpdatePanel就盡量不使用

不過基於ASP.net WebForm的開發模式是事件驅動+控制項為主,又不能像ASP.net MVC一樣使用一般的Html <select> tag,因為WebForm常常PostBack,如此一來<select>裡的值會被重置

所以我找到了AjaxControlToolkit的CascadingDropDown

現在AjaxControlToolKit出到18.1版也支援.net 4.5以上版本,維護組織換成DevExpress (我已很久沒碰ASP.net WebForm專案,想不到一轉眼變化這麼大XD)

剛好也藉此機會使用比較正確的CascadingDropDown來實現連動下拉選單,至少發出去的Ajax Request和Server端Response的內容算乾淨

以下是使用方法

1.先在專案透過NuGet 搜尋AjaxControlToolKit安裝即可,不必像教科書一樣為工具箱加入.dll產生一堆控制項(我想有開發經驗的程式設計師應該都直接寫Code了,很少使用拖曳方式把控制項拖放到畫面上)

安裝完後,Web.config會被自動加上一行↓,必須把tagPrefix記下來,待會在.aspx裡輸入會用到

※如果不想在Web.config註冊assembly的話,在.aspx的畫面上的Page修飾詞註冊AjaxControlToolkit也是可以,請參考:Step by Step Installation Guide

2.在.aspx畫面

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1Test.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Demo</title>
</head>
<body>
    <form id="form1" runat="server">
       <!--畫面上一定要放置ScriptManager控制項-->
       <asp:ScriptManager runat="server" ID="ScriptManager1" />
        
    <!--第一個下拉選單,記得把OnSelectedIndexChanged事件和AutoPostBack兩個屬性拿掉-->
     <asp:DropDownList ID="DropDownList1" runat="server"   />   
        <!--ajaxToolkit為剛剛在Web.config裡的tagPrefix-->
        <!--TargetControlID為想要擴充連動下拉功能的目標控制項-->
       <!-- Category屬性必填,它的值是為了要和WebService溝通用,兩個下拉選單都取名同樣-->
     <ajaxToolkit:CascadingDropDown ID="DropDownList1_CascadingDropDown"   
         runat="server" 
         Category="MyCategoryDemo"  TargetControlID="DropDownList1"
         PromptText="請選擇部門" 
         ServicePath="~/WS/WebService.asmx"  ServiceMethod="getDepts">  
     </ajaxToolkit:CascadingDropDown>  
   
  <!--第二個下拉選單-->
 <asp:DropDownList ID="DropDownList2" runat="server" />  
     
         <!--ParentControlID很直覺地,就是哪個父控制項會連動到DropDownList2-->
        <!--  ServicePath、ServiceMethod 為Ajax呼叫的Url位置和執行的function-->
    <ajaxToolkit:CascadingDropDown ID="DropDownList2_CascadingDropDown"   
        runat="server" Category="MyCategoryDemo"  
        PromptText="請選擇員工" TargetControlID="DropDownList2"   
        ServicePath="~/WS/WebService.asmx"  ServiceMethod="getEmps"   
        ParentControlID="DropDownList1">  
    </ajaxToolkit:CascadingDropDown>  


      
    </form>
</body>
</html>

3.新增一個WebService.asmx供第一個下拉選單和第二個下拉選單呼叫

using AjaxControlToolkit;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace WebApplication1Test.WS
{
    public class Department
    {
        public int ID { get; set; }
        public string DeptName { get; set; }
    }
    public class Employee
    {
        public int ID { get; set; }
        public string empName { get; set; }
        public int Dept_ID { get; set; }
    }
    /// <summary>
    ///WebService 的摘要描述
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // 若要允許使用 ASP.NET AJAX 從指令碼呼叫此 Web 服務,請取消註解下列一行。
    [System.Web.Script.Services.ScriptService] //此Attribute必須加上,才能給Client端 Ajax呼叫
    public class WebService : System.Web.Services.WebService
    {
        /// <summary>
        /// 第一個下拉選單要填寫的Value
        /// </summary>
        /// <param name="knownCategoryValues"></param>
        /// <param name="category"></param>
        /// <returns></returns>
        [WebMethod]
        public CascadingDropDownNameValue[] getDepts(string knownCategoryValues, string category)
        {
            List<Department> depts = new List<Department>()
            {
               new Department(){ ID=1,DeptName="Developer"},
               new Department(){ ID=2,DeptName="PM"},
               new Department(){ ID=3,DeptName="Sales"},
            };
            List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();

            values.AddRange(depts.Select(m => new CascadingDropDownNameValue
            {
                name = m.DeptName,
                value = m.ID.ToString()
            }));

            return values.ToArray();
        }
        [WebMethod]
        public CascadingDropDownNameValue[] getEmps(string knownCategoryValues, string category)
        {
            List<Employee> emps = new List<Employee>()
            {
               new Employee(){ ID=1, empName="Jack Dev", Dept_ID=1},
               new Employee(){ ID=2, empName="Tom Dev", Dept_ID=1},
               new Employee(){ ID=3, empName="Mary PM", Dept_ID=2},
               new Employee(){ ID=4, empName="Cherry PM", Dept_ID=2},
               new Employee(){ ID=5, empName="Will Sales", Dept_ID=3},
            };
            List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
            //解析第一個下拉選單傳遞過來的值
            StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
            //第一個下拉選單所選定的Dept_ID
            int Dept_ID = Convert.ToInt32(kv[category]);
            //查詢員工
            IEnumerable<Employee> query = emps.Where(m => m.Dept_ID == Dept_ID);
            //把員工資料加入回傳的集合
            values.AddRange(query.Select(m => new CascadingDropDownNameValue
            {
                name = m.empName,
                value = m.ID.ToString()
            }));

            return values.ToArray();
        }
    }
}

4.執行結果如下

如果透過瀏覽器開發者工具查看,比起UpdatePanel包覆DropDownList版本的連動下拉選單,AjaxControlToolkit的CascadingDropDown的Request、Response傳輸量減少很多,網頁效能較好,而且也比較接近正確的連動下拉選單運作方式(如果有寫過JSP、ASP.net MVC的人應該懂XD)