April 16, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Exception Handling in Web Services

  • October 7, 2003
  • By Thiru Thangarathinam
  • Send Email »
  • More Articles »

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 xmlns=" http://tempuri.org/CategoriesService /">
          <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.





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel