Partager via


Exception Handling in the Stock Trader V2 Reference Implementation

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

The latest Enterprise Library information can be found at the Enterprise Library site.

patterns & practices Developer Center

On this page:
Goals for Exception Handling in the Stock Trader V2 | Unhandled Exceptions | Handling Exceptions When Calling Services - Returning the Results from a Service, Correlating the Start and End of an Asynchronous Operation, Handling Exceptions in the Asynchronous Service Agent, Displaying Error Messages | More Information

In this chapter, we'll discuss how we applied exception handling in the Stock Trader V2 reference implementation. Exception handling is a true cross cutting concern, so it was applied to all modules. The News Module also applies exception handling using interception, but this is described in the chapter on Interception in the Stock Trader: The News Module.

Goals for Exception Handling in the Stock Trader V2

When we started to work on the reference implementation, we had the following goals:

  • Dealing with unhandled exceptions: If an unhandled exception occurs, we want to display a user-friendly error and exit the application.
  • Unified service exception handling mechanism: The****Stock Trader V2 uses several web service technologies, such as plain WCF services, WCF RIA Services and WCF Data Services. Each of these technologies has its own way of raising exceptions on a callback. These implementation details should be hidden by the service agent classes, so the service agents should provide a uniform mechanism for notifying the calling classes of exceptions.
  • Retrying after service exceptions: If a web service call fails for some reason, then the user should have the option to retry. If the web service call is the result of a button click, then the user should be able to click the button again. However, if a screen needs to load some data before it can be displayed, and the web service call to retrieve that data fails, then the UI should display a screen with a friendly error message and a retry button.

Unhandled Exceptions

Catching unhandled exceptions is really easy in Silverlight. By default, the App.xaml.cs file, which is located in the root of the StockTraderRI.Silverlight project, contains an event handler called Application_UnhandledException. This is an easy place to perform centralized exception handling.

The following code comes from the Application_UnhandledException event in the App.xaml.cs file, which demonstrates how unhandled exceptions are caught and then handled by the Exception Manager:

if (!enterpriseLibraryConfigured)
{
    // If enterprise library is not configured. Just show an error message
    // and exit (The error window will exit the application when the user 
    // closes the error message)
    App.Current.RootVisual = new UserControl();
              
    ErrorWindow.CreateNew("An unknown problem has occurred while starting the application", e.ExceptionObject, Guid.Empty, true);
    return;
}
// Call the Unhandled exception policy. This should: 
// 1. Log the error
// 2. Show a friendly error message
// 3. Exit the application (The DisplayMessageExceptionHandler will do this)
var exceptionManager =       
    EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
exceptionManager.HandleException(e.ExceptionObject, 
    "Unhandled Exception");

// set the exception to handled
e.Handled = true;

First of all, if an exception has occurred while Enterprise Library was being configured, then we can't rely on Enterprise Library to handle the exception properly. In that case, we're just showing an error message.

The following diagram shows how the exceptions get handled by the Exception Manager:

Follow link to expand image

The unhandled exceptions are handled using the Unhandled Exception policy. This policy is configured to:

  • **Log the exception
    **The Exception Handling block will forward the exception to the Logging Application Block, to a category called Exception, with severity of Critical. In the configuration for the logging block, this category is configured to be logged to the Remote Service trace listener, to the log file in isolated storage and to the developer trace window using the Notification trace listener. The Remote Service trace listener sends it back to the server. There, it will be handled by the Enterprise Library 5.0 Logging Application Block. This block is configured so that all log messages that are sent to the server are written to the Windows Event Log.
  • Show a friendly error message
    A custom exception handler, called DisplayMessageExceptionHandler
    , was written to display a friendly error message to the user. In order to use this class through configuration, we also needed to create a corresponding display error message exception handler data class. The DisplayMessageExceptionHandler can also be configured to exit the application after showing the technical error message.

Handling Exceptions When Calling Services

In Stock Trader V2, all interactions with web services are encapsulated within special service agent classes. These service agents abstract the technical implementation details for accessing the web services from the rest of the application. They are also very well suited to applying exception handling to web services.

Not all of the techniques described in this chapter use the Exception Handling block. They are more of a general description of how you can apply exception handling with asynchronous services.

Returning the Results from a Service

The service agents all use a callback action to notify the caller that the service has completed. This callback is also responsible for handing the result of the service callback to the caller. For example, the order service agent has a method to retrieve a list of orders. If this method executes successfully, then the callback should pass the list of orders to the caller. However, if there was an exception while accessing the web service, then the callback should return the exception that occurred to the calling method.

To make this process more manageable, two classes have been introduced. The AsyncServiceRequest and ServiceResult classes. The AsyncServiceRequest encapsulates the callback to notify the user interface that the download request has completed. It has several helper methods that make it easier for the service agent to either return the requested data or return an error.

public class AsyncServiceRequest<T> : IAsyncServiceRequest
    where T : ServiceResult, new()
{
    // Creates the Service Request class, which wraps the callback action
    public AsyncServiceRequest(Action<T> callback, object userState = null){}

    // Typically, this class is passed as userstate to an async web service
    // call. This method helps to restore it from the user state.
    public static AsyncServiceRequest<T> RestoreFromUserState(object userData){}

    // It's common to have to provide additional user state. 
    public object UserState { get; set; }

    // Helps the service agent to return a list of validation errors to the UI.
    public ServiceResult ReturnValidationErrors(
        ValidationErrorException validationErrorException) {}

    // Helps the service agent to return a regular result to the UI.
    public ServiceResult ReturnResult(T result){}

    // Helps the service agent to return an error to the UI.
    public ServiceResult ReturnError(Exception exception){}

    // Helps the service agent to return an empty result to the UI.
    public ServiceResult ReturnEmpty() {}

    // Helps to implicitly convert an action to an AsyncServiceRequest
    // to reduce the need to create one explicitly. 
    public static implicit operator AsyncServiceRequest<T>(
        Action<T> requestCompleteCallback){}

    // Event that gets fired before the callback is executed
    // to allow the callback to be intercepted and altered. 
    public event EventHandler<AsyncServiceRequestOnCallbackEventArgs> 
        OnCallback = delegate { };
}

The ServiceResult class encapsulates the result of a service call. This can either be the data that was actually requested, a set of validation results, or a technical exception that has occurred. There is also a generic variant of the ServiceResult, which makes it easier to program in a typed way, and a ServiceListResult class, which makes it easier to return a list of items.

All service agents use the AsyncServiceRequest class to start an asynchronous download and the ServiceResult to notify the caller of the results.

This is the signature of the ServiceResult and ServiceListResult class:

[DataContract]
public class ServiceResult<T>
{
    public IEnumerable<ValidationResult> ValidationResults { get; private set; }

    public Exception Error { get; set; }

    public bool HasError { get; set; }
    public bool IsHandled { get; set; }
    public object UserState { get; set; }

    [DataMember]
    public T Result { get; set; }
}

public class ServiceListResult<T> : ServiceResult<IEnumerable<T>> {}

You can see that the ServiceResult has properties for passing the result, but also properties for conveying exceptions and server-side validation errors.

In the OrderServiceAgent, the method to retrieve the list of orders has the following signature:

public void LoadOrders(AsyncServiceRequest<ServiceListResult<Order>> 
    callbackToUpdateUI, object userState)

You can see that the LoadOrders method receives an object of the type AsyncServiceRequest of ServiceListResult of Order. This means that the callback will return either a list of orders, or an exception.

In the OrderListViewModel, this is how the service list result is used:

public void StartLoadingOrders()
{
    // Start to load the orders asynchronously, and call the OnOrdersLoaded Method 
    // when done.
    Action<ServiceListResult<Order>> asyncServiceRequest = OnOrdersLoaded;
    ordersServiceAgent.LoadOrders(asyncServiceRequest, null);
}

public void OnOrdersLoaded(ServiceListResult<Order> result)
{
    // The order has been loaded.
    if (result.HasError)
    {
        // There were errors. Display a friendly error message.

        // Mark the error as handled. If you don't mark the error as handled
        // it will get rethrown. 
        result.IsHandled = true;
    }
    else
    {
        // Show the Orders in the user interface.
    }
}

You can see that the ViewModel has to explicitly set the IsHandled property to true, when it is finished with handling the exception. If not, it will be rethrown and trigger the global unhandled exception handler.

Hh852707.note(en-us,PandP.51).gifEd Says:
Ed For some service agent calls, not actually handling the exception is the preferred way of working. If it is ok to display the generic "Something is wrong" friendly error message, then you can simply not handle it in the ViewModel. However, in the Stock Trader V2, there are many places where we explicitly want to show a custom message or custom user interface when a service call fails. This cannot be done using an exception handling policy, so it needs to be handled in the ViewModel itself.

Correlating the Start and End of an Asynchronous Operation

The asynchronous methods provided by all the service agents allow you to pass in a callback, wrapped inside an AsyncServiceRequest, that should be executed when the asynchronous action is complete. Here, it is important to correlate the start of the asynchronous action with the completion. The following code sample, from the account holder service agent, demonstrates how you can correlate the start and the end of the callback.

public void GetAccountHolder(
    AsyncServiceRequest<AsyncServiceRequest<AccountHolder>> asyncServiceRequest, 
         object userState)
{
    query = context.AccountHolderSet.Expand("BankAccounts");


    // Begin the asynchronous action that will load the Account Holder;
    // the AsyncServiceRequest is then passed along as the userstate, 
    // which can be restored when the async action completes. 
    query.BeginExecute(OnGetAccountHolderComplete, asyncServiceRequest);
}

The different technologies for executing web services, such as WCF, WCF Data Services and WCF RIA Services, all have different techniques for starting an asynchronous action and notifying that an asynchronous action has completed. However, all of these techniques allow you to pass an object as user state. This user state is then passed back when the service request completes.

This code sample shows how you can restore the Async Service Request when the service returns and executes the correct callback:

private void OnGetAccountHolderComplete(IAsyncResult ar)
{
    // The ar.AsyncState is an object. Cast it to an AsyncServiceRequest class
    var asyncServiceRequest = AsyncServiceRequest<ServiceResult<AccountHolder>>.
             RestoreFromUserState(ar.AsyncState);

    List<AccountHolder> resultList;
    try
    {
        // If a problem occurred while executing the query, then WCF Data Services           
        // will throw that exception when you call the 'EndExecute' method. This 
        // exception will be handled by the catch block.
        resultList = query.EndExecute(ar).ToList();
    }
    catch (Exception ex)
    {
        // Handle the exception using the exception manager, and use the 
        // callback to report the error to the UI.
        exceptionManager.HandleAsyncWcfServiceError(
            ex,  
            asyncServiceRequest,
            ExceptionHandlingPolicies.ServiceLayer 
            );
        return;
    }

    // Try to get the account holder. Create it if it does not exist. 
    var result = resultList.FirstOrDefault() ?? CreateNewAccountHolder();

    // Execute the callback to hand the result back to the UI. 
    asyncServiceRequest.ReturnResult(new ServiceResult<AccountHolder>(result)
         {
             // Pass the user state that was passed with the GetAccountHolder 
             // method back to the calling method. 
             UserState = asyncServiceRequest.UserState
         });
}

The IAsyncResult that's passed by WCF Data Services, also contains the user state that was used when starting the request as an object. This user state will contain the Async Service Request instance that was created by the GetAccountHolder method.

The Async Service Request contains both the callback method that should be used to update the UI, and the user state that was passed to the GetAccountHolder method.

Handling Exceptions in the Asynchronous Service Agent

When an exception occurs in a synchronous method that you wish to handle, you can easily add a try-catch block and call the HandleException method of the ExceptionManager class. With the asynchronous actions in the service agents, this is not as simple. You will always need to notify the UI that the asynchronous action has completed, but the details depend on the actual settings in your exception handling policy.

Stock Trader V2 uses the following helper method to handle asynchronous WCF Service exceptions:

public static void HandleAsyncWcfServiceError(
    this ExceptionManager exceptionManager,
    Exception exception,
    IAsyncServiceRequest asyncServiceRequest,
    string policy)
{
    // Handle the exception using the supplied exception message.
    Exception errorToThrow;
    var rethrow = exceptionManager.HandleException(exception, 
        policy, out errorToThrow);

    if (rethrow)
    {
        // The exception is not swallowed. That means the UI needs to be 
        // notified of the exception. 
        errorToThrow = errorToThrow ?? exception;

        if (errorToThrow is ValidationErrorException)
        {
            asyncServiceRequest.ReturnValidationErrors(
                errorToThrow as ValidationErrorException);
        }

        // Notify the UI of the problem. 
        var result = asyncServiceRequest.ReturnError(errorToThrow);

        if (!result.IsHandled)
        {
            // The UI didn't handle the exception. That means we need 
            // to rethrow the exception
            // this will trigger the global exception handling logic in 
            // App.xaml.cs
            throw errorToThrow;
        }
    }
    else
    {
        // The exception is swallowed by the exception handling block. 
        // However, you will still need to notify
        // the UI that the exception was handled.
        asyncServiceRequest.ReturnEmpty();
    }
}

The exception needs to be handled by the exception handling block. In our service agents, we have configured the exception handler to log the error and replace it with a more generic ServiceUnavailableException. However, this behavior can easily be changed by changing the configuration.

If the post handling action is set to None, you are telling the exception handling block to effectively swallow the exception. Since the service threw an exception, there is no result to pass back to the UI. However, the UI still needs to be notified that the asynchronous action has completed without a result.

Hh852707.note(en-us,PandP.51).gifEd Says:
Ed Except for the unhandled exception handler, it is somewhat unusual to swallow exceptions using None as the post-handling action. Not returning a result is usually not something developers expect from a web service call and if they forget to test the result value for nulls, the outcome could be other errors.

If you have set the post handling action either to NotifyRethrow or ThrowNewException, then you should notify the UI that an exception has occurred. The ServiceResult class also allows you to pass an exception back to the UI. In that case, the HasError property is set to true.

If the user interface handles the error, then no further action is needed. However, if the UI does not handle the exception, then the exception should be rethrown. Throwing the exception again will trigger the global Unhandled Exception event.

Displaying Error Messages

The Stock Trader V2 also uses a custom exception handler to display exception messages. The advantage is that you can display the exception handling instance ID to the end user. This instance ID is also logged, to help the support organization find the particular error if the user reports it.

The following code shows how this custom exception handler displays an error window.

public class DisplayErrorMessageExceptionHandler : IExceptionHandler
{
    private readonly ErrorMessageType errorMessageType;
    private IThreadingSupport threadingSupport = new ThreadingSupport();

    public DisplayErrorMessageExceptionHandler(ErrorMessageType errorMessageType)
    {
        this.errorMessageType = errorMessageType;
    }

    public Exception HandleException(Exception exception, Guid handlingInstanceId)
    {
        threadingSupport.ExecuteOnUIThread(() => ErrorWindow.CreateNew(exception, handlingInstanceId, errorMessageType == ErrorMessageType.TechnicalErrorMessage));
        return exception;
    }
}


public class DisplayErrorMessageExceptionHandlerData : ExceptionHandlerData
{
    /// <summary>
    /// Get the set of <see cref="TypeRegistration"/> objects needed to
    /// register the call handler represented by this config element and its associated objects.
    /// </summary>
    /// <param name="nameSuffix">A suffix for the names in the generated type registration objects.</param>
    /// <returns>The set of <see cref="TypeRegistration"/> objects.</returns>
    public override IEnumerable<TypeRegistration> GetRegistrations(string nameSuffix)
    {
        yield return
            new TypeRegistration<IExceptionHandler>(
                () =>
                    new DisplayErrorMessageExceptionHandler(ErrorMessageType))
            {
                Name = BuildName(nameSuffix),
                Lifetime = TypeRegistrationLifetime.Transient
            };
    }

    public ErrorMessageType ErrorMessageType { get; set; }
}

More Information

In this chapter, we discussed how you can use the Exception Handling Application Block in a Silverlight application.

To read more about exception handling using the Exception Handling Application Block, please visit the online documentation on MSDN® and the Developer's Guide.

Next Topic | Previous Topic | Home

Last built: July 8, 2011