WCF学习之旅—WCF中传统的异常处理(十六)
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。错误信息也是很通用的一种,无法有效提供详细的错误信息,以供我们来解决问题。