WCF学习之旅—基于Fault Contract 的异常处理(十八)
WCF学习之旅—WCF中传统的异常处理(十六)
WCF学习之旅—基于ServiceDebug的异常处理(十七)
三、基于Fault Contract 的异常处理
第二个示例是通过定制ServiceDebug来获取服务端的异常,但是这种方式只能用于Debug阶段。在我们的WCF应用发布之后,这种获取异常的方式无法在我们的工作环境中使用。我们必须找到一种异常处理方式可以在客户端获取相应的异常提示信息。那就是我们接下来要介绍的基于FaultContract的解决方案。我们知道WCF采用一种基于 Contract,Contract定义了进行交互的双方进行消息交换所遵循的准则和规范。Service Contract定义了包含了所有Operation的Service的接口,Data Contract定义了交互的数据的结构,而FaultContract实际上定义需要再双方之间进行交互的了异常、错误的表示。现在我们来学习如何使用基于FaultContract的异常处理。
我们首先来定义一个表示Fault的类:SQLError。考虑到这个类需要在Service 和Client使用,我把它定义在SCF.Contracts中:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; namespace SCF.Contracts { [DataContract] public class SQLError { private string _operation; private string _errorMessage; public SQLError(string operation, string errorMessage) { this._operation = operation; this._errorMessage = errorMessage; } [DataMember] public string Operation { get { return _operation; } set { _operation = value; } } [DataMember] public string ErrorMessage { get { return _errorMessage; } set { _errorMessage = value; } } } }
如果你出现如下图的错误信息,请引用一下System.Runtime.Serialization.dll。
在SQLError中定义了两个成员:表示出 错操作的Operation和出错信息的ErrorMessage。由于该类的对象需要在终结点之间传递,所以必须是可序列化的,在WCF中, 我们一般用两个不同的Serializer实现Object和XML的Serialization和 Deserialization:Datacontract Serializer和XML Serializer。而对于Fault,只能使用前者。
定义了SQLError,我们需要通过特性FaultContract将其添加到EditBook方法上面,我们把IBookService接口修改成如下。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace SCF.Contracts { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IBookService”。 [ServiceContract] public interface IBookService { [OperationContract] string GetBook(string Id); [OperationContract] string AddBook(string book); [OperationContract] [FaultContract(typeof(SQLError))] string EditBook(string book); [OperationContract] string Search(string Category, string searchString); } }
我们在EditBook上运用了 FaultContract,并指定了封装了Fault对应的类型,那么最终这个基于SQLError类型的FaultContract会被写入 Service Description中,客户端通过获取该Service Description(一般是获取WSDL),它就被识别它,就会将从接收到的Soap中对该Fault的XML Mapping到具体的SQLError类型。
接着我们在服务端的出错处理中抛出Exception的方式植入这个SQLError对象:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.Data.Entity; using SCF.Contracts; using SCF.Model; using SCF.Common; namespace SCF.WcfService { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“BookService”。 // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 BookService.svc 或 BookService.svc.cs,然后开始调试。 public class BookService : IBookService { Entities db = new Entities(); public string AddBook(string mbook) { try { Books book = XMLHelper.DeSerializer<Books>(mbook); db.Books.Add(book); db.SaveChanges(); } catch (Exception ex) { return ex.Message; } return "true"; } public string EditBook(string mbook) { try { Books book = XMLHelper.DeSerializer<Books>(mbook); db.Entry(book).State = EntityState.Added; db.SaveChanges(); } catch (Exception ex) { //return ex.Message; SQLError error = new SQLError("更新数据库操作", ex.Message); string reason = string.Empty; if (ex.InnerException != null) { reason = string.Format("{0}。{1}"ex.Message, ex.InnerException.Message); } else reason = ex.Message; throw new FaultException<SQLError>(error, new FaultReason(reason), new FaultCode("Edit")); } return "true"; } public string GetBook(string Id) { int bookId = Convert.ToInt32(Id); Books book= db.Books.Find(bookId); string xml=XMLHelper.ToXML<Books>(book); return xml; //throw new NotImplementedException(); } public string Search(string Category, string searchString) { var cateLst = new List<string>(); var cateQry = from d in db.Books orderby d.Category select d.Category; cateLst.AddRange(cateQry.Distinct()); var books = from m in db.Books select m; if (!String.IsNullOrEmpty(searchString)) { books = books.Where(s => s.Name.Contains(searchString)); } List<Books> list = null; if (string.IsNullOrEmpty(Category)) { list = books.ToList<Books>(); } else { list = books.Where(x => x.Category == Category).ToList<Books>(); } return XMLHelper.ToXML<List<Books>>(list); } } }
在catch中,抛出FaultException<SQLError> Exception,并指定具体的SQLError对象,以及一个FaultCode(一般指明出错的来源)和FaultReason(出错的原因)。我们现在先不修改客户端的异常处理的相关代码,先运行Hosting,看看WSDL中什么特别之处,如下图:
通 过上图,我们可以看到,在EditBook方法的WSDL定义了中多了一些节点。
弄清楚了Fault在WSDL中表示后,我们来修改我们客户端的代码,来有效地进行异常处理:
using SCF.Contracts; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using SCF.Model; using SCF.Common; namespace WinClient { public partial class FrmBook : Form { public FrmBook() { InitializeComponent(); } private void ShowBook() { Books book = XMLHelper.DeSerializer<Books>(textBoxMsg.Text); txtBookId.Text = book.BookID.ToString(); txtAuthorID.Text = book.AuthorID.ToString(); textBoxName.Text = book.Name; textBoxCategory.Text = book.Category.ToString(); textBoxPrice.Text = book.Price.ToString(); textBoxRating.Text = book.Rating.ToString(); textBoxNumberofcopies.Text = book.Numberofcopies.ToString(); dateTimePickerPublishDate.Text = book.PublishDate.ToString(); } private void btnSearch_Click(object sender, EventArgs e) { BookServiceRef.BookServiceClient bookSvrClient = new BookServiceRef.BookServiceClient(); textBoxMsg.Text = bookSvrClient.Search(string.Empty, string.Empty); List<Books> books = XMLHelper.DeSerializer<List<Books>>(textBoxMsg.Text); gridBooks.DataSource = books; } private void buttonSave_Click(object sender, EventArgs e) { try { using (ChannelFactory<IBookService> channelFactory = new ChannelFactory<IBookService>("WSHttpBinding_IBookService")) { IBookService proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { if (string.IsNullOrEmpty(txtBookId.Text)) { textBoxMsg.Text = proxy.AddBook(GetBookInfo()); } else textBoxMsg.Text = proxy.EditBook(GetBookInfo()); } } } catch (FaultException<SQLError> ex) { SQLError error = ex.Detail; textBoxMsg.Text = string.Format("抛出一个服务端错误。\r\n\t错误代码:{0}\n\t错误原因:{1}\r\n\t操作:{2}\r\n\t错误信息:{3}",
ex.Code, ex.Reason, error.Operation, error.ErrorMessage); } catch (Exception ex) { if (ex.InnerException != null) { textBoxMsg.Text = ex.Message + ex.InnerException.Message; } else textBoxMsg.Text = ex.Message; } } public String GetBookInfo() { Books book = new Books(); book.AuthorID = NumberHelper.ToInt(txtAuthorID.Text); book.BookID = NumberHelper.ToInt(txtBookId.Text); book.Category = textBoxCategory.Text; book.Name = textBoxName.Text; book.Numberofcopies = NumberHelper.ToInt(textBoxNumberofcopies.Text); book.Price = NumberHelper.ToDecimal(textBoxPrice.Text); book.PublishDate = dateTimePickerPublishDate.Value; book.Rating = textBoxRating.Text; textBoxMsg.Text = XMLHelper.ToXML<Books>(book); return textBoxMsg.Text; } } }
执行“保存”操作之后,服务端抛出了如下错误信息: