http://www.developer.com/net/csharp/article.php/3088231/Exception-Handling-in-Web-Services.htm
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. 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. 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: 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. 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. 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. 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. 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. 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. Let us look at the code for the RaiseException method. 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. 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. 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: 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. 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. 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). 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. The following is the source code for this two part article: ExceptionHandling.zip - 23 kb. 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. # # #
Exception Handling in Web Services
October 7, 2003
Exception Handling in Web Services
Introduction
Using Structured Exceptions Handling to Handle Exceptions
Raising Exceptions from the Web Service
Implementation of Web Service
[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);
}
}
public enum FaultCode
{
Client = 0,
Server = 1
}
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;
}
public void ValidationHandler(object sender,
ValidationEventArgs args)
{
builder.Append("Validation error" + "<br>");
builder.Append("Severity:" + args.Severity + "<br>");
builder.Append("Message:" + args.Message + "<br>");
}
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;
}
<detail>
<Error xmlns=" http://tempuri.org/CategoriesService /">
<ErrorNumber>1000</ErrorNumber>
<ErrorMessage>Exception Information</ErrorMessage>
<ErrorSource>Exception Source</ErrorSource>
</Error>
</detail>
Advantages of Using SoapException
Handling Exceptions on the Client Side
private void btnInvoke_Click(object sender, System.EventArgs e)
{
try
{
Categories cat = new Categories();
MessageBox.Show(cat.AddCategories("<?xml version='1.0'?>
<Categories xmlns='http://tempuri.org/CategoriesNamespace'>
<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);
}
}
Putting It All Together
Conclusion
Source Code
About the Author