当前位置:操作系统 > 玩转网络 >>

Web服务中的异常处理

答案:

    Web服务是一种完成分布式计算的相关新方法。在分布式计算中,应用程序被设计为服务运行在服务器上。客户端通过一个可编程的接口访问这些服务。当在Web服务的执行期间发生了异常,Web服务应该不只是捕捉到异常,还应该把异常传回给Web服务的客户端。因为Web服务提供一种平台无关的方法来支持特定功能,所以Web服务中发生的异常也必须以一种平台无关的方式被传回。为了达到这个目的,必须确保Web服务所产生的异常兼容SOAP规范。本文中,我们将了解怎样使用SoapException对象从Web服务中抛出异常,而SoapException对象提供了一种表示SOAP Fault的工业标准方法。我们还将了解怎样在Web服务的客户端应用程序中处理这个异常。

简介

   异常是正在执行的程序所遇到的任何错误情形或者意外行为。很多原因都可以引起异常,例如,代码中错误、操作系统资源不可用、公共语言运行时(common language runtime)中的意外情况等等。然而应用程序能够从上述的一些情况中恢复执行,但是大多数运行时异常是不可恢复的。在这种情况下,需要一种有效的方法来处理这些异常并给调用者提供相同的异常。

用结构化的异常处理方法来处理异常

   在.NET Web服务中,对异常处理支持的关键点是由try...catch..finally语句提供的。关键字try放在可能抛出异常的普通处理代码块之前。关键字catch放在异常处理代码块之前。关键字finally放在那些经常在异常处理后还需要执行的代码块之前。一旦异常从try代码块中抛出,程序流切换到后面的第一个catch代码块。一套设计良好的错误处理代码块可以成功的使程序变得更加健壮,使程序崩溃的机率变得更小,那是因为应用程序处理像这样的错误所采取的方法的缘故。处理异常的最好实践可以总结如下:

   经常用try/finally块包围潜在的可能发生错误的代码,并且把catch语句集中在一个地方。用这种方法,try语句抛出异常,finally语句关闭或释放资源,而catch语句则集中处理异常。
   通常,catch语句块从非常特殊到一般来排列异常。这个技巧使得异常被传递到一般catch块之前先处理特殊异常。

   大多数情况下,使用已有的异常类型。新的异常类型应该仅在有计划的情形下引进。

   使用异常的构建方法。一个类的实现中在不同地方抛出同一个异常,这是非常普遍的现象。为了避免出现过量的这类代码,使用助手方法创建异常和返回异常。

   至此,我们已经大致了解处理异常的最好实践,下面就让我们看看怎样从Web服务中抛出异常。

从Web服务中抛出异常

   在Web服务中处理异常与在Web或Windows应用程序中处理异常没有什么区别。但是,在设计Web服务中的异常块时,必须明白一个事实,即你需要以一种平台无关的基于SOAP规范的方式把异常信息传递给Web服务的客户端。为了达到这个目的,你应该使用SoapException类,它抽象了SOAP Fault创建过程的复杂度。SoapException类具有下列属性,并且这些属性必须在把异常抛出给客户端之前被设定。

   Message--异常的内容

   Code--指定Fault编码类型(例如,ClientFaultCode和ServerFaultCode)的枚举常量

   Actor--发生异常的Web服务方法的URL

   Detail--Detail元素可以用来把更多的异常信息传递给调用者。
Web服务的实现

   为了达到这个示例的目的,我们创建一个名为CategoriesService的Web服务,选择一个可视化的C#ASP.NETWeb服务作为项目的模版。一旦创建项目,我们就添加一个名为AddCategories的方法,并且给这个方法添加下列代码:

   [WebMethod]

   publicboolAddCategories(stringxml)

   {

   try

   {

   using(SqlConnectionconn=newSqlConnection())

   {

   if(ValidateXml(xml))

   {

   XmlDocumentdoc=newXmlDocument();

   doc.LoadXml(xml);

   conn.ConnectionString=

   "server=localhost;uid=sa;pwd=thiru;database=northwind";

   conn.Open();

   XmlNamespaceManagernsManager=new

   XmlNamespaceManager(doc.NameTable);

   //AddthenamespacetotheNamespaceManager

   nsManager.AddNamespace("catNS",

   "http://tempuri.org/CategoriesNamespace");

   XmlNodecategoryNode=

   doc.DocumentElement.SelectSingleNode("catNS:Category",

   nsManager);

   stringcategoryName=

   categoryNode.SelectSingleNode("catNS:CategoryName",

   nsManager).InnerText;

   stringcategoryDescription=

   categoryNode.SelectSingleNode("catNS:CategoryDescription",

   nsManager).InnerText;

   SqlCommandcommand=new

   SqlCommand("usp_InsertCategories",conn);

   command.CommandType=CommandType.StoredProcedure;
   //AddtheCategoryNameparameter

   SqlParameterparamCategoryName=new

   SqlParameter("@CategoryName",SqlDbType.NVarChar,15);

   paramCategoryName.Direction=ParameterDirection.Input;

   paramCategoryName.Value=categoryName;

   command.Parameters.Add(paramCategoryName);

   //AddtheDescriptionparameter

   SqlParameterparamDescription=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);

   }

   returntrue;

   }

   catch(SoapExceptionsoapEx)

   {

   throwsoapEx;

   }

   catch(Exceptionex)

   {

   EventLog.WriteEntry("Test",ex.Message);

   throw

   RaiseException("AddCategories",

   "http://tempuri.org/CategoriesService",ex.Message,

     "1000",ex.Source,FaultCode.Server);

   }

   }
   正如其名所提示的那样,AddCategories方法负责把category的详细信息添加到Northwind数据库的categories表中。在执行添加操作之前,AddCategories方法使用一个外部的XML模式文件校验被添加的XML数据,如果校验失败,它给Web服务的客户端抛出一个异常。

让我们来大致浏览上面的代码吧。首先,把XML数据传递给它,调用ValidateXml方法。过一会我们再来看ValidateXml方法的代码。ValidateXml方法返回true或false,这完全取决于XML校验是否成功。如果返回true,那么就创建一个XmlDocument对象实例,并给它导入XML数据,另外还设置ConnectionString属性来初始化SqlConnection对象,然后调用SqlConnection对象的Open方法。其次,创建一个XmlNamespaceManager实例,调用AddNamespace方法关联一个命名空间。一旦关联命名空间,我们就可以使用命名空间标识符引用正确的XML元素。再次,创建一个SqlParameter对象实例,给存储过程添加参数。最后,调用SqlCommand对象的ExecuteNonQuery方法执行存储过程。

   如果ValidateXml方法返回false,则用名为RaiseException的助手方法抛出SoapException。我们现在就来讨论RaiseException。RaiseException方法一个基本的助手方法,它封装用来从Web服务中抛出异常的代码。RaiseException方法的最后一个参数是一个枚举常量,它的定义如下。

   publicenumFaultCode

   {

   Client=0,

     Server=1

   }

   XML校验失败表示客户端提供了无效的XML数据。这种情况,我们应该把枚举常量设为Client,给客户应用程序指出这种错误。这就使得我们通知客户端应用程序在再一次调用Web服务之前需要检查输入数据的格式成为可能。如果Web服务由于一些其他原因(例如,数据库服务器的不可用)而失败,那么就需要设置枚举常量为Server。这就说明Web服务失败是由于服务器端的一些问题造成的,客户应用程序可以在几秒钟后重新请求。事实上,在catch块中捕捉一般Exception,这正是我们要做的。

 既然我们已经了解了AddCategories方法,现在就让我们来看看AddCategories方法所用到的助手方法。首先,我们看看ValidateXml方法。像前面所提到的那样,该方法负责确保被添加的CategoriesXML数据遵从Categories.xsd中所预先定义好的XML模式。

   privateboolValidateXml(stringxml)

   {

   boolvalidXml=false;

   //LoadtheXMLdataintomemory

   XmlValidatingReadervalReader=new

   XmlValidatingReader(xml,XmlNodeType.Document,null);

   valReader.Schemas.Add(null,Server.MapPath("Categories.xsd"));

   valReader.ValidationType=ValidationType.Schema;

   valReader.ValidationEventHandler+=new

   ValidationEventHandler(ValidationHandler);

   //LoopthroughtheXMLfile

   while(valReader.Read())

   {}

   if(builder.Length>0)

   validXml=false;

   else

   validXml=true;

   valReader.Close();

   returnvalidXml;

   }
   以上代码首先把被添加的XML数据传递给构造函数,创建一个XmlValidatingReader类实例。然后,把Categories.xsd添加到XmlValidatingReader对象的Schemas集合中。接着,设置ValidationType为ValidationType.Schema,表明我们是在根据XML模式来验证XML数据。当你在使用XmlValidatingReader类验证XML数据时,你必须创建一个事件处理对象,并把它与ValidationEventHandler事件相关联。一旦做完这些后,校验错误和警告就通过这个回调事件处理对象被报告出来。ValidationEventHandler具有一个ValidationEventArgs类型的参数。ValidationEventArgs类提供两个重要属性,Message和Serverity。这两个属性提供更多的有关于校验错误的信息。

   在这种情况下,我们把ValidationEventHandler事件与ValidationHandler方法相关联。在这个方法中,我们附加错误信息到StringBuilder对象中,而该StringBuilder对象定义在模块中。如果没有任何校验错误,那么StringBuilder对象的Length属性将返回0。我们就是使用这个来检查XML模式校验是否失败。ValidationHandler方法定义如下。

   publicvoidValidationHandler(objectsender,

   ValidationEventArgsargs)

   {

   builder.Append("Validationerror"+"
");

   builder.Append("Severity:"+args.Severity+"
");

   builder.Append("Message:"+args.Message+"
");

   }

   下面,让我们来看看RaiseException方法的代码。

   publicSoapExceptionRaiseException(stringuri,

   stringwebServiceNamespace,

   stringerrorMessage,

   stringerrorNumber,

   stringerrorSource,

   FaultCodecode)

   {

   XmlQualifiedNamefaultCodeLocation=null;

   //IdentifythelocationoftheFaultCode

   switch(code)

   {

   caseFaultCode.Client:

   faultCodeLocation=SoapException.ClientFaultCode;

   break;

   caseFaultCode.Server:

   faultCodeLocation=SoapException.ServerFaultCode;

   break;

   }
   XmlDocumentxmlDoc=newXmlDocument();

   //CreatetheDetailnode

   XmlNoderootNode=xmlDoc.CreateNode(XmlNodeType.Element,

   SoapException.DetailElementName.Name,

   SoapException.DetailElementName.Namespace);

   //BuildspecificdetailsfortheSoapException

   //AddfirstchildofdetailXMLelement.

   XmlNodeerrorNode=xmlDoc.CreateNode(XmlNodeType.Element,"Error",

   webServiceNamespace);

   //CreateandsetthevaluefortheErrorNumbernode

   XmlNodeerrorNumberNode=

   xmlDoc.CreateNode(XmlNodeType.Element,"ErrorNumber",

   webServiceNamespace);

   errorNumberNode.InnerText=errorNumber;

   //CreateandsetthevaluefortheErrorMessagenode

   XmlNodeerrorMessageNode=xmlDoc.CreateNode(XmlNodeType.Element,

   "ErrorMessage",

   webServiceNamespace);

   errorMessageNode.InnerText=errorMessage;

   //CreateandsetthevaluefortheErrorSourcenode

   XmlNodeerrorSourceNode=

   xmlDoc.CreateNode(XmlNodeType.Element,"ErrorSource",

   webServiceNamespace);

   errorSourceNode.InnerText=errorSource;

   //AppendtheErrorchildelementnodestotherootdetailnode.

   errorNode.AppendChild(errorNumberNode);

   errorNode.AppendChild(errorMessageNode);

   errorNode.AppendChild(errorSourceNode);

   //AppendtheDetailnodetotherootnode

   rootNode.AppendChild(errorNode);

   //Constructtheexception

   SoapExceptionsoapEx=newSoapException(errorMessage,

   faultCodeLocation,uri,

   rootNode);

   //Raisetheexceptionbacktothecaller

   returnsoapEx;

   }
   正如其名所提示的那样,RaiseException方法用于以SoapException对象的形式抛出Web服务中的异常。上面所示代码首先检查包含在FaultCode中的枚举参数的值,而该枚举参数用来标识异常发生的源头。如果是因为服务器端的问题(例如,数据库服务器已经关闭)而发生异常,那么应该设置FaultCode的值为SoapException.ServerFaultCode。接着,RaiseException方法创建一个XmlDocument对象来保存detail元素的内容。该对象添加detail元素下的所有子元素,然后把detail节点传递给SoapException对象的构造函数。最后,方法用return语句把SoapException对象返回给调用者。如果你检查SoapException对象内部的detail元素,你会发现它与下面内容有点类似。

 

 

1000

ExceptionInformation

ExceptionSource

 

 

   当客户应用程序接收到Web服务的异常时,它可以查看SoapException对象的Detail属性以获取更多的有关于已产生的异常的信息。

   使用SoapException的优点:

   使用SoapException类把异常信息返回给Web服务的客户端,这种方法具有很多的优点。如下:

   能够以一致的方式来处理异常情况

   基于SOAP规范

   通过显示的产生异常,这可以传达更多的信息,例如,异常原因,Web服务方法的URL等等(使用诸如Actor、Code和Detail之类的属性)

   使用FaultCode,可以清楚地表示异常归因于客户端还是服务器端

   使用Detail属性,可以更详细地描述异常信息
客户端的异常处理

   这个部分,我们将看看在客户端怎样处理从Web服务中所抛出的异常。为了说明这个做法,我们来创建一个新项目CategoriesServiceClient。一旦项目被创建,就在默认的表单上添加一个命令按钮,并命名为btnInvoke。因为需要在客户端引用Web服务,所以在项目CategoriesService中添加一个WebReference。可以通过Project->AddReference菜单选项来完成添加。然后修改命令按钮的Click事件,如下所示。

   privatevoidbtnInvoke_Click(objectsender,System.EventArgse)

   {

   try

   {

   Categoriescat=newCategories();

   MessageBox.Show(cat.AddCategories("

 

TestCategory

TestCategoryDescription

").ToString());

   }

   catch(SoapExceptionsoapEx)

   {

   MessageBox.Show(soapEx.Code.ToString());

   //LoadtheDetailelementoftheSoaopExceptionobject

   XmlDocumentdoc=newXmlDocument();

   doc.LoadXml(soapEx.Detail.OuterXml);
   XmlNamespaceManagernsManager=new

   XmlNamespaceManager(doc.NameTable);

   //AddthenamespacetotheNamespaceManager

   nsManager.AddNamespace("errorNS",

   "http://tempuri.org/CategoriesService");

   XmlNodecategoryNode=

   doc.DocumentElement.SelectSingleNode("errorNS:Error",

   nsManager);

   stringerrorNumber=

   categoryNode.SelectSingleNode("errorNS:ErrorNumber",

   nsManager).InnerText;

   stringerrorMessage=
   categoryNode.SelectSingleNode("errorNS:ErrorMessage",

   nsManager).InnerText;

   stringerrorSource=

   categoryNode.SelectSingleNode("errorNS:ErrorSource",

   nsManager).InnerText;

   MessageBox.Show("ErrorNumberis"+errorNumber);

   MessageBox.Show("ErrorMessageis"+errorMessage);

   MessageBox.Show("ErrorSourceis"+errorSource);

   }
   catch(Exceptionex)

   {

   MessageBox.Show(ex.Message);

   }

   }

   客户端需要处理Web服务所产生的异常。因为Web服务所产生的异常都是SoapException形式的,所以调用Web服务的客户应用程序的代码应该被包含在try...catch块中,并且第一个catch块应该具有捕捉SoapException的处理器。让我们大致看看以上所示的代码。

   首先创建一个Categories类的实例。然后传入所要求的XML字符串参数,调用Categories类的AddCategories方法。再后,我们让catch块处理Web服务所产生的异常。在这个代码块中,我们以消息提示框的方式显示异常的产生者。我们通过使用SoapException对象的Code属性来完成这个显示。如果异常是因为客户端的非法输入而产生的,那么Code属性被设置为Client。如果异常是因为Web服务代码(例如,据库服务器已经关闭)而产生的,则Code属性被设为Server。

   然后,我们把包含在SoapException对象的Detail元素中的XML数据导入到一个XmlDocument对象中。类似于Web服务的代码,这里同样利用XmlNamespaceManager对象把命名空间与XmlDocument对象关联在一起。之后,取出包含在不同元素中的值,并把它们赋给本地变量。最后,使用消息提示框显示本地变量的值。
   把它放在一起

至此,我们已经完成了客户应用程序,现在让我们运行测试它。如果运行客户程序,它将显示一个消息框(具有true值),那么则表明categories详细信息已经成功保存到数据库中。现在,从输入的XML数据中删除 元素,运行客户程序。将会得到一个消息,表明异常的原因是客户程序,除此之外,还可以在SoapException对象中得到更多的异常信息。

   正如前面所提到的那样,如果Web服务失败是由于服务器端的一些问题引起的,那么SoapException对象的Code属性应该被设置为Server。为了测试这个,修改Web服务中的连接字符串为一个无效值。现在,如果运行客户程序,将会得到一个表明异常的原因是Server的消息(该例中的Web服务)。

   结论

   本文中,我们已经了解到怎样使用SoapException对象来处理和传递异常给Web服务的客户端。我们还了解到SoapException对象是怎样利用SOAPfault编码(定义在SOAP规范中)来传递异常的。顺便,我们还讨论了处理客户端所产生的异常的步骤。虽然我们创建的应用的功能非常的简单,但是它给我们理解怎样抛出和处理Web服务中的异常提供了一个坚实的基础。

上一个:E-mail存储备份解决方案
下一个:轻松建立Web服务器

CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,