前言
又墮落了,哎。
公司是做乙方的,工資還湊合,主要是項目基本都已完成,進去就是干維護,體會不到那種從頭徹尾的成就感。項目中具體用了EF+Ado.net+WCF+WPF+(VB.net啊,坑啊,完全不知道是這個東西),整個解決方案有47個項目,是一個國際化的電話保險銷售系統中的一部分。感覺自己是去體驗生活的。這個項目也就是自己學習WCF的原因。
工作方面,等手上的結束了再賣自己一次吧。
第十集 Message Contract in WCF (WCF 的MessageContract特性)
很簡單的一集,講的主要是WCF中的MessageContract特性,具體作用是用來控制WCF 的xml request和response的soap消息格式。可以理解為前面講過的DataContract的升級版。如果我們想在程序上完全控制soap消息的格式,這就是一個很好的解決方案。
視頻上通過修改以前的Demo來演示了一遍他的具體使用。我們也來做一遍。
首先,定義兩個類EmployeeRequest 和 EmployeeInfo

1 [MessageContract(IsWrapped = true, WrapperName = "EmployeeRequestObj", WrapperNamespace = "http://HelloWcf.com/2015/02/04")] 2 public class EmployeeRequest 3 { 4 [MessageHeader(Namespace = "http://HelloWcf.com/2015/02/04")] 5 public String LicenseKey { get; set; } 6 7 [MessageBodyMember(Namespace = "http://HelloWcf.com/2015/02/04")] 8 public int Id { get; set; } 9 } 10 [MessageContract] 11 public class EmployeeInfo 12 { 13 public EmployeeInfo() 14 { 15 16 } 17 public EmployeeInfo(Employee emp) 18 { 19 this.ID = emp.Id; this.Name = emp.Name; 20 this.Gender = emp.Gender; this.DOB = emp.DateOfBirth; 21 this.EmployeeType = emp.EmployeeType; 22 } 23 [MessageBodyMember] 24 public int ID { get; set; } 25 [MessageBodyMember] 26 public String Name { get; set; } 27 [MessageBodyMember] 28 public bool Gender { get; set; } 29 [MessageBodyMember] 30 public DateTime DOB { get; set; } 31 [MessageBodyMember] 32 public EmployeeType EmployeeType { get; set; } 33 }
區別與以前的DataContract ,這個類是用MessageContract來修飾,里面的屬性用MessageHeader 或者 MessageBodyMember ,作用很明顯,分別對應soap message里面的head 和 body。
LicenseKey在實際的環境中用來標識客戶端的身份,他可以以加密的方式存在,以后會講到。放在Header中,區別於我們Request的body中具體業務邏輯相關的內容。
然后修改IEmployeeService這個 ServiceContract

1 [ServiceContract] 2 public interface IEmployeeService 3 { 4 [OperationContract] 5 EmployeeInfo GetEmployee(EmployeeRequest req); 6 7 [OperationContract] 8 void SaveEmployee(EmployeeInfo emp); 9 10 }
注意,當我們修改了原先的兩個OperationContract 方法的參數后,客戶端就不曉得原先的Employee的概念,他們只知道現在的Request 和 Info。
接口改完之后自然要改寫接口的實現

1 public class EmployeeService : IEmployeeService 2 { 3 public EmployeeInfo GetEmployee(EmployeeRequest er) 4 { 5 Employee emp = null; 6 var connStr = ConfigurationManager.ConnectionStrings["WCFEmployee"].ConnectionString; 7 using(var conn = new SqlConnection(connStr)) { 8 conn.Open(); 9 var cmd = conn.CreateCommand(); 10 cmd.CommandType = System.Data.CommandType.StoredProcedure; 11 cmd.CommandText = "spGetEmployeeById"; 12 cmd.Parameters.Add(new SqlParameter("id", er.Id)); 13 var reader = cmd.ExecuteReader(); 14 if(!reader.HasRows) return null; 15 emp = new Employee(); 16 while(reader.Read()) { 17 emp.Id = Convert.ToInt32(reader["Id"]); 18 emp.Name = reader["Name"].ToString(); 19 emp.Gender = Convert.ToBoolean(reader["Gender"]); 20 emp.DateOfBirth = Convert.ToDateTime(reader["DateOfBirth"]); 21 emp.EmployeeType = (EmployeeType)Convert.ToInt16(reader["EmployeeType"]); 22 } 23 } 24 return new EmployeeInfo(emp); 25 } 26 27 public void SaveEmployee(EmployeeInfo emp) 28 { 29 var connStr = ConfigurationManager.ConnectionStrings["WCFEmployee"].ConnectionString; 30 using(var conn = new SqlConnection(connStr)) { 31 conn.Open(); 32 var cmd = conn.CreateCommand(); 33 cmd.CommandType = System.Data.CommandType.StoredProcedure; 34 cmd.CommandText = "spSaveEmployee"; 35 cmd.Parameters.Add(new SqlParameter("id", emp.ID)); 36 cmd.Parameters.Add(new SqlParameter("name", emp.Name)); 37 cmd.Parameters.Add(new SqlParameter("gender", emp.Gender)); 38 cmd.Parameters.Add(new SqlParameter("dateOfBirth", emp.DOB)); 39 cmd.Parameters.Add(new SqlParameter("employeeType", (short)emp.EmployeeType)); 40 cmd.ExecuteNonQuery(); 41 } 42 } 43 44 }
全是基本的Ado.net的內容,可不看。測試項目用EntityFramework來做挺爽的。記得在VPN掛掉之前下了視頻同個作者的EF教程,等學完這個再相對系統的學習一下EF。
服務端OK了,啟動Host運行一下。(記得要以管理員方式運行host)
客戶端更新一下服務引用,修改一下以前的兩個click事件的代碼

1 protected void btnGet_Click(object sender, EventArgs e) 2 { 3 IEmployeeService client = new EmployeeServiceClient(); 4 try { 5 var emp = client.GetEmployee(new EmployeeRequest() { LicenseKey = "ABCDEFGHI1234567", Id = Convert.ToInt32(tbId.Text) }); 6 if(emp == null) { tbName.Text = ""; tbDateOfBirth.Text = ""; rbGenderList.SelectedIndex = -1; throw new Exception("沒有找到員工"); } 7 lbRstMsg.Text = "查找成功"; 8 tbName.Text = emp.Name; tbDateOfBirth.Text = emp.DOB.ToShortDateString(); rbGenderList.SelectedValue = emp.Gender.ToString(); 9 } catch(Exception ex) { 10 lbRstMsg.Text = ex.Message; 11 } finally { 12 } 13 } 14 protected void btnSave_Click(object sender, EventArgs e) 15 { 16 IEmployeeService client = new EmployeeServiceClient(); 17 try { 18 19 var emp = new EmployeeInfo() 20 { 21 ID = Convert.ToInt32(tbId.Text), Name = Convert.ToString(tbName.Text), 22 Gender = Convert.ToBoolean(rbGenderList.SelectedValue), DOB = Convert.ToDateTime(tbDateOfBirth.Text) 23 }; 24 client.SaveEmployee(emp); 25 lbRstMsg.Text = "保存成功"; 26 } catch(Exception ex) { 27 lbRstMsg.Text = ex.Message; 28 } 29 }
話說這個WebForm的Demo,寫起來也挺開心的。
這里有個要注意的地方是第三行,實例化client的時候要定義個IEmployeeService 接口類型,不能直接用var來,如果用var就是后面的EmployeeServiceClient類型,他的參數不是ServiceContract中定義的EmployeeRequest,至於為什么,我也不曉得,以后應該會知道的吧。
最后保存,編譯,測試一下。
一切如願,再來看看上集中講的Log。
首先是Request:

1 <MessageLogTraceRecord> 2 <HttpRequest xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace"> 3 <Method>POST</Method> 4 <QueryString></QueryString> 5 <WebHeaders> 6 <VsDebuggerCausalityData>uIDPo34fm0IuoetItvuIdT55jAEAAAAAXsy48FQlpECWY/zesZsAtJ94pCeN9F5IvPTu2MbWQ3AACQAA</VsDebuggerCausalityData> 7 <SOAPAction>"http://tempuri.org/IEmployeeService/GetEmployee"</SOAPAction> 8 <Connection>Keep-Alive</Connection> 9 <Content-Length>291</Content-Length> 10 <Content-Type>text/xml; charset=utf-8</Content-Type> 11 <Accept-Encoding>gzip, deflate</Accept-Encoding> 12 <Expect>100-continue</Expect> 13 <Host>localhost:8080</Host> 14 </WebHeaders> 15 </HttpRequest> 16 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 17 <s:Header> 18 <h:LicenseKey xmlns:h="http://HelloWcf.com/2015/02/04">ABCDEFGHI1234567</h:LicenseKey> 19 <To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:8080/EmployeeService</To> 20 <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IEmployeeService/GetEmployee</Action> 21 </s:Header> 22 <s:Body> 23 <EmployeeRequestObj xmlns="http://HelloWcf.com/2015/02/04"> 24 <Id>1</Id> 25 </EmployeeRequestObj> 26 </s:Body> 27 </s:Envelope> 28 </MessageLogTraceRecord>
Header中有LicenseKey,body中有Id。
再看Response:

1 <MessageLogTraceRecord> 2 <Addressing xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace"> 3 <Action>http://tempuri.org/IEmployeeService/GetEmployeeResponse</Action> 4 </Addressing> 5 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 6 <s:Header> 7 <ActivityId CorrelationId="7451a535-182c-4b89-8e42-8e312b48d8e0" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">f50e4154-a211-4ed1-b959-9965f22a70d3</ActivityId> 8 </s:Header> 9 <s:Body> 10 <EmployeeInfo xmlns="http://tempuri.org/"> 11 <DOB>1990-01-02T00:00:00</DOB> 12 <EmployeeType>FullTimeEmployee</EmployeeType> 13 <Gender>true</Gender> 14 <ID>1</ID> 15 <Name>員工1</Name> 16 </EmployeeInfo> 17 </s:Body> 18 </s:Envelope> 19 </MessageLogTraceRecord>
屬性都是我們在EmployeeInfo中定義的,WrapperName,Namespace等也都ok。
綜上所述,有了MessageContract這個特性,我們對soap信息可以想怎么干就怎么干。
ThankYou。