Sharing values across WP7 pages

When you’re writing a Silverlight application, such as Windows Phone 7 application, it is common to have a value one one page and want to use the value on another page. A good solution for this situation is to use application resources, or to be more specific using the ResourceDictionary for the current application. You can access to the application resources using the Application.Current.Resources property.

ApplicationResources class

If you use application resources frequently, you’ll find that working directly with Application.Current.Resources is a little tedious and can clutter the code with details of working with the ResourceDictionary object. I like to wrap these details in a general purpose ApplicationResources class, and then use that class to implement an ApplicationProperties class specific to my application. It provides a nice degree of abstraction, making my application easier to write and later read, and the right amount of reuse across applications.

Let’s start with the ApplicationResources class that I can use without alteration in any WP7 application. This class encapsulates the low-level details of working with application resources. I’ve remove most comments for readability, but you’ll find them in the source code download, see below.

using System.Windows;

namespace VisualStuart.WP7.Applications
{
  public class ApplicationResources
  {

    public static T GetResource<T>( object key )
    {
      object value = AppResources[ key ];
      return ( value == null )  // if resource doesn't exist or is set to null
        ? default( T )          // return null for reference type, 0 for numeric value types, etc.
        : (T)value;             // otherwise return the resource value
    }

    public static void SetResource( object key, object resource )
    {
      if ( AppResources.Contains( key ) )
        AppResources.Remove( key );

      AppResources.Add( key, resource );
    }

    // Helper methods

    private static ResourceDictionary AppResources
    { get { return Application.Current.Resources; } }
  }
}

The ResourceDictionary stores resources as System.Object types, so the GetResource<T> method adds type safety to the return type. It also deals with two related considerations. First, it is possible for an application to try to get a resource before setting the resource. Rather than flagging that situation as an error, e.g., by throwing an exception, and making the consuming code deal with that, I’ve opted to return a reasonable default value when getting resource before it is set. Second, the resource type might be a value type (such as bool, int, or a struct) which cannot be null. Now, using a key to index into a ResourceDictionary that does not contain that key will return null. That’s an acceptable value for reference types, but will generate an error for value types. The default keyword for generics provides just what is needed for both value and reference types.

The SetResource method hides a distracting little detail that application resources are not mutable: once a resource has been added to the ResourceDictionary its value cannot be changed. However, you can create the illusion of changing a resource’s value by first removing the old resource, if it exists, and then adding the new resource.

While the ApplicationResources class successfully encapsulates most of the details of programming with application resources, it does not address the question of managing the keys used within an application. That’s next.

ApplicationProperties

I really want to avoid unrestricted use of string literals as application resource keys, sprinkling them throughout my code. The big problem with that is what happens when one of those string values changes, or I just type it wrong in the first place. A string value is a string value, my code will compile without error, and I’ll be faced with debugging a problem that can be extremely subtle to detect. So I like to create an ApplicationProperties class for each application that is the exclusive consumer of the ApplicationResources class in the application.

using VisualStuart.WP7.Applications;

namespace WP7ShareValueAcrossPages
{
  /// <summary>
  /// Properties backed by application resources.
  /// </summary>
  public class ApplicationProperties
  {
    // Properties

    // Sample property of a reference type
    public static string UserText
    {
      get { return ApplicationResources.GetResource<string>( userTextKey ); }
      set { ApplicationResources.SetResource( userTextKey, value ); }
    }

    // Sample property of a value type
    public static bool UserLikesPears
    {
      get { return ApplicationResources.GetResource<bool>( userLikesPearsKey ); }
      set { ApplicationResources.SetResource( userLikesPearsKey, value ); }
    }

    // Resource keys

    // A key can be an object of any type. Strings are used here for readability.
    private static readonly string userLikesPearsKey = "userLikesPears";
    private static readonly string userTextKey = "UserText";
  }
}

This class encapsulates the keys used for application resources, and makes the resources available as properties. The setter method also strengthens the type-safety, since the property enforces that the getter and setter use identical types.

Since this class is specific to each application that I write, I can customize it as needed. For example, I can create a read-only property by marking the visibility of the set method as private. Or I can insert additional logic in the getter and setter to match the data semantics of my application.

Using application properties

Putting it all together, I have created a sample Widows Phone 7 application containing two pages. Values set on PageOne are consumed on PageTwo.

using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;

namespace WP7ShareValueAcrossPages
{
  public partial class PageOne : PhoneApplicationPage
  {
    // other code elided...

    protected override void OnNavigatedFrom( NavigationEventArgs e )
    {
      ApplicationProperties.UserText = UserTextBox.Text;
    }

    private void LikePearsCheckBox_Checked( object sender, RoutedEventArgs e )
    {
      ApplicationProperties.UserLikesPears = LikePearsCheckBox.IsChecked ?? false;
    }
  }
}

When navigating away from PageOne, the event handler saves a value from a TextBox control on the page into the UserText application property that I created above. The syntax is clear and simple.

Another event handler is invoked each time a a CheckBox control on the page is checked or unchecked, and the handler sets the UserLikesPears application property accordingly. That leaves open the possibility that the CheckBox may never checked before navigating away from PageOne. That works in a completely natural way in the consuming PageTwo.

using System.Windows.Navigation;
using Microsoft.Phone.Controls;

namespace WP7ShareValueAcrossPages
{
  public partial class PageTwo : PhoneApplicationPage
  {
    // other code elided...

    protected override void OnNavigatedTo( NavigationEventArgs e )
    {
      string userText = ApplicationProperties.UserText;

      if ( string.IsNullOrEmpty( userText ) )
        userText = "(You didn't enter any text)";

      userTextBlock.Text = userText;

      pearPreferenceTextBlock.Text = ApplicationProperties.UserLikesPears
        ? "You like pears"
        : "You do not like pears";
    }
  }
}

When PageTwo is navigate to, the UserText and UserLikesPears application properties are used in setting the text on different controls. If the bool UserLikesPears property was not set on PageOne, the default value for bool (false) is provided. Since the CheckBox on PageOne is initially unchecked, this default value works correctly.

Resources

Download the completed code.

You will need the Windows Phone SDK to compile and run the sample code.

Anatomy of a simple REST client

In Anatomy of a simple REST service we created a service using WFC Web HTTP services. The service has a single resource that supports the GET and POST verbs. Let’s create a client for this REST service.

Creating a client application

Start with the previous solution (see the download at the end) open in Visual Studio, and create a new Console Application: in the Solution Explorer, right-click on the solution node and select Add | New Project | Visual C# | Windows | Console Application and name it AcmePaintClient.

Creating a client DTO

The Colors service from the previous solution has the Color resource’s GET send, and POST receive Color objects. However, they are JSON object on the wire, so on the client side we are free to create any DTO we want so long as it’s compatible.with the JSON object. Compatibility include having properties whose names match the names of the fields in the JSON object. Compatibility does not include having the same class name as was using in the server DTO or preserving the order of the properties..

Add a class named AcmeColor and add properties whose names match the names of the Color DTO defined in the previous solution, like this.

public class AcmeColor
{
  public byte Green { get; set; }
  public byte Blue { get; set; }
  public byte Red { get; set; }
}

Creating a client

First, let’s change the service URL to be a fixed value. In Solution Explorer | AcmePaint double-click on the Properties item. On the AcmePaint Properties window, select the Web tab. Under Servers, verify that Use Visual Studio Development Server is selected, and select Specific port. Note the value of the port, we’ll be using that shortly. My value is 42524.

Next, we’ll add a reference to an assembly we’ll be using. In Solution Explorer, right-click on the AcmePaintClient project and select Add Reference. On the .NET tab, select System.Runtime.Serialization.

In the Program.cs file, add the following using declarations and the GetColor method.

using System;
using System.Net;
using System.Runtime.Serialization.Json;

private static AcmeColor GetColor( int id )
{
  using ( var client = new WebClient() )
  {
    var uri = new Uri(
      string.Format( "http://localhost:42524/Colors.svc/Color/{0}", id ) );

    using ( var stream = client.OpenRead( uri ) )
    {
      var serializer = new DataContractJsonSerializer( typeof( AcmeColor ) );
      return (AcmeColor)serializer.ReadObject( stream );
    }
  }
}

Quite simply, this is using the WebClient.OpenRead method (== open then read) to perform the GET operation, which returns a Stream that contains the returned JSON. Then the DataContractJsonSerialicer.ReadObject method reads the JSON stream into an AcmeColor type.

Also in the Program.cs file, add the following PostColor method.

private static void PostColor( AcmeColor color )
{
  using ( var client = new WebClient() )
  {
    client.Headers[ HttpRequestHeader.ContentType ] = "application/json";

    var uri = new Uri( "http://localhost:42524/Colors.svc/Color" );
    using ( var stream = client.OpenWrite( uri, "POST" ) )
    {
      var serializer = new DataContractJsonSerializer( typeof( AcmeColor ) );
      serializer.WriteObject( stream, color );
      stream.Close();
    }
  }
}

This time we’re using the WebClient.OpenWrite method (== open then write) to perform the POST operation. This method also returns a Stream, but this time it is a stream for us to write the JSON from an AcmeColor type. That task is performed by DataContractJsonSerializer.WriteObject.

Also note that we’ve set the HTTP Content-Type header so that the service knows what it is receiving.

Finally, modify the Main method to call both of our methods.

static void Main( string[] args )
{
  var color = GetColor( 3 );

  Console.WriteLine( "Color: Red={0}, Green={1}, Blue={2}",
    color.Red, color.Green, color.Blue );

  PostColor( new AcmeColor { Red = 0x80, Green = 0xff, Blue = 0x80 } );

  Console.WriteLine( "Press any key to exit..." );
  Console.ReadKey();
}

Running the client

In the Solution Explorer, right-click on the AcmePaintClient project and select Set as StartUp Project. Then press F5 to start with debugging. The following output is displayed:

Color: Red=255, Green=128, Blue=0
Press any key to exit...

Debugging the service

So far you have no real evidence that the PostColor method actually communicated with the Colors.svc service. I did that expressly so that we can also explore how to debug the service, which a very handy thing to know.

Here’s a simple technique based on the following observation. An instance of Visual Studio can only debug a single program at a time; however it can start as many programs as you like without debugging.

Let’s add a pause at the very top of the Program.Main method so that we can control when it starts making service calls.

Console.WriteLine( "Press any key to start..." );
Console.ReadKey();

Next, in the AcmePaint project, open Color.svc.cs (double-click on the Color.svc item) and set a breakpoint on the close curly-brace for the PostColor method, e.g., by clicking in the left column next to that line in the source code, or by setting the cursor on that line and pressing F9.

Now, with AcmePaintClient still set as the start-up project, press Ctrl+F5 to start running the client without debugging.

Return to Visual Studio, and in Solution Explorer, right-click on AcmePaint project and select Set as StartUp Project. Then press F5 to start the service with debugging.

Return to the console window running the AcmePaintClient and press any key so that it starts making calls to the service.

Visual Studio should hit the breakpoint at the end of the PostColor method. View the value of the requestedColor variable in the Locals window (if the Locals window is not displayed select Debug | Windows | Locals.) You can verify that it is the requestedColor value is the same value that was sent by the client.

Cleaning up

In Visual Studio, press F5 to let the service invocation run to completion. In the AcmePaintClient console window, press any key to exit. In Visual Studio, press Shift+F5 to stop debugging the AcmePaint service.

Review

We created a minimal REST service client using the WebClient class to perform HTTP requests for both the GET and POST verbs. We also learned a technique for debugging and stepping into the service.

Resources

Download the completed code

Additional Posts in this series

Anatomy of a simple REST service

Anatomy of a simple REST service

I want to discuss some interesting topics about services, notably service versioning. Let me start by looking at a very simple REST service using WCF Web HTTP services, creating one to explore how it is put together.

Creating a service application

Start by creating a new WCF Service Application project in Visual Studio with File | New | Project | Visual C# | WCF | WCF Service Application and name it AcmePaint. In the Solution Explorer, delete the generated Service1.svc and IService1.cs files.

Creating a DTO

Add a class named Color. This class is a Data Transfer Object (DTO) whose singular goal should be to represent the service’s message as it is transferred from one process to another. This class is responsible for the shape of “the message on the wire.”

public class Color
{
  public byte Red { get; set; }
  public byte Green { get; set; }
  public byte Blue { get; set; }
}

Adding a WCF service

Right-click on the project in the Solution Explorer and select Add | New Item | Visual C# | Web | WCF Service and name it Colors.svc.

Writing the service contract

The service contract is expressed as an the interface IColors with the [ServiceContract] attribute; that part hasn’t changed since WCF was introduced. Edit the IColors interface to define two operations like this.

using System.ServiceModel;
using System.ServiceModel.Web;

namespace AcmePaint
{
  [ServiceContract]
  public interface IColors
  {
    [WebGet( UriTemplate = "Color/{id}" )]
    Color GetColor( string id );

    [WebInvoke( Method = "POST", UriTemplate = "Color" )]
    void PostColor( Color color );
  }
}

The GetColor method is our archetypal method for performing an HTTP request with the GET verb. The [WebGet] attribute tells the WCF infrastructure that this method is invoked using GET, and the UriTemplate named parameter declares the structure of the URL. The id parameter is written in curly braces which declares that its value in the URL will be passed to the method each time the method is invoked.

The PostColor method is our archetypal method invoked using an HTTP request with the POST verb as declared with the [WebInvoke( Method = “POST” )] attribute. Here UriTemplate declares that it uses the same resource name, Color, as the GetColor method, just without any following URL parts.

Note that both methods use the Color class for the resource sent (POST) and received (GET) by the service’s client.

Writing the service implementation

Edit the Colors class in Colors.svc.cs which implements the IColors interface. You can get to this file by double-clicking on the Colors.svc entry in the Solution Explorer. I am going to be focusing on getting data in and out of a service, so the implementations here are intentionally bare bones.

namespace AcmePaint
{
  public class Colors : IColors
  {
    public Color GetColor( string id )
    {
      // retrieve the color from its id here...

      return new Color { Red = 0xff, Green = 0x80, Blue = 0x00 };
    }

    public void PostColor( Color color )
    {
      Color requestedColor = color; // illustrate the type of color parameter

      // do something with color here...
    }
  }
}

Inspecting the .svc file

In the Solution Explorer, right-click on Colors.svc and select View Markup. You should see that it s a one-liner containing the following.

<%@ ServiceHost Language="C#" Debug="true" Service="AcmePaint.Colors" CodeBehind="Colors.svc.cs" %>

Aside from declaring the implementation language and enabling debugging, this file associates a service name, AcmePaint.Colors, with a source code file containing the class that implements the service. We’ll see that service name again in our next step.

Configuring the service

Open the Web.config file and replace its contents with the following.

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.serviceModel>

    <services>
      <service name="AcmePaint.Colors">
        <endpoint address=""
                  contract="AcmePaint.IColors"
                  kind="webHttpEndpoint"/>
      </service>
    </services>

    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name=""
                          helpEnabled="true"
                          defaultOutgoingResponseFormat="Json"/>
      </webHttpEndpoint>
    </standardEndpoints>

  </system.serviceModel>

</configuration>

The <service> element declares the service name using the same value we saw in the Colors.svc file.

Inside of that element we declare a service endpoint. The address attribute is empty which means that the endpoint address will just be the address of the Colors.svc service. The contract attribute names the interface that is our service contract. And the kind attribute declares this endpoint to be a standard web HTTP endpoint, which uses a fixed webHttpBinding. That is a binding configures endpoints to respond to HTTP requests instead of SOAP messages.

The standardEndpoint element has an empty name, making this the default for all webHttpEndpoints. We’ve used this default to enable help and to set the default format for outgoing response messages to be JSON..

Selecting hosting options

In the AcmePaint project in the Solution Explorer, double-click on the Properties item to display the AcmePaint properties window. Select the Web tab. In the Servers section, verify that Use Visual Studio Development Server is selected.

(If you have IIS installed on your local workstation, you could use the option to Use Local IIS Web Server instead.)

Running the service

In the Solution Explorer, select the AcmePaint project, then press F5 to run the project in debug mode.

Selecting a node in the Solution Explorer first is important! Otherwise, e.g., if you have the Colors.svc.cs file open and selected (active), then pressing F5 to run will launch the WCF Test Client which will display an error because this service is not exposing metadata.

Be patient for a moment while the ASP.NET Development Server starts up, and then your browser will launch a page with the URL http://localhost:xxxx/ where xxxx is the auto-assigned port number that was listed on the AcmeProject Properties page where you selected to use the Visual Studio Development Server.

restservicedirectorylisting

In the browser address bar, edit the URL to read http://localhost:xxxx/Colors.svc/help, using the same port number for xxxx as before. This is the help page that we enabled in configuration.

restservicehelppage

One last time edit the URL in the browser address bar, this time to read http://localhost:xxxx/Colors.svc/Color/42, with xxxx as before. This time the browser prompts whether you was to Open, Save or Cancel the response. Select Open. In the Open With dialog, select Notepad | OK. In the Open File dialog select Open. The following JSON is displayed in Notepad

{"Blue":0,"Green":128,"Red":255}

Cleaning up

Close the browser to stop debugging the service.

Review

We created a minimal REST service and used a browser as an impromptu client to GET a resource, with the response data being delivered as JSON.

Resources

Download the completed code

Serializing a DataContract class to an XML string

Sometimes during development I want to have a light-weight approach to logging request and response messages without setting up WCF tracing.​ Regardless of how I am logging (Trace, EntLib, whatever), I need to get a printable version of the data message.

I always recommend treating DataContract classes strictly as data transfer objects (DTO), that is they exist purely for describing the shape of the message on the wire. That means I don’t add any fancy constructors or business logic of any kind to the DTOs. This maintains a separation of concerns. However, all CLR types do have a ToString method, so I an fine with putting it to better use than telling me the name of the type.

In the sample code, CreateSecretRequest overrides ToString using a static helper method, Helper.ToXmlString. That helper method wraps an XmlSerializer and a StringWriter together to serialize the DTO to a string of XML that I can log. All DataContract classes can use the same ToXmlString method to implement their override of ToString in a simple one-liner.

using System.IO;
using System.Xml.Serialization; 

public static class Helper
{
  public static string ToXmlString( object value, string defaultNamespace )
  {
    var writer = new StringWriter();
    (new XmlSerializer( value.GetType(), defaultNamespace )).Serialize( writer, value );
    return writer.ToString();
  }
} 

// a sample data transfer object

using System.Runtime.Serialization; 

[DataContract( Namespace = XmlNamespaces.Secret.CreateSecretRequest )]
public class CreateSecretRequest
{
  [DataMember]
  public string Name { get; set; } 

  [DataMember]
  public string Description { get; set; } 

  public override string ToString()
  { return Helper.ToXmlString( this, XmlNamespaces.Secret.CreateSecretRequest ); }
}

Be careful! This is not the same XML that the service sent or received on the wire. Use WCF Tracing to see what is really happening with your services. This is nearly a 100% genuine approximation of the message, suitable for some quick-and-dirty logging or debugging diagnostics.

Get your current WindowsIdentity name and groups

Are you having a little identity crisis? Not sure who you really are in a given environment? Or what groups you belong to? Find out for real, for sure. Get the name of the current WindowsIdentity (Windows user) and the groups to which the user belongs.

using System;
using System.Text;
using ssp = System.Security.Principal;

var user = ssp.WindowsIdentity.GetCurrent();
var name = user.Name;

var groups = new StringBuilder();
foreach ( var group in user.Groups )
  groups.AppendLine( group.Translate( typeof( ssp.NTAccount ) ).ToString() );

var message = string.Format( "Name: {0}\nGroups: {1}", name, groups.ToString() );

This nugget of code has saved me at least three times when I was convinced that I was someone else or in some other role/group than I really was.

How to demo and test sending email

Making your application send email is a pretty common requirement. But without setting up your very own development SMTP server to actually send that email, how can you demonstrate that capability to the stakeholder or test that it works?

The sample below shows how to send email locally on your workstation. The email will appear as a .eml file in a specified folder. Double-click on the .eml file and it will open in Outlook.

using System.Net.Mail;

namespace SmtpDemo
{
  class Program
  {
    static void Main( string[] args )
    {
      // Note: In .NET 4.0, System.Net.Mail.SmtpClient implements IDisposable;
      // .NET 3.5 and earlier it does not.
      using ( var smtpClient =
                  new SmtpClient( "localhost" )
                  {
                    DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
                    PickupDirectoryLocation = @"C:\Users\visualstuart\Desktop"
                  } )
      {
        string from = "<a href="mailto:support@visualstuart.net">support@visualstuart.net</a>";
        string to   = "<a href="mailto:pipi@visualstuart.net">pipi@visualstuart.net</a>";
        string subject = Your request has been received";
        string body = "We got your request. Hang in there, help is on the way.";

        smtpClient.Send( from, to, subject, body );
      }
    }
  }
}

The interesting bits are in the instantiation of the SmtpClient object. Also note the comment on SmptClient implementing IDisposable in .NET 4.0, but not in earlier versions of the framework. Since I am targeted the .NET 4.0 framework, I’ve treated that instantiation as resource acquisition and enclosed it in a using statement. 

This sample uses no configuration. Alternatively, you can configure these values in the <smtp> element of app.config.

Reversing something that doesn’t belong to you

I was working on an implementation of a REST service recently, making it respond to the HTTP Accept headers on the incoming request. As you can see from the HTTP/1.1 specification, Accept request-headers should be honored in the order that they’re listed. So it was absolutely awesome to learn that

using System.ServiceModel.Web;

var acceptHeaders = WebOperationContext.Current.IncomingRequest.GetAcceptHeaderElements();
foreach ( var acceptHeader in acceptHeaders )
{
  // process acceptHeader...
}

processes the headers in the opposite order in which they are specified in the request. And that’s not awesome in a good sense.

The IncomingWebRequestContext.GetAcceptHeaderElements method returns a System.Collections.ObjectModel.Collection<T> and that is not a class I own or can change. But still I’d like an iterator for Collection<T> that yields up its elements in reverse order so that I can still use the foreach syntax. Since Collection<T> is not a class I can modify, extension methods come to mind. Here’s an extension method for an iterator on Collection<T> that does the trick.

using System.Collections.Generic;
using System.Collections.ObjectModel;

public static class Extensions
{
  public static IEnumerable<T> Reverse<T>( this Collection<T> collection )
  {
    for ( int i = collection.Count - 1; i >= 0; --i )
      yield return collection[ i ];
  }
}

Now I can write the foreach loop to use the extension method.

foreach ( var acceptHeader in acceptHeaders.Reverse() )
{
  // process acceptHeader in the correct order...
}

I am still dumbfounded that GetAcceptHeaderElements reverses the order of the elements (why would that ever be good?) but a few lines sends me on my way without cluttering up the code.

Or you could look it up

I look at a lot of code. Code that was written a long time ago and those coders have long since left the building. Code that someone once cut their C# teeth on. Code that only its mother could love.

A common shortcoming is writing out what should be a table look up with flow control logic. Here’s an example of what I mean.

enum AccountType { Retail, Wholesale, Nonprofit }

private static decimal GetDiscountUsingIf( AccountType type )
{
  if ( type == AccountType.Wholesale )
    return 0.2M;
  else if ( type == AccountType.Nonprofit )
    return 0.1M;
  else if ( type == AccountType.Retail )
    return 0.0M;

  return 0.0M;
}

There’s no joy in this code. If the enumeration is prone to change over time, support and maintenance of this style of code quickly becomes error prone. And why is there a default return value at the end of the GetDiscountUsingIf method? Is it a hedge against additional values being added to the enumerations?

Only slightly better would be to convert if/else-if structure to a switch statement.

private static decimal GetDiscountUsingSwitch( AccountType type )
{
  switch ( type )
  {
    case AccountType.Wholesale:
      return 0.2M;
    case AccountType.Nonprofit:
      return 0.1M;
    case AccountType.Retail:
      return 0.0M;
    default:
      return 0.0M;
  }
}

This time the reason for having a default clause is that without it the compiler will warn that not all code paths return a value. Switch statements are cumbersome (e.g., with the potential for fall-throughs, etc.), the performance isn’t great (average number of comparisons is N/2, and worst case in N comparisons), and I think switch statements in general tend to be overused. So every time you see a switch statement I encourage you to think hard if it can be replaced. In this case, it is easily replaced with a Dictionary from the System.Collections.Generic namespace.

using System.Collections.Generic;

private static readonly Dictionary<AccountType, decimal> discounts =
  new Dictionary<AccountType, decimal>
  {
    { AccountType.Wholesale, 0.2M },
    { AccountType.Nonprofit, 0.1M },
    { AccountType.Retail, 0.0M }
  };

private static decimal GetDiscountUsingDictionary( AccountType type )
{
  if ( discounts.ContainsKey( type ) )
    return discounts[ type ];
  throw new ArgumentOutOfRangeException( "type" );
}

The discounts dictionary structure separates the relationship of the keys and values from the lookup logic. The indexer property, discounts[ type ], does most of the lookup logic, and at O(1) it is more efficient than the O(N) linear search approach of the previous two solutions. With the key-value association and lookup work taken care of with pretty terse syntax, the code is simple enough that I can contemplate what should be done with an AccountType key that’s not in the table, in this case throwing an exception that the argument is out of range.

So the next time you’re got a job that is essentially a dictionary look up, use a Dictionary to look it up.