.NET Web Services - Exception Handling And Non-Exception Error Handling

Today's article shows one approach to bubbling up exceptions and error messages from your business layer to your windows form UI as well as web methods when called via Web Services. It is a great beginner sample to utilizing web services and custom classes in .NET and can also be useful in ASP.NET / AJAX / Anthem .NET applications. Learn how to change the web service url at runtime in your windows forms application and how to copy class properties to and from web service references.

When you think of how you are going to handle exceptions via web services, one typically thinks of how to handle unexpected problems such as database connection failures or network failures.  Of course, you'll also need to notify the caller of failed business rules.  You often times see this in the form of return codes which can be problematic if not just plain insufficient.

For example, if you want to have a web method that saves a record to the database, how do you communicate which properties are required fields and were not populated?

You also have to consider how to communicate those same types of failures if your business layer is called directly from another application (ie windows forms, .net compact framework, windows service, etc...).  You certainly don't want to write a bunch of duplicated error logic for different environments.

It would be nice to enable the business layer to bubble or throw back errors that may be true exceptions as well as notifications of steps that took place that may or may not need to be reviewed by the end user without necessarily stopping the whole process by throwing exceptions.  The following diagram shows how each business layer class (ie Users) exposes an Errors collection.  This collection can contain all sorts of messages that need to be communicated back to a wide variety of user interface environments either directly or easily transferred for transmittal over web services.

The sample code to this article contains an application in the form of a web service, a windows application, and an asp.net application.  Both the web service and the two client applications (windows and asp.net) share the same underlying business logic and data objects.

The idea being that the client apps might work against a local database and then need to deploy that same data to one or more web sites via web services when direct access to the remote databases isn't permitted.

There is a WebService.sln, ASPNETSample.sln, and a WindowsSample.sln in the root folder.  Make sure you have the web service solution running prior to attempting to run the windows sample.  The WindowsSample.Form1 has a few class level properties that you'll want to review.  The ASP.NET sample is almost identical.

A couple of side notes...

.NET Web Services utilizes custom attributes for xml serialization.  Since I have both a business layer class named Users that inherits a DataObjects class named Users, I had to create unique xml attribute properties so that they could be distinguished from one another.

A business class of Users is not considered to be the exact same thing as a WebService version of Users.  So,  when you return a business layer class of Users as a return value of a WebMethod, the windows forms application has to copy its properties.  The opposite is true when sending a WebService Users class up as an input parameter to a WebMethod.  To deal with this, I like to use an ObjectConverter class to manage all of the class types that can possibly be sent to and from a web service.  You'll see this class in the WindowsSample and ASPNETSample application.
 
  Sample Code

The following is just a small portion of the sample code:

using System;
using System.Web;
using System.Data;
using System.Diagnostics; 
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using EggHeadCafe.Business;
using EggHeadCafe.ErrorManagement;
using EggHeadCafe.WebServices;

namespace EggHeadCafe.WebService
{

    /// <summary>
    /// Summary description for WebService
    /// </summary>
    [WebService(Namespace = "http://www.webservice.com/", Description = "After calling a given web method, if you don't receive the desired return value(s), check the object's errors collection for details.  This errors collection will contain both validation/access rights oriented messages as well as system failure messages deemed safe for end user consumption.  All \"get\" oriented web methods will return null if no records are found to match the search criteria.  All \"delete\" or \"save\" oriented web methods return a boolean value.  If \"false\", you'll want to check the errors collection for possible validation errors.", Name = "WebService")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class WebService : System.Web.Services.WebService
    {

        public WebService() { }
      
        private string _nameSpace = "http://www.webservice.com/";
        private string _connectionString = "some connection string goes here if this were real";
   
        #region Get Web Service Version
        [WebMethod(Description = "Returns the current version number for the web service.  Use this to ensure other applications have the proper references.")]
        public string GetWebServiceVersion()
        {
            return "1.0.0";
        }
        #endregion

        #region Get User
        [WebMethod(Description = "Returns user details.  Will throw an error if less than 1.")]
        public Users GetUser(string authenticationUserName,
                             string authenticationPassword,
                             Int64 userID)
        {
 
            Users handle = new Users();
            
            try
            {
                // Implement your own authentication scheme to see if the user
               // running the web service has proper permission to access this data.
                
                return handle.GetUser(_connectionString,userID);
            }
            catch (Exception)
            {
                throw SoapErrorCreator.RaiseException(_nameSpace,
                                                      handle.Errors,
                                                      true);
            }
        }
        #endregion
  
        #region Save User
        [WebMethod(Description = "Save this record.")]
        public bool SaveUser(string authenticationUserName,
                             string authenticationPassword, 
                             Users record)
        {

            Users handle = new Users();

            try
            {

                // Implement your own authentication scheme to see if the user
                // running the web service has proper permission to modify this data.
                 
                if (record == null) { throw new Exception(); }
               
                if (!handle.Save(_connectionString,
                                        record))
                {
                    throw new Exception();
                }

            }
            catch (Exception)
            {
                throw SoapErrorCreator.RaiseException(_nameSpace,
                                                      handle.Errors,
                                                      true);
            }
            return true;
        }
        #endregion
       

    }

}










 
using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using EggHeadCafe.Business;
using EggHeadCafe.WebServices;
using EggHeadCafe.ErrorManagement;
using EggHeadCafe.Shared;

public partial class _Default : System.Web.UI.Page 
{
 
    private string _connectionString = "test connection string.";
    private string _webServiceUrl = "http://localhost:2043/default.asmx";
    private Int64 _userID = 0;
    private string _authenticationUserName = "joeblow";
    private string _authenticationPassword = "blowjoe"; 
    
    protected void Page_Load(object sender, EventArgs e)
    {
            this.lblErrorMessage.Text = "";
    }

    #region Set Record From Form
    private void SetRecordFromForm(Users record)
    {
        if (record == null) { return; }
        record.UserID = this._userID;
        record.Email = this.txtEmail.Text;
        record.UserName = this.txtUserName.Text;
        record.Password = this.txtPassword.Text;
        record.FirstName = this.txtFirstName.Text;
        record.LastName = this.txtLastName.Text;
    }
    #endregion

    #region Clear Form
    private void ClearForm()
    {
        this._userID = 0; 
        this.txtEmail.Text = "";
        this.txtUserName.Text = "";
        this.txtPassword.Text = "";
        this.txtFirstName.Text = "";
        this.txtLastName.Text = "";
    }
    #endregion 
   
    #region Set Form
    private void SetForm(Users record)
    {
        if (record == null) { return; }
        this._userID = record.UserID;
        this.txtEmail.Text = record.Email;
        this.txtUserName.Text = record.UserName;
        this.txtPassword.Text = record.Password;
        this.txtFirstName.Text = record.FirstName;
        this.txtLastName.Text = record.LastName;
    }
    #endregion 

    #region Get User From Local DataBase 1
    protected void GetUserFromLocalDataBase1_Click(object sender, EventArgs e)
    {
        Users handle = new Users();
        Users record = null;

        try
        {
            ClearForm();
            record = handle.GetUser(_connectionString, 1);
            SetForm(record);
        }
        catch (Exception ex)
        {
            if (handle.Errors.Count > 0)
            {
                ShowError(handle.Errors);
            }
            else
            {
                ShowError(ex);
            }
        }
    } 
   #endregion

    #region Get User From Local DataBase 2
    protected void GetUserFromLocalDataBase2_Click(object sender, EventArgs e)
    {
        Users handle = new Users();
        Users record = null;

        try
        {
            ClearForm();
            record = handle.GetUser(_connectionString, 2);
            SetForm(record);
        }
        catch (Exception ex)
        {
            if (handle.Errors.Count > 0)
            {
                ShowError(handle.Errors);
            }
            else
            {
                ShowError(ex);
            }
        }
    }
    #endregion

    #region Get User From Local DataBase Error
    protected void GetUserFromLocalDataBaseError_Click(object sender, EventArgs e)
    {
        Users handle = new Users();
        Users record = null;

        try
        {
            ClearForm();
            record = handle.GetUser(_connectionString, 0);
            SetForm(record);
        }
        catch (Exception ex)
        {
            if (handle.Errors.Count > 0)
            {
                ShowError(handle.Errors);
            }
            else
            {
                ShowError(ex);
            }
        }
    }
    #endregion 
   
    #region Save Local DataBase Error
    protected void SaveLocalDataBaseError_Click(object sender, EventArgs e)
    {
        Users record = new Users();

        try
        {

            this._userID = 1;
            this.txtFirstName.Text = "";  // Purposely force an error for testing.
            
            SetRecordFromForm(record);

            record.FirstName = "";

            if (!record.Save(_connectionString))
            {
                ShowError(record.Errors);
            }

        }
        catch (Exception ex)
        {
            if (record.Errors.Count > 0)
            {
                ShowError(record.Errors);
            }
            else
            {
                ShowError(ex);
            }
        }
    }
    #endregion  
   
    #region Get User From Web Service 1
    protected void GetUserFromWebService1_Click(object sender, EventArgs e)
    {
        EggHeadCafeWebService.Users1 wsRecord = null;
        Users localRecord = null;

        try
        {
            ClearForm();

            // To dynamically change the web service url at runtime,
            // call the .SetUrl method I've created on the WSController:

            WSController.SetUrl(_webServiceUrl);

            wsRecord = WSController.Handle.GetUser(_authenticationUserName,
                                                   _authenticationPassword,
                                                   1);

            if (wsRecord != null)
            {
                localRecord = new Users();
                ObjectConverter.CopyProperties(wsRecord, localRecord, true);
                SetForm(localRecord);
            }

        }
        catch (SoapException soapException)
        {
            ShowError(SoapErrorHandler.HandleError(soapException));
        }
        catch (Exception ex)
        {
            ShowError(ex);
        }
    }
    #endregion

    #region Get User From Web Service 2
    protected void GetUserFromWebService2_Click(object sender, EventArgs e)
    {
        EggHeadCafeWebService.Users1 wsRecord = null;
        Users localRecord = null;

        try
        {
            ClearForm();

            // To dynamically change the web service url at runtime,
            // call the .SetUrl method I've created on the WSController:

            WSController.SetUrl(_webServiceUrl);

            wsRecord = WSController.Handle.GetUser(_authenticationUserName,
                                                   _authenticationPassword,
                                                   2);

            if (wsRecord != null)
            {
                localRecord = new Users();
                ObjectConverter.CopyProperties(wsRecord, localRecord, true);
                SetForm(localRecord);
            }

        }
        catch (SoapException soapException)
        {
            ShowError(SoapErrorHandler.HandleError(soapException));
        }
        catch (Exception ex)
        {
            ShowError(ex);
        }
    }
    #endregion

    #region Get User From Web Service Error
    protected void GetUserFromWebServiceError_Click(object sender, EventArgs e)
    {
        EggHeadCafeWebService.Users1 wsRecord = null;
        Users localRecord = null;

        try
        {
            ClearForm();

            // To dynamically change the web service url at runtime,
            // call the .SetUrl method I've created on the WSController:

            WSController.SetUrl(_webServiceUrl);

            wsRecord = WSController.Handle.GetUser(_authenticationUserName,
                                                   _authenticationPassword,
                                                   0);

            if (wsRecord != null)
            {
                localRecord = new Users();
                ObjectConverter.CopyProperties(wsRecord, localRecord, true);
                SetForm(localRecord);
            }

        }
        catch (SoapException soapException)
        {
            ShowError(SoapErrorHandler.HandleError(soapException));
        }
        catch (Exception ex)
        {
            ShowError(ex);
        }
    }
    #endregion

    #region Save Web Service Error 
    protected void SaveWebServiceError_Click(object sender, EventArgs e)
    {
        EggHeadCafeWebService.Users1 wsRecord = new EggHeadCafeWebService.Users1();
        Users localRecord = new Users();

        try
        {

            this._userID = 1;
            this.txtFirstName.Text = "";  // Purposely force an error for testing.
            
            SetRecordFromForm(localRecord);
            
            ObjectConverter.CopyProperties(wsRecord, localRecord, false);

            if (WSController.Handle.SaveUser(_authenticationUserName,
                                             _authenticationPassword,
                                             wsRecord))
            {
                this.lblErrorMessage.Text = "save successful";
            }

        }
        catch (SoapException soapException)
        {
            ShowError(SoapErrorHandler.HandleError(soapException));
        }
        catch (Exception ex)
        {
            ShowError(ex);
        }
    }
    #endregion   
   
   
    #region Show Error
    /// <summary>
    /// Display the error thrown back up the stack to the 
    /// user interface.  
    /// </summary>
    private void ShowError(Exception ex)
    {
        this.lblErrorMessage.Text = ex.Message;
    }
    #endregion

    #region Show Error
    /// <summary>
    /// Display the error thrown back up the stack to the 
    /// user interface.
    /// </summary>
    private void ShowError(List<Error> errors)
    {
        string msg = "";

        if (errors == null) { return; }

        for (int i = 0; i < errors.Count; i++)
        {
            msg += errors[i].Message + "<br>";
        }

        if (msg.Length > 0)
        {
            this.lblErrorMessage.Text = msg;
        }
    }
    #endregion 
    
}






using System;
using System.Collections.Generic;
using System.Text;

 
    public class WSController
    {

        public static EggHeadCafeWebService.WebService 
Handle = new EggHeadCafeWebService.WebService(); public static string WebServiceVersion = ""; #region Set Url public static void SetUrl(string url) { // reset the web service url and rebind the references. WSController.Handle.Url = url; WSController.Handle.Discover(); } #endregion }
By Robbe Morris   Popularity  (19122 Views)
Picture
Biography - Robbe Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.  Robbe also loves to scuba dive and go deep sea fishing in the Florida Keys or off the coast of Daytona Beach. Microsoft MVP