WCF中的異常處理
在軟件開發過程中,不可能沒有異常的出現,所以在開發過程中,對不可預知的異常進行解決時,異常處理顯得尤為重要。對於一般的.NET系統來說,我們簡單地借助try/catch可以很容易地實現這一功能。但是對於 一個分布式的環境來說,異常處理就沒有那么簡單了。按照面向服務的原則,我們把一些可復用的業務邏輯以服務的形式實現,各個服務處於一個自治的環境中,一個服務需要和另一個服務進行交互,只需要獲得該服務的描述(Description)就可以了(比如 WSDL,Schema和Strategy)。借助標准的、平台無關的通信構架,各個服務之間通過標准的Soap Message進行交互。Service Description、Standard Communication Infrastructure、Soap Message based Communication促使各服務以松耦合的方式結合在一起。但是由於各個服務是自治的,如果一個服務調用另一個服務,在服務提供方拋出的Exception必須被封裝在Soap Message中,方能被處於另一方的服務的使用者獲得、從而進行合理的處理。下面我們結合一個簡單的示例來簡單地介紹一下我們可以通過哪些方式在WCF服務應用程序中進行異常處理。
一、傳統的異常處理
我們還是使用上面文章中使用過的書籍管理示例。如下圖。

1. SCF.Contracts
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] void DoWork(); [OperationContract] string GetBook(string Id); [OperationContract] string AddBook(string book); [OperationContract] string EditBook(string book); [OperationContract] string Search(string Category, string searchString); } }
2. SCF.WcfService
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 void DoWork() { } 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; throw ex; } 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>(); //return XMLHelper.ToXML<List<Books>>(list); } else { list = books.Where(x => x.Category == Category).ToList<Books>(); // return XMLHelper.ToXML<IQueryable<Books>>(books.Where(x => x.Category == Category)); } return XMLHelper.ToXML<List<Books>>(list); } } }
注:在編輯書籍信息時,會拋出一個DbUpdateException信息。我在數據庫中設了一個唯一索引,不能插入重復值
3. Service Hosting
配置文件信息如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8888/BookService/metadata" /> <serviceDebug includeExceptionDetailInFaults="True" /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration="metadataBehavior" name="SCF.WcfService.BookService"> <endpoint address="http://127.0.0.1:8888/BookService" binding="wsHttpBinding" contract="SCF.Contracts.IBookService" /> </service> </services> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <connectionStrings> <add name="TestEntities" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Test;Integrated Security=SSPI"
providerName="System.Data.SqlClient" /> <add name="Entities" connectionString="metadata=res://*/BookModel.csdl|res://*/BookModel.ssdl|
res://*/BookModel.msl;provider=System.Data.SqlClient;provider connection string="data source=.\SQLEXPRESS;initial catalog
=Test;integrated security=SSPI;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings> </configuration>
Host程序代碼如下:
using SCF.WcfService; using SCF.Contracts; using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.ServiceModel.Description; using System.Text; using System.Threading.Tasks; namespace Hosting { class Program { static void Main(string[] args) { try { using (ServiceHost host = new ServiceHost(typeof(BookService))) { host.Opened += delegate { Console.WriteLine("BookService,使用配置文件,按任意鍵終止服務!"); }; host.Open(); Console.ForegroundColor = ConsoleColor.Yellow; foreach (ServiceEndpoint se in host.Description.Endpoints) { Console.WriteLine("[終結點]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-綁定]: {2} \r\n\t [C-協定]: {3}", se.Name, se.Address, se.Binding.Name, se.Contract.Name); } Console.Read(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } //Console.Read(); } } }
4. 客戶端代碼如下
配置文件信息如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IBookService" /> <binding name="WSHttpBinding_IBookService1" /> </wsHttpBinding> <customBinding> <binding name="listenUriBinding"> <textMessageEncoding /> <httpTransport /> </binding> </customBinding> </bindings> <client> <endpoint address="http://127.0.0.1:8888/BookService" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IBookService" contract="SCF.Contracts.IBookService" name="WSHttpBinding_IBookService"> <identity> <userPrincipalName value="DEVELOPER\Administrator" /> </identity> </endpoint> <endpoint address="http://127.0.0.1:8888/BookService" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IBookService1" contract="BookServiceRef.IBookService" name="WSHttpBinding_IBookService1"> <identity> <userPrincipalName value="DEVELOPER\Administrator" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
客戶端程序代碼如下:
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 btnGetBook_Click(object sender, EventArgs e) { Books book = new Books(); BookServiceRef.BookServiceClient bookSvrClient = new BookServiceRef.BookServiceClient(); if (gridBooks.SelectedRows.Count > 0) { book = gridBooks.SelectedRows[0].DataBoundItem as Books; textBoxMsg.Text = bookSvrClient.GetBook(book.BookID.ToString()); ShowBook(); } else { textBoxMsg.Text = "沒有選中相應的記錄!"; } } /// <summary> /// ChannelFactory方式,直接在代碼中寫配置信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonChannelFactory_Click(object sender, EventArgs e) { using (ChannelFactory<IBookService> channelFactory = new ChannelFactory<IBookService> (new WSHttpBinding(), "http://127.0.0.1:8888/BookService")) { IBookService proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { textBoxMsg.Text = proxy.GetBook("4"); ShowBook(); } } } /// <summary> /// ChannelFactory配置方式,在配置文件中寫配置信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonChannelConfig_Click(object sender, EventArgs e) { using (ChannelFactory<IBookService> channelFactory = new ChannelFactory<IBookService>("WSHttpBinding_IBookService")) { IBookService proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { textBoxMsg.Text = proxy.GetBook("5"); ShowBook(); } } } 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 btnSearchCategory_Click(object sender, EventArgs e) { BookServiceRef.BookServiceClient bookSvrClient = new BookServiceRef.BookServiceClient(); textBoxMsg.Text = bookSvrClient.Search(txtCategory.Text, 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 (Exception ex) { throw ex; } } 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; } } }
把Service調用放在一個try/catch 程序代碼段中,看看Service端拋出的DbUpdateException能否被Catch。
現在我們運行這個程序,看看客戶端報錯信息如下:
我們發現客戶端無法捕捉服務端真正拋出的出錯信息,而是一個比較通用的FaultException。錯誤信息也是很通用的一種,無法有效提供詳細的錯誤信息,以供我們來解決問題。