【WCF--初入江湖】03 配置服务
03 配置服务
数据库
生成数据库脚本:
CREATE DATABASE [EmployeeDb]; CREATE TABLE [dbo].[T_Employee]( [Id] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](50) NOT NULL, [Job] [nvarchar](50) NOT NULL, [Salary] [float] NOT NULL, [Dept] [nchar](10) NULL, ) ;
Employee.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; namespace Keasy5.WCF.Imin.Chart06.DTO { [DataContract] public class Employee { [DataMember] public int Id { get; set; } [DataMember] public string Name { get; set; } [DataMember] public string Job { get; set; } [DataMember] public double Salary { get; set; } [DataMember] public string Dept { get; set; } } }
数据访问EmployeeDAL.cs
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Text; using Keasy5.WCF.Imin.Chart06.DTO; namespace Keasy5.WCF.Imin.Chart06.DAL { public class EmployeeDAL { private const string ConnectingString = "Data Source=.;Initial Catalog=EmployeeDb;Integrated Security=True;Pooling=False"; public IEnumerable<Employee> GetEmployees() { using (var sqlConnection = new SqlConnection(ConnectingString)) { List<Employee> result = new List<Employee>(); Employee employee = null; string sqlQuery = "Select * from T_Employee;"; DataTable dataTable = new DataTable(); SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlQuery, sqlConnection); sqlDataAdapter.Fill(dataTable); for (int i = 0; i < dataTable.Rows.Count; i++) { employee = new Employee(); employee.Id = Convert.ToInt32(dataTable.Rows[i]["Id"]); employee.Name = Convert.ToString(dataTable.Rows[i]["Name"]); employee.Job = Convert.ToString(dataTable.Rows[i]["Job"]); employee.Salary = Convert.ToDouble(dataTable.Rows[i]["Salary"]); employee.Dept = Convert.ToString(dataTable.Rows[i]["Dept"]); result.Add(employee); } return result; } } public void AddEmployee(Employee employee) { using (var sqlConnection = new SqlConnection(ConnectingString)) { sqlConnection.Open(); using (SqlCommand command = sqlConnection.CreateCommand()) { StringBuilder insertSql = new StringBuilder(); insertSql.Append("insert into T_Employee values(@Name,@Job,@Salary,@Dept)"); command.CommandType = CommandType.Text; command.CommandText = insertSql.ToString(); command.Parameters.Add(new SqlParameter("@Name", employee.Name)); command.Parameters.Add(new SqlParameter("@Job", employee.Job)); command.Parameters.Add(new SqlParameter("@Salary", employee.Salary)); command.Parameters.Add(new SqlParameter("@Dept", employee.Dept)); command.ExecuteNonQuery(); } } } } }
WCF的定义
服务接口IService1
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.Text; using System.Data; using System.Data.SqlClient; using Keasy5.WCF.Imin.Chart06.DTO; namespace Keasy5.WCF.Imin.Chart06.Pro02 { [ServiceContract] public interface IService1 { [OperationContract] IEnumerable<Employee> GetEmployees(); [OperationContract] void AddEmployee(Employee employee); } }
服务实现类Service1.cs
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using Keasy5.WCF.Imin.Chart06.DAL; using Keasy5.WCF.Imin.Chart06.DTO; namespace Keasy5.WCF.Imin.Chart06.Pro02 { public class Service1 : IService1 { private EmployeeDAL employeeDal = new EmployeeDAL(); public IEnumerable<Employee> GetEmployees() { return employeeDal.GetEmployees(); } public void AddEmployee(Employee employee) { employeeDal.AddEmployee(employee); } } }
WCF服务端的配置
一个最基本的服务(宿主)端配置如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name ="Keasy5.WCF.Imin.Chart06.Pro02.Service1" behaviorConfiguration="testBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:5555"/> </baseAddresses> </host> <endpoint address ="" binding="wsHttpBinding" contract="Keasy5.WCF.Imin.Chart06.Pro02.IService1"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="testBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
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.Windows.Forms; using Keasy5.WCF.Imin.Chart06.Pro02; namespace Keasy5.WCF.Chart06.Pro02.Host { public partial class Form1 : Form { private ServiceHost serviceHost = new ServiceHost(typeof(Service1)); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { ServiceHost serviceHost = new ServiceHost(typeof(Service1)); serviceHost.Open(); this.label1.Text = "服务器已开启!"; } private void button2_Click(object sender, EventArgs e) { if (serviceHost.State != CommunicationState.Closed) { serviceHost.Close(); this.label1.Text = "服务已关闭!"; } } } }
宿主选择WinForm,服务的开启和关闭如下代码所示:
其中创建宿主的ServiceHost类的构造函数:
// // 摘要: // 使用服务的类型及其指定的基址初始化 System.ServiceModel.ServiceHost 类的新实例。 // // 参数: // serviceType: // 承载服务的类型。 // // baseAddresses: // System.Uri 类型的数组,包含承载服务的基址。 // // 异常: // System.ArgumentNullException: // serviceType 为 null。 public ServiceHost(Type serviceType, params Uri[] baseAddresses);
第二个参数是可变参数类型:所以可以参入基于不同协议的Uri,
Uri = “net.TCP://localhost/....”
Uri = "http://....."
Uri = "net.pipe://。。。"
。。。。
传入这些Uir基地址,实现同一服务,可同时提供多种协议的服务:
//地址 Uri pipeaddress = new Uri("net.pipe://localhost/NetNamedPipeBinding"); Uri tcpaddress = new Uri("net.tcp://localhost:8088/TcpBinding"); //服务宿主对象 host = new ServiceHost(typeof(WcfServiceLibrary1.Service1), pipeaddress, tcpaddress); //绑定 NetNamedPipeBinding pb = new NetNamedPipeBinding(); NetTcpBinding tp = new NetTcpBinding();
或者使用配置文件进行配置:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="WcfServiceLibrary1.Service1" behaviorConfiguration="textBehavior"> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:8088/tcpBinding"/> <add baseAddress="http://localhost:3200/httpBinding"/> <add baseAddress="net.pipe://localhost/pipeBinding"/> </baseAddresses> </host> <endpoint address="tcpmex" binding="mexTcpBinding" contract="IMetaExchange"></endpoint> <endpoint address="pipemex" binding="mexNamedPipeBinding" contract="IMetaExchange"></endpoint> <endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary1.IService1"></endpoint> <endpoint address="" binding="netTcpBinding" contract="WcfServiceLibrary1.IService1"></endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="textBehavior"> <serviceMetadata/> <serviceDebug/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
客户端调用WCF服务
客户端调用WCF服务,需要一个WCF服务的代理类;
获取该代理类的方法有几种:
【1】在客户端的项目中引用服务的方式
添加服务引用时自动生成的服务代理时自动生成的EndPiont
<!--添加服务引用时自动生成的服务代理时自动生成的EndPiont--> <endpoint address="http://127.0.0.1:9999/Easy5WCFService" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CalcutorService" contract="CalcutorWCFService.CalcutorService" name="BasicHttpBinding_CalcutorService" />
//方法1:使用添加服务引用时自动生成的服务代理(这时,自动生成app.config) //此法用的代理服务命名空间的类型 using (CalcutorWCFService.CalcutorServiceClient proxy = new CalcutorWCFService.CalcutorServiceClient()) { double x = Convert.ToDouble(this.textBox1.Text); double y = Convert.ToDouble(this.textBox2.Text); this.textBox3.Text = Convert.ToString(proxy.Add(x, y)); }
【2】手工生成代理类,这种又有很多种方式:
2.1 ChannelFactory<T>
使用ChannelFactory<T>的好处是支持泛型。
//此法需要:自己动手添加app.config using (ChannelFactory<Contract.ICalcuator> channelFactory = new ChannelFactory<Contract.ICalcuator>("calcutorService"))//calcutorService为配置文件(app.config)中的endpoint的Name属性 { Contract.ICalcuator calcuator = channelFactory.CreateChannel(); double x = Convert.ToDouble(this.textBox1.Text); double y = Convert.ToDouble(this.textBox2.Text); this.textBox3.Text = Convert.ToString(calcuator.Add(x, y)); }
需要手工在app.config文件中添加endPoint节点配置:
<!--自己动手写服务代理时,需要手动添加的EndPoint--> <endpoint address="http://127.0.0.1:9999/Easy5WCFService" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CalcutorService" contract="Contract.ICalcuator" name="calcutorService" />
ChannelFactory<T>的构造函数有多个重载,也可以按如下方式创建:
using (ChannelFactory<Contract.ICalcuator> channelFactory = new ChannelFactory<Contract.ICalcuator>(new BasicHttpBinding(), "http://127.0.0.1:9999/Easy5WCFService")) { Contract.ICalcuator calcuator = channelFactory.CreateChannel(); double x = Convert.ToDouble(this.textBox1.Text); double y = Convert.ToDouble(this.textBox2.Text); this.textBox3.Text = Convert.ToString(calcuator.Add(x, y)); }
但是,这种方式是硬编码方式,灵活性不好,不推荐。
2.2 使用svcutil.exe工具生成代理类:
WCF的服务元数据(Metadata),遵循Web服务描述语言(WSDL)标准,所以支持多种编程语言,处理WCF的svcutil.exe外,
java程序员也可以使用诸如WSDL2Java工具生成Java语言的客户端代理类。
下面介绍使用svcutil.exe生成C#语言的代理类:
第一步:公开WCF服务的元数据信息
方法一: 通过<serviceMetadata httpGetEnabled="true"/>公开,如下所示
<system.serviceModel> <services> <service name ="Keasy5.WCF.Imin.Chart06.Pro02.Service1" behaviorConfiguration="testBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:5555"/> </baseAddresses> </host> <endpoint address ="" binding="wsHttpBinding" contract="Keasy5.WCF.Imin.Chart06.Pro02.IService1"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="testBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
方法二: 通过EndPoint端点的方式进行公开,在服务的配置文件中添加如下配置
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name ="Keasy5.WCF.Imin.Chart06.Pro02.Service1" behaviorConfiguration="testBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:5555"/> </baseAddresses> </host> <endpoint address ="" binding="wsHttpBinding" contract="Keasy5.WCF.Imin.Chart06.Pro02.IService1"> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetatadaExchange"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="testBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
公开WCF服务的元数据信息后就可以在浏览器中输入:http://localhost:5555 就可以查看WCF服务信息。
第二步:
打开【VS2013 开发人员命令提】,输入:
svcutil.exe http://localhost:5555/?wsdl
自动生成的文件:
Service1.cs是生成的代理类
//------------------------------------------------------------------------------ // <auto-generated> // 此代码由工具生成。 // 运行时版本:4.0.30319.18408 // // 对此文件的更改可能会导致不正确的行为,并且如果 // 重新生成代码,这些更改将会丢失。 // </auto-generated> //------------------------------------------------------------------------------ using System.Data; [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IService1")] public interface IService1 { // CODEGEN: 参数“GetEmployeesResult”需要其他方案信息,使用参数模式无法捕获这些信息。特定特性为“System.Xml.Serialization.XmlElementAttribute”。 [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService1/GetEmployees", ReplyAction="http://tempuri.org/IService1/GetEmployeesResponse")] [System.ServiceModel.XmlSerializerFormatAttribute()] GetEmployeesResponse GetEmployees(GetEmployeesRequest request); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService1/GetEmployees", ReplyAction="http://tempuri.org/IService1/GetEmployeesResponse")] System.Threading.Tasks.Task<GetEmployeesResponse> GetEmployeesAsync(GetEmployeesRequest request); // CODEGEN: 参数“employee”需要其他方案信息,使用参数模式无法捕获这些信息。特定特性为“System.Xml.Serialization.XmlElementAttribute”。 [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService1/AddEmployee", ReplyAction="http://tempuri.org/IService1/AddEmployeeResponse")] [System.ServiceModel.XmlSerializerFormatAttribute()] AddEmployeeResponse AddEmployee(AddEmployeeRequest request); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService1/AddEmployee", ReplyAction="http://tempuri.org/IService1/AddEmployeeResponse")] System.Threading.Tasks.Task<AddEmployeeResponse> AddEmployeeAsync(AddEmployeeRequest request); } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] [System.ServiceModel.MessageContractAttribute(WrapperName="GetEmployees", WrapperNamespace="http://tempuri.org/", IsWrapped=true)] public partial class GetEmployeesRequest { public GetEmployeesRequest() { } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] [System.ServiceModel.MessageContractAttribute(WrapperName="GetEmployeesResponse", WrapperNamespace="http://tempuri.org/", IsWrapped=true)] public partial class GetEmployeesResponse { [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://tempuri.org/", Order=0)] [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] public System.Data.DataTable GetEmployeesResult; public GetEmployeesResponse() { } public GetEmployeesResponse(System.Data.DataTable GetEmployeesResult) { this.GetEmployeesResult = GetEmployeesResult; } } /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "4.0.30319.33440")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.datacontract.org/2004/07/Keasy5.WCF.Imin.Chart06.DTO")] public partial class Employee { private string deptField; private int idField; private bool idFieldSpecified; private string jobField; private string nameField; private double salaryField; private bool salaryFieldSpecified; /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=0)] public string Dept { get { return this.deptField; } set { this.deptField = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Order=1)] public int Id { get { return this.idField; } set { this.idField = value; } } /// <remarks/> [System.Xml.Serialization.XmlIgnoreAttribute()] public bool IdSpecified { get { return this.idFieldSpecified; } set { this.idFieldSpecified = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=2)] public string Job { get { return this.jobField; } set { this.jobField = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=3)] public string Name { get { return this.nameField; } set { this.nameField = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Order=4)] public double Salary { get { return this.salaryField; } set { this.salaryField = value; } } /// <remarks/> [System.Xml.Serialization.XmlIgnoreAttribute()] public bool SalarySpecified { get { return this.salaryFieldSpecified; } set { this.salaryFieldSpecified = value; } } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] [System.ServiceModel.MessageContractAttribute(WrapperName="AddEmployee", WrapperNamespace="http://tempuri.org/", IsWrapped=true)] public partial class AddEmployeeRequest { [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://tempuri.org/", Order=0)] [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] public Employee employee; public AddEmployeeRequest() { } public AddEmployeeRequest(Employee employee) { this.employee = employee; } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] [System.ServiceModel.MessageContractAttribute(WrapperName="AddEmployeeResponse", WrapperNamespace="http://tempuri.org/", IsWrapped=true)] public partial class AddEmployeeResponse { public AddEmployeeResponse() { } } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] public interface IService1Channel : IService1, System.ServiceModel.IClientChannel { } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] public partial class Service1Client : System.ServiceModel.ClientBase<IService1>, IService1 { public Service1Client() { } public Service1Client(string endpointConfigurationName) : base(endpointConfigurationName) { } public Service1Client(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public Service1Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] GetEmployeesResponse IService1.GetEmployees(GetEmployeesRequest request) { return base.Channel.GetEmployees(request); } public System.Data.DataTable GetEmployees() { GetEmployeesRequest inValue = new GetEmployeesRequest(); GetEmployeesResponse retVal = ((IService1)(this)).GetEmployees(inValue); return retVal.GetEmployeesResult; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] System.Threading.Tasks.Task<GetEmployeesResponse> IService1.GetEmployeesAsync(GetEmployeesRequest request) { return base.Channel.GetEmployeesAsync(request); } public System.Threading.Tasks.Task<GetEmployeesResponse> GetEmployeesAsync() { GetEmployeesRequest inValue = new GetEmployeesRequest(); return ((IService1)(this)).GetEmployeesAsync(inValue); } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] AddEmployeeResponse IService1.AddEmployee(AddEmployeeRequest request) { return base.Channel.AddEmployee(request); } public void AddEmployee(Employee employee) { AddEmployeeRequest inValue = new AddEmployeeRequest(); inValue.employee = employee; AddEmployeeResponse retVal = ((IService1)(this)).AddEmployee(inValue); } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] System.Threading.Tasks.Task<AddEmployeeResponse> IService1.AddEmployeeAsync(AddEmployeeRequest request) { return base.Channel.AddEmployeeAsync(request); } public System.Threading.Tasks.Task<AddEmployeeResponse> AddEmployeeAsync(Employee employee) { AddEmployeeRequest inValue = new AddEmployeeRequest(); inValue.employee = employee; return ((IService1)(this)).AddEmployeeAsync(inValue); } }
output.config是配置文件
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IService1" /> </wsHttpBinding> </bindings> <client> <endpoint address="http://localhost:5555/" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1" contract="IService1" name="WSHttpBinding_IService1"> <identity> <userPrincipalName value="easy5-PC\easy5" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
现在用在项目中引用服务的方式添加代理,客户端如下调用代理:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Keasy5.WCF.Chart06.Pro02.Client.ServiceReference1; using Keasy5.WCF.Imin.Chart06.DTO; namespace Keasy5.WCF.Chart06.Pro02.Client { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { IService1 proxService1 = new Service1Client(); Employee newEmployee = new Employee() { Name = "Jack", Job = "Killer", Salary = 1000000, Dept = "N/A" }; proxService1.AddEmployee(newEmployee); } private void button2_Click(object sender, EventArgs e) { IService1 proxService1 = new Service1Client(); IEnumerable<Employee> employees = proxService1.GetEmployees(); this.dataGridView1.DataSource = employees; } } }
本文代码下载:
链接: http://pan.baidu.com/s/1ntKA03J 密码: 4p4h
【The end】