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; } } }
執行“保存”操作之后,服務端拋出了如下錯誤信息: