2010年8月30日 星期一

ASP.NET WebForm DropDownList與Enum

在專案之中,有些不常變動或是固定、為數不多的資料項目,我一般不會去存在資料庫或是其他資料體中,
我會優先選擇使用Enum存放這些KeyValue的資料,不論是取用或是轉換上都蠻方便的。


像是性別、處理、狀態、週期等,都是屬於固定且不容易變動的項目,
例如在此文章的範例中會使用的處理。
/// <summary>
/// 處理狀態
/// </summary>
public enum ProcessType
{
/// <summary>
/// 
/// </summary>
[EnumDescription("未處理")]
Unprocessed = 0,
 
/// <summary>
/// 
/// </summary>
[EnumDescription("處理中")]
Processing = 1,
 
/// <summary>
/// 
/// </summary>
[EnumDescription("已處理")]
Processed = 2
 
}

我存在資料庫中的資料是會存放Enum Value,ex: Unprocess、Processing、Processed。
這樣子的資料在於識別上以及後續的維護上會比較直覺且好理解。
在ProcessType中可以看到每個Enum Type上面都會有個自訂的Attribute「EnumDescription」,
這是在Enum的Key Value資料之外,以Attribute來存放Enum Type的描述。
Reference by: CodeProject: Data Binding an Enum with Descriptions
/// <summary>
/// Provides a description for an enumerated type.
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false)]
public sealed class EnumDescriptionAttribute : Attribute
{
 
#region Fields (1) 
 
private string description;
 
#endregion Fields 
 
#region Constructors (1) 
 
/// <summary>
/// Initializes a new instance of the
/// <see cref="EnumDescriptionAttribute"/> class.
/// </summary>
/// <param name="description">The description to store in this attribute.
/// </param>
public EnumDescriptionAttribute(string description)
: base()
{
this.description = description;
}
 
#endregion Constructors 
 
#region Properties (1) 
 
/// <summary>
/// Gets the description stored in this attribute.
/// </summary>
/// <value>The description stored in the attribute.</value>
public string Description
{
get
{
return this.description;
}
}
 
#endregion Properties 
 
}
取出EnumType項目的GetDescription方法
#region -- GetDescription --
/// <summary>
/// Gets the <see cref="DescriptionAttribute" /> of an <see cref="Enum" />
/// type value.
/// </summary>
/// <param name="value">The <see cref="Enum" /> type value.</param>
/// <returns>A string containing the text of the
/// <see cref="DescriptionAttribute"/>.</returns>
public static string GetDescription(Enum value)
{
if (value == null) throw new ArgumentNullException("value");
 
string description = value.ToString();
FieldInfo fieldInfo = value.GetType().GetField(description);
EnumDescriptionAttribute[] attributes =
(EnumDescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
 
if (attributes != null && attributes.Length > 0)
{
description = attributes[0].Description;
}
return description;
}
 
#endregion

之前我在專案中,就會經常性的在DropDownList的DataItem使用Enum,所以我設計了一個方法來使用。EnumHelper.GetListItem方法
#region -- GetListItem --
/// <summary>
/// Converts the <see cref="Enum"/> type to an <see cref="IList"/>
/// ListItem Array
/// </summary>
/// <param name="enumType">Type of the enum.</param>
/// <returns></returns>
public static ListItem[] GetListItem(Type enumType)
{
if (Checker.IsNull(enumType))
{
throw new ArgumentNullException("enumType", "enumType can not Null or Empty.");
}
 
List<ListItem> list = new List<ListItem>();
foreach (Enum value in Enum.GetValues(enumType))
{
list.Add(new ListItem(GetDescription(value), value.ToString()));
}
return list.ToArray<ListItem>();
}
 
#endregion

在專案中實際使用情境如下:
// 處理狀態
this.ddlProcessStatus.Items.Clear();
this.ddlProcessStatus.Items.AddRange(EnumHelper.GetListItem(typeof(ProcessType)));

而資料的存取與轉換的操作如下:EnumHelper.GetEnumValue方法
#region -- GetEnumValue --
 
///<summary>
/// Allows the discovery of an enumeration value based on the <c>Enum.Parse</c>
///</summary>
/// <param name="text">The text of a enum value of the specified enumeration.</param>
/// <returns>A enum value of the specified enumeration,</returns>
public static T GetEnumValue<T>(string text) where T : struct
{
return (T)Enum.Parse(typeof(T), text, true);
}
 
#endregion

取出資料以及轉換、取得Description
ProcessType process = EnumHelper.GetEnumValue<ProcessType>(this.ProcessStatus);
string description = EnumHelper.GetDescription(process);

如果有將EnumHelper的這些Methods編寫為擴充方法,就會將程式變得夠為簡潔一些。
string description = this.ProcessStatus.GetEnumValue<ProcessType>().GetDescription();

ProcessStatus為某一型別中的一個屬性,其型別為string,而存放的是ProcessType的某個項目的字串
this.ProcessStatus = Checker.IsNull(processStatus) ? ProcessType.Unprocessed.ToString() : processStatus.ToString();

當 DropDownList + Enum 的應用多了之後,就會覺得每次都要寫重複的程式,蠻煩索的。
例如,性別 GenderType,都要重複做一樣的程序
// Gender
this.ddlGender.Items.Clear();
this.ddlGender.Items.AddRange(EnumHelper.GetListItem(typeof(GenderType)));

如果有一個簡單的方式,只要指定DropDownList的DataSource使用Enum就可以簡化一些程序,
之前剛好看到一篇文章,說明如何利用繼承DropDownList後,再去自訂一些指定Enum的方式,就可以簡單使用Enum作為DataSource

Enum List DropDown Control
繼承原有的WebControl 「DropDownList」
藉由直接指定DataSource => EnumType
就可以轉為所需的下拉選單
Text為 EnumDescrition
Value 為 EnumType.ToString()

不過這篇文章的範例是使用VB.NET,而我就把它轉為C#,並且做了一些修改,以符合我的需求,

DefaultSelectedText:
增加了DefaultSelectedText屬性,可以自行指定DropDownList第一個ListItem的顯示文字,而此ListItem的Value是沒有值的。
當DefaultSelectedText指定空字串或是string.Empty時,DropDownList就不會有第一項的提示ListItem

NamespaceOfEnumType:
參考文章的內容是DropDownList所指定的DataSource必須要包含NameSpace,
因為我將此自訂的WebControl以及自訂的EnumType都放至於我自己的mrkt.Library中,
所以預設使用的Namespace是「mrkt.Library」
增加此屬性,可在日後當使用非mrkt.Libray的EnumType時,可讓程式找得到指定的EnumType。
/// <summary>
/// EnumType DropDownList
/// </summary>
public class EnumTypeDropDownList : DropDownList
{
#region -- Fields and Properties --
 
private Type _enumType;
 
private string _NamespaceOfEnumType;
public string NamespaceOfEnumType
{
get { return _NamespaceOfEnumType; }
set { _NamespaceOfEnumType = value; }
}
 
private string _DefaultSelectedText;
public string DefaultSelectedText
{
get { return _DefaultSelectedText; }
set { _DefaultSelectedText = value; }
}
 
public new string DataSource
{
get
{
return _enumType.ToString();
}
set
{
_enumType = string.IsNullOrEmpty(this.NamespaceOfEnumType)
? System.Type.GetType(value, true, true)
: Reflector.GetType(NamespaceOfEnumType, value);
}
}
 
public new Enum SelectedValue
{
get
{
string requestValue = this.Page.Request.Params[this.UniqueID];
return string.IsNullOrEmpty(requestValue)
? null
: Enum.Parse(_enumType, requestValue) as Enum;
}
set
{
base.SelectedValue = value.ToString();
}
}
 
#endregion
 
#region -- Constructors --
 
public EnumTypeDropDownList()
{
this.NamespaceOfEnumType = "mrkt.Library";
this.DefaultSelectedText = "Please Select An Item ...";
}
 
#endregion
 
#region -- DataBind --
/// <summary>
/// 將資料來源繫結至叫用的伺服器控制項和它的全部子控制項。
/// </summary>
public override void DataBind()
{
this.Items.Clear();
this.Items.AddRange(EnumHelper.GetListItem(_enumType));
if (!string.IsNullOrEmpty(this.DefaultSelectedText))
{
this.Items.Insert(0, new ListItem(this.DefaultSelectedText, string.Empty, true));
}
base.DataBind();
}
#endregion
 
}

應用於專案中的實際情況:
ASPX原始碼
DataSource必須於原始碼裡做指定,指定需要使用的EnumType
<%@ Register Assembly="mrkt.Library.Web" Namespace="mrkt.Library.Web.WebControls" TagPrefix="mrkt" %>
 
...
...
...
 
ProcessType: 
<mrkt:EnumTypeDropDownList ID="ddlProcessType" runat="server" DataSource="ProcessType">
</mrkt:EnumTypeDropDownList>

CodeBehind
DefaultSelectedText,預設的第一個提示項目,只有Text,沒有Value
如果希望一開始就指定EnumType的某一個項目,就直接在SelectedValue屬性指定EnumType的某個項目
最後就用DataBind(),就可以將EnumType資料繫結到我們所另外繼承並設計的EnumTypeDropDownList中
this.ddlProcessType.DefaultSelectedText = "請選擇處理項目";
this.ddlProcessType.SelectedValue = ProcessType.Unprocessed;
this.ddlProcessType.DataBind();

實際頁面:

image

沒有指定DefaultSelectedText
this.ddlProcessType.DefaultSelectedText = string.Empty;
this.ddlProcessType.DataBind();

頁面

沒有出現提示文字的ListItem
image

取得DropDownList所選擇的項目資料
image

Code
protected void Button1_Click(object sender, EventArgs e)
{
if (this.ddlProcessType.SelectedValue == null)
{
Label1.Text = "請選擇項目";
}
else
{
Label1.Text = string.Concat(
this.ddlProcessType.SelectedValue.ToString(),
" - ",
this.ddlProcessType.SelectedValue.GetDescription()
);
}
}

可以看到無論是資料的繫結或是資料的取得與轉換都少了許多的程序與步驟。
提供給各位參考。



沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。