When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, August 4, 2010

Defining Descriptive Text for Enumeration Members

By Scott Mitchell


Introduction


An enumeration is a special type in the .NET Framework that is comprised of a number of named constants. While you might not have created an enumeration type yourself, you have likely used enumerations many times in day-to-day programming. For example, the rows in a GridView have a RowType property that returns an enumeration of type DataControlRowType that indicates the row's type: Header, Footer, DataRow, and so on.

When working with an enumeration we may need to display a descriptive message based on the enumeration's value. For example, using ASP.NET's Membership system you can programmatically create a new user account calling the Membership class's CreateUser method. This method specifies the success or failure of the operation via the MembershipCreateStatus enumeration. This enumeration has members like Success, InvalidUserName, InvalidPassword, DuplicateUserName, and the like. When calling this method from an ASP.NET page you might want to show the user a descriptive message based on this enumeration value.

This article explores three different ways to provide descriptive text for enumeration members. Read on to learn more!

- continued -

A First Stab At Displaying Descriptive Text Based On An Enumeration Value


You're sitting at your desk, feverishly pounding out code trying to hit a near impossible deadline. Your building a website that needs to support user accounts and are in the middle of creating the registration page. Rather than use the CreateUserWizard Web control, you have a custom user interface and are calling the Membership.CreateUser method in the page's code-behind class to create the new user account. You've got your user interface done and you've written the code to call Membership.CreateUser and to redirect the user to a subsequent page should when their account was created successfully. All that remains is to add some feedback to the user if their account was not created successfully.

Your code to call the Membership.CreateUser method looks something like this:

MembershipCreateStatus createStatus;

MembershipUser newUser = Membership.CreateUser(
                                        userName,
                                        password,
                                        email,
                                        securityQuestion,
                                        securityAnswer,
                                        true
                                        out createStatus
                                    );

Note that a variable of type MembershipCreateStatus is created before the call to the Membership.CreateUser. This variable is then passed into the Membership.CreateUser method as an out parameter (or as a ByRef parameter, in VB parlance). In a nutshell, the Membership.CreateUser method assigns a value to this variable based on what happened when attempting to create the user account. Your job is to display a message based on the value of createStatus after the call to Membership.CreateUser.

The simplest approach and the one you might take given your time constraints is to simply add a switch statement or a series of if ... else like so:

if (createStatus == MembershipCreateStatus.InvalidUserName)
   // Let the user know the username they entered is invalid...
else if (createStatus == MembershipCreateStatus.InvalidPassword)
   // Let the user know the password they entered is invalid...
else if (createStatus == MembershipCreateStatus.InvalidEmail)
   ...
...

See Joe Stagner's blog entry Code Snippet: Manually Creating a New ASP.NET Membership User in C# for a more thorough example of displaying a descriptive message based on the value of the MembershipCreateStatus enumeration returned by the Membership.CreateUser method...

This approach certainly gets the job done, but has a couple of drawbacks. First, it embeds the business rules - the association of the descriptive text with the enumeration values - in the presentation layer. More importantly, it tightly couples this logic with this web page. What happens if you need to also add a registration option to the admin pages that allows for an administrator to register a new user? You'd have to copy and paste these if ... else statements, which means if the verbage needs to change at a future date you've got two places that need to be updated.

A better approach is to put the descriptive text for each entry somewhere else. This article explores how to move this textual information to three different places: into an extension method; into a resource file; and into the enumeration definition.

Moving the Switch Statement / If ... Else Statements Into An Extension Method


Extension methods are a useful way for a developer to add methods to an existing type. Typically, extension methods are used to add functionality to classes in the .NET Framework, but they can also be used to add methods to enumerations, whether those enumerations exist as part of the .NET Framework or were created by you. For an in-depth look at extension methods be sure to read Extending Base Type Functionality with Extension Methods.

With extension methods we can add a method to an enumeration type - named, say, ToDescriptiveString() - that contains the switch statement or if ... else statements used to determine the text to emit based on value. The following code creates such an extension method:

public static class MembershipCreateStatusExtensions
{
   public static string ToDescriptiveString(this MembershipCreateStatus createStatus)
   {
      if (createStatus == MembershipCreateStatus.InvalidUserName)
         return "The user name was not found in the database.";
      else if (createStatus == MembershipCreateStatus.InvalidPassword)
         return "The password is not formatted correctly.";
      else if (createStatus == MembershipCreateStatus.InvalidEmail)
         return "The e-mail address is not formatted correctly.";
      ...
   }
}

With this code in place, the ASP.NET page that calls the Membership.CreateUser method has been greatly simplified, as we can now emit the status using one line of code, as the snippet below shows. First, the Membership.CreateUser method is called to create the user account. If the user account was created successfully, the user is redirected to UserCreated.aspx. If, however, the account was not successfully created the failure reason is displayed in a Label control (lblCreationError). The text explaining the reason for failure is returned by the ToDescriptiveString() extension method.

MembershipCreateStatus createStatus;

MembershipUser newUser = Membership.CreateUser(...);

if (createStatus == MembershipCreateStatus.Success)
   Response.Redirect("UserCreated.aspx");
else
   lblCreationError.Text = createStatus.ToDescriptiveString();

Putting the logic that determines the descriptive text based on the selected enumeration value into the ToDescriptiveString() extension method keeps the clutter out of the ASP.NET page, making it more terse and more readable. More importantly, the extension method serves as a single location for this logic, meaning that if we need to display a descriptive message based on the value of the MembershipCreateStatus enumeration elsewhere in our codebase, we don't have to rewrite that code or copy and paste. Instead, we just call the same extension method.

Pulling Enumeration Descriptions From A Resource File


Rather than hard-coding the descriptive text in the extension method you may want to move the text associated with each enumeration value to a separate store. This is especially useful if the text for each status can vary based on some external condition. For example, if you are building a multilingual website then the text associated with each enumeration status would vary based on the language of the user visiting the site. The various translations of an enumeration's descriptive text can be stored in a resource files.

The demo available for download at the end of this article includes a demo using resource files (ByResource.aspx). This page has a TextBox where the visitor can enter a directory on the web server's file system. A GridView control beneath the TextBox lists the files in the selected directory along with their file size (in bytes) and file attributes. The list of files in the specified directory is retrieved using the DirectoryInfo class's GetFiles method, which returns an array of FileInfo objects. The FileInfo class has a property named Attributes which is an enumeration of type FileAttributes. This enumeration indicates the attributes on the file - whether it's archived, a hidden file, read-only, and so on. (Bear in mind that multiple attributes can apply to a single file. For more information on how enumerations can be used to specify multiple values and for code examples that show how to determine which, of the possible values, are indeed selected, refer to Understanding Enumerations.)

In addition to the TextBox and GridView, there's also a DropDownList on the page from which the user can select his preferred language - English or French. Based on the selection, the thread processing the request has its CurrentCulture property assigned to the appropriate culture string - "en-US" for English and "fr-FR" for French.

There is a ToDescriptiveStringUsingResourceFile() extension method defined for the FileAttributes enumeration that is similar to the ToDescriptiveString() extension method for the MembershipCreateStatus enumeration that we examined earlier. However, instead of hard-coding the descriptive text, the ToDescriptiveStringUsingResourceFile() extension method calls the GetGlobalResourceObject method to retrieve the descriptive text from a resource file. There are two resource files defined in the App_GlobalResources folder:

  • FileAttributeDescriptions.resx - contains the descriptive text entries for the FileAttributes enumeration for the default culture ("en-US").
  • FileAttributeDescriptions.fr.resx - contains the descriptive text entries for the FileAttributes enumeration for the French culture ("fr").
For example, in the FileAttributeDescriptions.resx file there are entries like:
  • Archive - The file is archived.
  • Compressed - The file is compressed.
  • Normal - This file has no attributes set.
Whereas in the FileAttributeDescriptions.resx file these entries are en français:
  • Archive - Ce dossier est archivé.
  • Compressed - Ce dossier est serré.
  • Normal - Ce dossier n'a pas de série d'attribut.
The following code shows the ToDescriptiveStringUsingResourceFile() extension method. Because the FileAttributes enumeration can specify multiple values, a list of strings (descriptions) is maintained, adding the descriptive text to the list for each matching enumeration value. The string returned by the ToDescriptiveStringUsingResourceFile() method is the combination of these individual descriptions, each separated by a <br /> tag. Also take note of the code used to retrieve a value from the resource file. The GetGlobalResourceObject method is passed the name of the global resource file, the name of the string to retrieve, and the culture of the thread processing the request. The GetGlobalResourceObject method uses this information to retrieve the appropriate block of text from the appropriate resource file.

public static class FileAttributesExtensions
{
   public static string ToDescriptiveStringUsingResourceFile(this FileAttributes attributes)
   {
      var descriptions = new List<string>(13);

      if ((attributes & FileAttributes.Archive) > 0)
         descriptions.Add(HttpContext.GetGlobalResourceObject("FileAttributeDescriptions", "Archive", Thread.CurrentThread.CurrentCulture) as string);

      if ((attributes & FileAttributes.Compressed) > 0)
         descriptions.Add(HttpContext.GetGlobalResourceObject("FileAttributeDescriptions", "Compressed", Thread.CurrentThread.CurrentCulture) as string);

      if ((attributes & FileAttributes.Directory) > 0)
         descriptions.Add(HttpContext.GetGlobalResourceObject("FileAttributeDescriptions", "Directory", Thread.CurrentThread.CurrentCulture) as string);

      ...

      return string.Join("<br />", descriptions.ToArray());
   }
}

The screen shot below shows the ByResource.aspx page when viewing the files of the C:\Windows directory with English as the preferred language.

The file attribute text is displayed in English.

Here is the same page after selecting the French option from the drop-down list. Note that the file attribute text now uses the French translation.

The file attribute text is displayed in French.

Using Attributes To Define Descriptive Text Within the Enumeration Itself


So far we have examined two techniques for storing descriptive text for an enumerations members - in an extension method and in resource files. You can alternatively store an enumeration's descriptions directly in the enumeration definition itself, as attributes. (This option only works for enumerations you create and not for existing enumerations in the .NET Framework.) When defining an enumeration you can use the DescriptionAttribute class, which is found in the System.ComponentModel namespace. To use this attribute, be sure to add a using System.ComponentModel statement to the file where the enumeration is defined, and then you can use the DescriptionAttribute class like so:

using System.ComponentModel;

public enum OldComputers
{
   [Description("Packard Bell Legend IV")]
   PackardBell,

   [Description("Commodore SX-64")]
   Commodore,

   [Description("Tandy 1000 HX")]
   Tandy
}

The DescriptionAttribute class allows us to supply descriptive text for each enumeration member directly in the enumeration itself. We can then read these attribute values programmatically using reflection. The following extension method comes from Brenton House's blog entry Extension Methods with Enum Description:

public static class EnumAttributes
{
   private const char FLAGS_ENUM_SEPARATOR_CHARACTER = ',';

   public static string ToDescriptiveTextUsingAttributes(this Enum value)
   {
      var entries = value.ToString().Split(FLAGS_ENUM_SEPARATOR_CHARACTER);

      var description = new string[entries.Length];

      for (var i = 0; i < entries.Length; i++)
      {
         var fieldInfo = value.GetType().GetField(entries[i].Trim());
         var attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
         description[i] = (attributes.Length > 0) ? attributes[0].Description : entries[i].Trim();
      }

      return String.Join(", ", description);
   }
}

First things first - note that this extension method is applied to the Enum type, which is the type that all enumerations extend. That means you'll have this extension method at your disposal for any enumeration. The ToDescriptiveTextUsingAttributes() method starts by converting the enumeration value into a string. If the enumeration value does not support multiple values being selected (or has only one value selected) then the ToString() method returns the name of the selected member. (The member names in the OldComputers enumeration are PackardBell, Commodore, and Tandy.) If the enumeration supports multiple values and has more than one value selected, ToString() returns the names of the selected members separated by a comma.

Next, a new string array is created (description) to hold the descriptive text for the selected enumeration member(s). (The descriptive texts in the OldComputers enumeration are: "Packard Bell Legend IV"; "Commodore SX-64"; and "Tandy 1000 HX".) The selected member(s) are then enumerated via a for loop and the GetField and GetCustomAttributes methods are used to first get information about the enumeration member and then about its Description attribute. If there was a Description attribute for the selected member then it is used as the descriptive text; otherwise, the name of the member is used. After the selected member(s) are enumerated, they are returned as a string, with each member separated by a comma.

The demo available for download includes a page that illustrates using the ToDescriptiveTextUsingAttributes() method to populate a DropDownList control with the options from an enumeration, using the descriptive text as the text of the DropDownList and the enumeration member name as the value. The following code creates three ListItem objects for the three members in the OldComputers enumeration and then adds them to the DropDownList's Items collection:

var items = new ListItem[3]
{
   new ListItem(
      OldComputers.Commodore.ToDescriptiveTextUsingAttributes(),
      OldComputers.Commodore.ToString()
   ),
   new ListItem(
      OldComputers.PackardBell.ToDescriptiveTextUsingAttributes(),
      OldComputers.PackardBell.ToString()
   ),
   new ListItem(
      OldComputers.Tandy.ToDescriptiveTextUsingAttributes(),
      OldComputers.Tandy.ToString()
   )
};

ddlOldComputers.Items.AddRange(items);

The above code results in a DropDownList with the following display:

The enumeration's descriptive text is displayed in the DropDownList.

Conclusion


When working with enumerations there are times when it's useful to associate a human-friendly, descriptive text with each enumeration value. As we saw in this article, this can be accomplished in a number of ways. The text can be hard-coded within an extension method. It can be embedded in resource files, which is a great option for multilingual sites. Or it can be defined as part of the enumeration itself, using attributes. Each choice has some pros and cons, but all three are better options than writing if ... else statements in your presentation layer to display descriptive text based on an enumeration value.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the code demos presented in this article
  • Further Readings:


  • Understanding Enumerations
  • Extending Base Type Functionality with Extension Methods
  • Localizing ASP.NET Web Pages By Using Resources
  • Extension Methods with Enum Description


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article