Microsoft & .NETVisual C#Exception Handling in Web Services

Exception Handling in Web Services

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Exception Handling in Web Services

Web Services are a relatively new way to achieve distributed computing. In distributed computing, applications are designed as services that run on a server. Clients access these services through a programmable interface. When there is an exception during the execution of the Web service, the Web service should not only capture the exceptions, but also communicate the exception back to the consumers of the Web service. Because Web services provide a platform-independent of way of leveraging a specific functionality, the exceptions that occur in the Web Services must also be communicated in a platform-independent manner. To accomplish this, you need to make sure that the exceptions raised from the Web services are compliant with the SOAP specification. In this article, we will understand how to raise exceptions from a Web service by using the SoapException object that provides an industry-standard way of representing a SOAP Fault. We will also see how to handle this exception from the Web service consumer application.

Introduction

An exception is any error condition or unexpected behavior encountered by an executing program. Exceptions can occur due to a number of reasons such as fault in your code, operating system resources not being available, unexpected conditions in the common language runtime, and so on. While your application can recover from some of these conditions, most of the runtime exceptions are irrecoverable. In that case, you need an effective way of handling those exceptions and informing the callers of the exception of the same.

Using Structured Exceptions Handling to Handle Exceptions

The crux of the exception handling support in a .NET Web service is provided by the try…catch…finally statement. The try keyword precedes a block of normal processing code that may throw an exception. The catch keyword precedes a block of exception handling code. The finally keyword precedes a block of code that will always be executed after handling the exceptions. Once an exception is thrown from a try block, the program flow switches to the first catch block following it. A well-designed set of error handling code blocks can go a long way in making the program more robust and less prone to crashing because of the way the application handles such errors. The best practices for handling exceptions can be summarized as follows:

  • Always wrap potentially error-prone code with the try/finally blocks and centralize catch statements in one location. In this way, the try statement generates the exception, the finally statement closes or deallocates resources, and the catch statement handles the exception from a central location.
  • Always arrange exceptions in catch blocks from the most specific to the least specific. This technique handles the specific exception before it is passed to a more general catch block.
  • Always derive custom exception classes from the ApplicationException class.
  • Always suffix custom exception class names with the word “Exception.”

    For example: public class LogonException: ApplicationException {}

  • In most cases, use the predefined exceptions types. New exception types should be introduced only for programmatic scenarios.
  • Use exception builder methods. It is common for a class to throw the same exception from different places in its implementation. To avoid excessive code, use helper methods that create the exception and return it.

Now that we have had a look at the best practices for handling exceptions, let us look at how to raise exceptions from Web services.

Raising Exceptions from the Web Service

Handling exceptions in a Web service is no different than handling exceptions in a Web or Windows application. However, when designing exception blocks in Web services, you need to be aware of the fact that you need to communicate the exception information to the consumers of your Web service in a platform-independent manner based on the SOAP specification. To accomplish this, you should use the SoapException class that abstracts the complexities of the SOAP fault creation process. The SoapException class consists of the following properties that need to be populated before throwing the exception to the consumers.

  • Message—Contents of the exception
  • Code—Enum constant that specifies the type of Fault code (e.g. ClientFaultCode, ServerFaultCode)
  • Actor—URL of the Web service method where the exception has occurred
  • Detail—Detail element can be used to communicate more information about the exception to the callers

Implementation of Web Service

For the purposes of this example, let us create a Web service named CategoriesService, selecting Visual a C# ASP.NET Web Service as the project template. Once the project is created, we will add a method named AddCategories and add the following lines of code to it.

[WebMethod]
public bool AddCategories(string xml)
{
try
  {
    using(SqlConnection conn = new SqlConnection())
    {
      if (ValidateXml(xml))
      {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xml);
        conn.ConnectionString = 
          "server=localhost;uid=sa;pwd=thiru;database=northwind";
        conn.Open();
        XmlNamespaceManager nsManager = new
                            XmlNamespaceManager(doc.NameTable);
        // Add the namespace to the NamespaceManager
        nsManager.AddNamespace("catNS",
          "http://tempuri.org/CategoriesNamespace");
        XmlNode categoryNode = 
          doc.DocumentElement.SelectSingleNode("catNS:Category",
                                               nsManager);
        string categoryName = 
          categoryNode.SelectSingleNode("catNS:CategoryName",
                                        nsManager).InnerText;
        string categoryDescription = 
          categoryNode.SelectSingleNode("catNS:CategoryDescription",
                                        nsManager).InnerText;
        SqlCommand command = new
          SqlCommand("usp_InsertCategories",conn);
        command.CommandType = CommandType.StoredProcedure;

        //Add the Category Name parameter
        SqlParameter paramCategoryName = new
          SqlParameter("@CategoryName", SqlDbType.NVarChar,15);
        paramCategoryName.Direction = ParameterDirection.Input;
        paramCategoryName.Value = categoryName;
        command.Parameters.Add(paramCategoryName);
        //Add the Description parameter
        SqlParameter paramDescription = new 
          SqlParameter("@Description", SqlDbType.Text);
        paramDescription.Direction = ParameterDirection.Input;
        paramDescription.Value = categoryDescription;
        command.Parameters.Add(paramDescription);
        command.ExecuteNonQuery();
      }
      else
        throw
          RaiseException("AddCategories",
          "http://tempuri.org/CategoriesService",
          builder.ToString(),
          "2000","AddCategories",FaultCode.Client);
    }
    return true;
  }
  catch (SoapException soapEx)
  {
    throw soapEx;
  }
  catch(Exception ex)
  {
    EventLog.WriteEntry("Test",ex.Message);
    throw
      RaiseException("AddCategories",
      "http://tempuri.org/CategoriesService",ex.Message,
      "1000",ex.Source,FaultCode.Server);
  }
}

As the name suggests, the AddCategories method is responsible for adding the details of the category to the categories table in the Northwind database. Before performing that, it validates the supplied XML data by using an external XML schema file and, if the validation fails, it throws an exception to the consumer of the Web service.

Let us walk through the above lines of code. We start by invoking the ValidateXml method, passing in the XML data as an argument to it. We will look at the code of the ValidateXml method in a moment. The ValidateXml method returns true or false depending on whether the XML validation succeeds or not. If it returns true, we then create an instance of the XmlDocument object and load the XML into it. Then, we also initialize the SqlConnection object by first setting the ConnectionString property and then invoking the Open method of the SqlConnection object. After that, we create an instance of the XmlNamespaceManager and associate a namespace to it by using the AddNamespace method. Once we associate the namespace, we then can use the namespace identifier to reference the appropriate XML element. After that, we create instances of the SqlParameter objects to add parameters to the stored procedure. Finally, we execute the stored procedure by using the ExecuteNonQuery method of the SqlCommand object.

If the ValidateXml method returns false, we throw SoapException by using a helper method named RaiseException, which we will discuss in a moment. The RaiseException method is basically a helper method that encapsulates the code required for raising SoapException from the Web service. The last parameter of the RaiseException method is an enum constant that is defined as follows.

public enum FaultCode
{
    Client = 0,
    Server = 1
  }

Failure of the XML validation indicates that the client has supplied an invalid XML data. In this case, we should indicate this to the client application by setting the enum constant to Client. This makes it possible for us to indicate that the client application needs to check the format of the input data before invoking the Web service method again. If the Web service fails due to some other reason (for example, the non-availability of the database server), you need to set the enum constant to Server. This allows us to indicate that the Web service failed due to some problem in the server side and the client application can retry the request after a few seconds. In fact, in the catch block that catches the generic Exception, this is exactly we do.

Now that we have looked at the AddCategories method, let us look at the helper methods used inside the AddCategories method. Let us start by looking at the ValidateXml method. As mentioned before, this method is responsible for ensuring that the supplied Categories XML data is compliant with the pre-defined XML schema that is defined in the Categories.xsd file.

private bool ValidateXml(string xml)
{
  bool validXml = false;
  //Load the XML data into memory
  XmlValidatingReader valReader = new 
    XmlValidatingReader(xml,XmlNodeType.Document,null);
  valReader.Schemas.Add(null, Server.MapPath("Categories.xsd"));
  valReader.ValidationType = ValidationType.Schema;
  valReader.ValidationEventHandler += new 
    ValidationEventHandler(ValidationHandler);
  //Loop through the XML file 
  while(valReader.Read())
  {}
  if (builder.Length > 0)
    validXml = false;
  else
    validXml = true;
  valReader.Close();
  return validXml;
}

In the above code, we first create an instance of the XmlValidatingReader class by passing in the supplied XML data as one of the arguments to its constructor. Then, we add the Categories.xsd file to the Schemas collection of the XmlValidatingReader object. Then, we set the ValidationType to ValidationType.Schema to indicate that we are validating the XML data against the XML schema. When you are validating XML data using the XmlValidatingReader class, you need to create an event handler and associate that with the ValidationEventHandler event. Once this is done, the validation errors and warnings are reported through this callback event handler. The ValidationEventHandler takes the ValidationEventArgs class as one of its arguments. This class exposes two important properties, named Message and Severity, that provide more information about the validation errors.

In this case, we associate the ValidationEventHandler event to a method named ValidationHandler method. In this method, we append the error messages to a StringBuilder object that is defined at the module level. If there are no validation errors, the Length property of the StringBuilder object will return 0. We use this to check whether the XML schema validation failed. The ValidationHandler method is defined as follows.

public void ValidationHandler(object sender,
                              ValidationEventArgs args)
{
  builder.Append("Validation error" + "<br>");
  builder.Append("Severity:" +  args.Severity + "<br>");
  builder.Append("Message:" + args.Message + "<br>");
}

Let us look at the code for the RaiseException method.

public SoapException RaiseException(string uri,
                                    string webServiceNamespace,
                                    string errorMessage,
                                    string errorNumber,
                                    string errorSource,
                                    FaultCode code)
{
  XmlQualifiedName faultCodeLocation = null;
  //Identify the location of the FaultCode
  switch (code)
  {
    case FaultCode.Client:
      faultCodeLocation = SoapException.ClientFaultCode;
      break;
    case FaultCode.Server:
      faultCodeLocation = SoapException.ServerFaultCode;
      break;
  }
  XmlDocument xmlDoc = new XmlDocument();
  //Create the Detail node
  XmlNode rootNode = xmlDoc.CreateNode(XmlNodeType.Element,
                     SoapException.DetailElementName.Name,
                     SoapException.DetailElementName.Namespace);
  //Build specific details for the SoapException
  //Add first child of detail XML element.
  XmlNode errorNode = xmlDoc.CreateNode(XmlNodeType.Element,"Error",
                                        webServiceNamespace);
  //Create and set the value for the ErrorNumber node
  XmlNode errorNumberNode = 
    xmlDoc.CreateNode(XmlNodeType.Element,"ErrorNumber",
                      webServiceNamespace );
  errorNumberNode.InnerText = errorNumber;
  //Create and set the value for the ErrorMessage node
  XmlNode errorMessageNode =xmlDoc.CreateNode(XmlNodeType.Element,
                                              "ErrorMessage",
                                              webServiceNamespace);
  errorMessageNode.InnerText = errorMessage;
  //Create and set the value for the ErrorSource node
  XmlNode errorSourceNode = 
    xmlDoc.CreateNode(XmlNodeType.Element,"ErrorSource",
                      webServiceNamespace);
  errorSourceNode.InnerText = errorSource;
  //Append the Error child element nodes to the root detail node.
  errorNode.AppendChild(errorNumberNode);
  errorNode.AppendChild(errorMessageNode);
  errorNode.AppendChild(errorSourceNode);
  //Append the Detail node to the root node
  rootNode.AppendChild(errorNode);
  //Construct the exception
  SoapException soapEx = new SoapException(errorMessage,
                                           faultCodeLocation, uri,
                                           rootNode);
  //Raise the exception  back to the caller
  return soapEx;
}

As the name suggests, the RaiseException method is used to raise exceptions from the Web service in the form of a SoapException object. The code shown above starts off by inspecting the value contained in the FaultCode enum parameter that is used to indicate the source of exception. If an exception occurs due to problems in the server side (for example, the database server is down), you would then set the value of FaultCode to SoapException.ServerFaultCode. After that, it creates an XmlDocument object to hold the contents of the detail element. It adds all the child elements under the detail element and then passes the detail node to the constructor of the SoapException object. Finally it returns the SoapException object back to the caller by using the return statement. If you inspect the detail element inside the SoapException object, it should look somewhat similar to the following.

<detail>
        <Error >
          <ErrorNumber>1000</ErrorNumber>
          <ErrorMessage>Exception Information</ErrorMessage>
          <ErrorSource>Exception Source</ErrorSource>
        </Error>
</detail>

When the client application receives a SoapException from the Web service, it can inspect the Detail property of the SoapException object to get more information about the generated exception.

Advantages of Using SoapException

There are a number of advantages to using the SoapException class to convey the exception information back to the consumers of the Web service. They are:

  • Allows us to handle the exceptional conditions in a consistent fashion
  • Based on the SOAP specification
  • By explicitly raising the SoapException, it is possible to communicate more information, such as the reason for the exception, URL of the Web service method, and so on (using properties such as Actor, Code, and Detail)
  • Use of FaultCode to clearly convey the cause for the exception—Due to client or server
  • Use of the Detail property to give a detailed description of the exception information

Handling Exceptions on the Client Side

In this section, we will see how to handle the exceptions raised from the Web service on the client side. To demonstrate this, let us create a new project named CategoriesServiceClient. Once the project is created, add a command button to the default form and name it btnInvoke. Because you need to reference the Web service from the client side, add a Web Reference to the CategoriesService project. You can do this by using the Project->Add Reference menu option. Then, modify the Click event of the command button to look like the following.

private void btnInvoke_Click(object sender, System.EventArgs e)
{
  try
  {
    Categories cat = new Categories();
MessageBox.Show(cat.AddCategories("<?xml version='1.0'?>
       <Categories >
       <Category><CategoryName>Test Category</CategoryName>
       <CategoryDescription>Test Category Description
       </CategoryDescription></Category></Categories>").ToString());
  }
  catch(SoapException soapEx)
  {
    MessageBox.Show(soapEx.Code.ToString());
    //Load the Detail element of the SoaopException object
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(soapEx.Detail.OuterXml);
    XmlNamespaceManager nsManager = new
      XmlNamespaceManager(doc.NameTable);
    // Add the namespace to the NamespaceManager
    nsManager.AddNamespace("errorNS",
      "http://tempuri.org/CategoriesService");
    XmlNode categoryNode =
      doc.DocumentElement.SelectSingleNode("errorNS:Error",
                                           nsManager);
    string errorNumber = 
      categoryNode.SelectSingleNode("errorNS:ErrorNumber",
                                    nsManager).InnerText;
string errorMessage = 
  categoryNode.SelectSingleNode("errorNS:ErrorMessage",
                                nsManager).InnerText;
string errorSource = 
  categoryNode.SelectSingleNode("errorNS:ErrorSource",
                                nsManager).InnerText;
    MessageBox.Show("Error Number is" +  errorNumber);
    MessageBox.Show("Error Message is" + errorMessage);
    MessageBox.Show("Error Source is" + errorSource);
  }
  catch(Exception ex)
  {
    MessageBox.Show(ex.Message);
  }
}

The exceptions raised by the Web service need to be handled on the client side. Because the Web service raises all the exceptions in the form of SoapException, the client application code that invokes the Web service should be wrapped in a try…catch block and the first catch block should have the handler for catching the SoapException as shown in the above code. Let us walk through the above lines of code.

We start by creating an instance of the Categories class. After that, we invoke the AddCategories method of the Categories class by passing in the required XML string as an argument. Then, we have a catch block to handle the SoapException raised from the Web service. In this block, we display the source of the exception in a message box. We do this by using the Code property of the SoapException object. The Code property will be set to Client if the exception is caused by an invalid input from the client. If the exception is caused by the Web service code (for example, the database server is down), the Code property will be set to Server.

Then we load the XML data contained in the Detail element of the SoapException object to an XmlDocument object. Similarly to the Web service code, here also we utilize the XmlNamespaceManager object to associate the namespace with the XmlDocument object. Then, we retrieve the values contained in the individual elements and assign them to local variables. Finally, we display the values contained in the local variables using message boxes.

You can download the client code for handling the exceptions along with the support material for this article.

Putting It All Together

Now that we have completed the client application, let us exercise it by running it. If you run the client application, it will display a message box (with the value true), indicating that the categories details have been successfully saved to the database. Now, remove the <CategoryName> element from the input XML data and run the application. You will get a message indicating that the cause of the exception is the Client application and you will also get more information about the exception in the Detail element of the SoapException object.

As mentioned before, if the Web service fails due to some problems on the server side, the Code property of the SoapException object should be set to Server. To test this, modify the connection string in the Web service code to an invalid value. Now, if you run your client application, you will get a message indicating that the cause of the exception is the Server (Web service in this case).

Conclusion

In this article, we have seen how to handle and communicate exceptions back to the consumers of the Web service by using the SoapException object. We have also seen how the SoapException object allows us to communicate the exceptions using the SOAP fault code defined in the SOAP specification. Along the way, we have also discussed the steps involved in handling the raised exception from the client side. Although the application we created was simple in functionality, it should provide a solid foundation for understanding how to raise and handle exceptions from a Web service.

Source Code

The following is the source code for this two part article: ExceptionHandling.zip – 23 kb.

About the Author

Thiru Thangarathinam has many years of experience in architecting, designing, developing and implementing applications using Object Oriented Application development methodologies. He also possesses a thorough understanding of software life cycle (design, development and testing). He is an expert with ASP.NET, .NET Framework, Visual C#.NET, Visual Basic.NET, ADO.NET, XML Web Services and .NET Remoting and holds MCAD for .NET, MCSD and MCP certifications. Thiru has authored numerous books and articles. He can be reached at thiruthangarathinam@yahoo.com.

# # #

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories