使用CascadingDropDown实现级联式下拉框
引言
级联式下拉框,有时也叫联动下拉框,也是Web页面中一个很常见的功能。就是假设有若干个下拉框,比如说有3个,分别显示 省份、城市、街道,当选择某一省份后,城市下拉框仅显示属于该省的城市,选择好城市之后,街道下拉框仅显示属于该城市的街道。记得以前做Asp的时候,做一个类似这样的下拉框需要花费不少的力气,编写不少的javascript代码。如今到了Asp.Net时代,使用Ajax Control Toolkit中的CascadingDropDown控件,再配合普通的DropDownList控件以及Web Serive,实现这样一个功能变得非常容易了。本文将一步步来实现它。
数据库和数据访问
这一部分和我们本文要讨论的主题实际上没有关系,但是我们需要“可以级联显示”的数据,因此需要创建一个范例数据库,同时还需要有对它的数据访问方法。对于这部分,我仅给出简单的步骤,依然以省份、城市、街道为例:
- 新建一个解决方案,添加网站,并且在App_Data中创建一个数据库SiteDB。
- 在SiteDB中创建三张表,分别为Province(Id, Name),City(Id, Name, ProvinceId),Street(Id, Name, CityId)。其中Id为int自增类型,且为主键;Name为Varchar(50)。ProvinceId和CityId则为外键。
- 为上面三个表添加一些范例数据,如果你觉得麻烦,那么可以拷贝本文附带代码中的数据库。
- 在App_Code中添加一个SiteDataSet数据集。然后打开“Server Explore(服务器浏览器)”将上面三张表拖进去。
- 分别对它们的TableAdapter进行配置,其中Province的为获得全部数据,而City和Street分别根据ProvinceId和CityId获取数据,最终的SiteDataSet如下图所示:
OK,这样数据库和数据访问就完毕了,接下来我们看下Web页面。
Web页面布局和设置
首先我们在站点中添加一个“Ajax-enabled WCF Service(启用了AJAX的WCF服务)”,命名为AddressService,暂时不用管它的实现。在ScriptManager中对它进行注册(在本文中这里不注册也可以):
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/AddressService.svc" />
</Services>
</asp:ScriptManager>
打开Default.aspx,像下图这样对页面进行布局:
三个DropDownList分别的Id为ddlProvince、ddlCity、ddlStreet。之后从AjaxControlToolkit中拖三个CascadingDropDown到页面上,像下面这样对其进行配置:
<ajaxControlToolkit:CascadingDropDown ID="CascadingDropDown1"
runat="server"
TargetControlID="ddlProvince"
Category="Province"
PromptText="请选择省份...."
LoadingText="加载中,请稍后 ..."
ServicePath="AddressService.svc"
ServiceMethod="GetProvince"
>
</ajaxControlToolkit:CascadingDropDown>
<ajaxControlToolkit:CascadingDropDown ID="CascadingDropDown2"
runat="server"
TargetControlID="ddlCity"
ParentControlID="ddlProvince"
Category="City"
PromptText="请选择城市...."
LoadingText="加载中,请稍后 ..."
ServicePath="AddressService.svc"
ServiceMethod="GetCity"
>
</ajaxControlToolkit:CascadingDropDown>
<ajaxControlToolkit:CascadingDropDown ID="CascadingDropDown3"
runat="server"
TargetControlID="ddlStreet"
ParentControlID="ddlCity"
Category="Street"
PromptText="请选择城市...."
LoadingText="加载中,请稍后 ..."
ServicePath="AddressService.svc"
ServiceMethod="GetStreet"
>
</ajaxControlToolkit:CascadingDropDown>
这三个CascadingDropDown分别对应之前添加的通常的DropDownList控件,下面是对其各个属性的一个简单说明:
- TargetControlID:与之协作的DropDownList控件的ID。
- ParentControlID:父级下拉框的ID,显然顶级下拉框ddlProvince不含有父级下拉框,因此不含有ParentControlID。
- Category:此下拉框的“类别”,也可以称为名称,具体使用到后面再说,这里说了也不好理解。
- PromptText:进行选择之前显示的文本。
- LoadingText:加载时显示的文本,因为需要与服务端进行通信,所以难免会产生延迟,这里的文本便是在延迟时显示的文本。
- ServicePath:获取数据时的Web服务。此处即为我们的WCF服务。
- ServiceMethod:获取数据的方法。这些方法由WCF服务实现,显然我们暂时并没有实现它。
好了,现在页面部分就设置完毕了,我们最后看下WCF服务的实现,它仅仅是使用第一步创建的数据集来获取数据。
编写WCF服务代码
WCF服务所要实现的实际就是上面CascadingDropDown中所设置的三个用于获取数据的方法:GetProvince()、GetCity()和GetStreet()。因为需要在页面的后置代码中使用AjaxControlToolkit所定义的类型,所以如果你和我一样使用了GAC来部署AjaxControlToolkit程序集的话,那么需要首先在Web.Config中对它进行注册:
<compilation debug="true">
<assemblies>
<add assembly="AjaxControlToolkit, Version=3.0.20820.37372, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e"/>
<!-- 省略其他 -->
</assemblies>
</compilation>
接下来我们来实现这三个方法,先看App_Code中AddressService下的GetProvince()方法的实现,显然,它获得所有的省份:
public class AddressService
{
[OperationContract]
public CascadingDropDownNameValue[] GetProvince(string knownCategoryValues, string category)
{
Thread.Sleep(1000); // 等待1秒,模拟网络延迟
SiteDataSetTableAdapters.ProvinceTableAdapter provinceAdapter
= new SiteDataSetTableAdapters.ProvinceTableAdapter();
SiteDataSet.ProvinceDataTable provinceTable = provinceAdapter.GetProvince();
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
foreach (SiteDataSet.ProvinceRow province in provinceTable) {
string id = province.Id.ToString();
string name = province.Name;
values.Add(new CascadingDropDownNameValue(name, id));
}
return values.ToArray();
}
}
我们先看下方法的签名,第2个参数category即是我们在CascadingDropDown中定义的category属性的值,它传到了这里供我们做一些处理。第1个参数以“Category:Id”的形式包含父下拉框的category值和id值,因为省份为顶级下拉框,所以此时knownCategoryValues为空字符串。返回值为一个CascadingDropDownNameValue数组,其实就是一个键/值对,用于返回给客户端进行绑定。
方法的内容我觉得不需要有任何解释,都是最平常不过的强类型DataSet的基本操作。需要注意的是Thread.Sleep(1000),这里是为了模拟网络延迟的效果,以让控件能够显示出LoadingText的效果。
接下来,我们再看剩下两个方法GetCity和GetStreet方法的实现:
[OperationContract]
public CascadingDropDownNameValue[] GetCity(string knownCategoryValues, string category)
{
Thread.Sleep(1000);
StringDictionary keyValue =
CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
int provinceId = Convert.ToInt32(keyValue["Province"]);
SiteDataSetTableAdapters.CityTableAdapter cityAdapter =
new SiteDataSetTableAdapters.CityTableAdapter();
SiteDataSet.CityDataTable cityTable = cityAdapter.GetCityByProvinceId(provinceId);
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
foreach (SiteDataSet.CityRow city in cityTable) {
string id = city.Id.ToString();
string name = city.Name;
values.Add(new CascadingDropDownNameValue(name, id));
}
return values.ToArray();
}
[OperationContract]
public CascadingDropDownNameValue[] GetStreet(string knownCategoryValues, string category) {
Thread.Sleep(1000);
StringDictionary keyValue =
CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
int cityId = Convert.ToInt32(keyValue["City"]);
SiteDataSetTableAdapters.StreetTableAdapter streetAdapter =
new SiteDataSetTableAdapters.StreetTableAdapter();
SiteDataSet.StreetDataTable streetTable = streetAdapter.GetStreetByCityId(cityId);
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
foreach (SiteDataSet.StreetRow street in streetTable) {
string id = street.Id.ToString();
string name = street.Name;
values.Add(new CascadingDropDownNameValue(name, id));
}
return values.ToArray();
}
这两个方法是完全类似的,所以我们仅就GetCity()方法说明。假如我们此刻在页面选择了省份,为“陕西”,且它的id为“2”,那么按照上面的说明,knownCategoryValues包含的值为“Province:2”,它含有了父控件的category值和选中的id值,通过它我们就可以获得选中的省份id。我们并不需要手动去处理这个字符串,比如使用Split()方法,而可以使用CascadingDropDown.ParseKnownCategoryValuesString静态方法来完成,如上面的代码所示。
在获得了省份id之后,我们可以调用DataAdapter上的方法,来根据省份id获得其下所包含的城市,这些代码大家应该已经很熟悉了。
好了,现在一切已经就绪,大家可以打开页面看一下实际的效果了:
总结
在这篇文章中,我们又实现了Web中的一个常见功能:级联式下拉框。我们先创建了范例数据库,以及一个强类型DataSet用作数据访问。随后对Web页面进行了布局,设置了控件属性。最后编写了WCF服务,获取客户端所需要的数据。
感谢阅读,希望这篇文章能给你带来帮助!