一、WCF服务应用程序与WCF服务库
我们在平时开发的过程中常用的项目类型有“WCF 服务应用程序”和“WCF服务库”。
WCF服务应用程序,是一个可以执行的程序,它有独立的进程,WCF服务类契约的定义,可以直接看到运行的效果。此项目模板基于IIS托管的程序,如本系列的第一节所示。在开发基于IIS托管的WCF服务程序时,比较多见,自学的时候也可以使用这种类型,简单易懂。
WCF服务库,可以认为是一个包含WCF服务以及契约定义的类库。不能直接运行,你可以在其他项目里引用,在宿主里启用托管这个库,有点类似于我们在Web项目中应用的类库。考虑WCF服务设计的时候,服务类的定义为单独的库,可以为其它项目使用。提高代码的复用性。
当然你也可以修改这些代码,比如把WCF服务程序里的类,移到一个单独的类库里,或是把类库里的类移到WCF服务程序中。
二、概述
通过前面的介绍我们知道,WCF在运行时必寄宿在“宿主程序”之上,WCF本身不能够独自运行(每个WCF服务必须宿主在一个Windows进程中)。.net 提供了多种宿主供WCF运行,WCF还是非常灵活的。
WCF的宿主可以是 Windows 服务、COM+应用程序、WAS(Windows Activation Services,Windows进程激活服务)或IIS、Windows应用程序,或简单的控制台应用程序及任何.net程序。
这节的示例我们重新建立WCF类库项目为例做示例,名称为:WCFLibrary 如下图所示:
鼠标右键查看项目属性。我们发现,其实“WCF类库项目”与我们平时建立的“类库项目”都是类库,只不过多了WCF的类库项目在新建时多了两个dll的引用(System.ServiceModel.dll、System.Runtime.Serialization.dll)和一个自动生成的配置文件(该配置文件只用于调试时使用,在WCF寄宿以后会应用宿主的配置文件与其他应用程序通信)。这更说明了我们在做分式程序开发的时候与我们平时开发的应用程序没有多大的区别,只要我们在应用程序间通信时“符合WCF的约定”即可。
[IService1.cs]
namespace WCFLibrary{ [ServiceContract] [ServiceKnownType(typeof(Entity))] [ServiceKnownType(typeof(EntityCollection))] [ServiceKnownType(typeof(Entity[]))] public interface IService1 { [OperationContract] [WebGet()] Stream GetData1(string callback, string value); [OperationContract] [WebGet()] Stream GetData2(string callback, string cityid, string cityname); [OperationContract] [WebGet()] Stream GetData3(string callback, string data); [OperationContract] [WebGet(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] string GetData4(string name); [OperationContract] [WebGet(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Stream GetData5(string userID, string userName); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] string GetData6(string name); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Stream GetData7(string userID, string userName); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Stream GetData8(DynamicXml entity); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] DynamicXml GetData9(DynamicXml entity); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Stream GetData10(DynamicXml entity); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] DynamicXmlCollection GetData11(DynamicXml entity); [OperationContract] [WebInvoke(UriTemplate = "/GetData12", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Entity GetData12(Entity entity); [OperationContract] [WebInvoke(UriTemplate = "/GetData13", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] EntityCollection GetData13(Entity entity); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] User GetData14(string userID, string userName); [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] User GetData15(User user); [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] List<User> GetData16(string userID, string userName); // TODO: 在此添加您的服务操作 } // 使用下面示例中说明的数据约定将复合类型添加到服务操作。 [DataContract] public class User { int userID; string userName; string userAge; [DataMember] public int UserID { get { return userID; } set { userID = value; } } [DataMember] public string UserName { get { return userName; } set { userName = value; } } [DataMember] public string UserAge { get { return userAge; } set { userAge = value; } } }}
[Service1.cs]
namespace WCFLibray{ [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class Service1 : IService1 { public Stream GetData1(string callback, string value) { string jsCode = ((callback == null) ? "" : callback + "(") + "{\"value\":\"" + value + "\"}" + ((callback == null) ? "" : ")"); return new MemoryStream(Encoding.UTF8.GetBytes(jsCode)); } public Stream GetData2(string callback, string cityid, string cityname) { string jsCode = ((callback == null) ? "" : callback + "(") + "{\"CityID\":\"" + cityid + "\",\"CityName\":\"" + cityname + "\"}" + ((callback == null) ? "" : ")"); return new MemoryStream(Encoding.UTF8.GetBytes(jsCode)); } public Stream GetData3(string callback, string data) { dynamic entity = JsonConvert.DeserializeObject<dynamic>(data); string jsCode = ((callback == null) ? "" : callback + "(") + "{\"CityID\":\"" + entity.CityID + "\",\"CityName\":\"旧:" + entity.CityName + " -> 新:武汉\"}" + ((callback == null) ? "" : ")"); return new MemoryStream(Encoding.UTF8.GetBytes(jsCode)); } public string GetData4(string name) { string wcfName = string.Format("WCF服务,显示姓名:{0}", name); return wcfName; } public Stream GetData5(string userID, string userName) { string data = "{\"UserID\":\"" + userID + "\",\"UserName\":\"" + userName + "\"}"; return new MemoryStream(Encoding.UTF8.GetBytes(data)); } public string GetData6(string name) { string wcfName = string.Format("WCF服务,显示姓名:{0}", name); return wcfName; } public Stream GetData7(string userID, string userName) { string data = "{\"UserID\":\"" + userID + "\",\"UserName\":\"" + userName + "\"}"; return new MemoryStream(Encoding.UTF8.GetBytes(data)); } public Stream GetData8(DynamicXml entity) { dynamic result = new DynamicXml(); result.UserID = 1000; result.UserName = entity.GetValue("UserName"); result.Sex = entity.GetValue("Sex"); return new MemoryStream(Encoding.UTF8.GetBytes(result.ToJson())); } public DynamicXml GetData9(DynamicXml entity) { dynamic result = new DynamicXml(); result.UserID = 1000; result.UserName = entity.GetValue("UserName"); result.Sex = entity.GetValue("Sex"); return result; } public Stream GetData10(DynamicXml entity) { DynamicXmlCollection resultList = new DynamicXmlCollection(); dynamic dObject = entity.GetDynamicObject(); foreach (var item in dObject.Users.item) { dynamic result = new DynamicXml(); result.UserID = Convert.ToInt32(item.UserID.Value); result.UserName = item.UserName.ToString(); result.Sex = item.Sex.ToString(); resultList.Add(result); } return new MemoryStream(Encoding.UTF8.GetBytes(resultList.ToJson())); } public DynamicXmlCollection GetData11(DynamicXml entity) { DynamicXmlCollection resultList = new DynamicXmlCollection(); dynamic dObject = entity.GetDynamicObject(); foreach (var item in dObject.Users.item) { dynamic result = new DynamicXml(); result.UserID = Convert.ToInt32(item.UserID.Value); result.UserName = item.UserName.ToString(); result.Sex = item.Sex.ToString(); resultList.Add(result); } return resultList; } public Entity GetData12(Entity entity) { Entity result = null; string userID = entity.GetValue("UserID").ToString(), userName = entity.GetValue("UserName").ToString(); SimpleProperty sUserID = new SimpleProperty("UserID", typeof(Int32)); SimpleProperty sUserName = new SimpleProperty("UserName", typeof(string)); result = new Entity(new TalentCloud.Base.Entities.PropertyCollection() { sUserID, sUserName }); result.SetValue(sUserID, userID); result.SetValue(sUserName, userName); result.Add(new SimpleProperty("CurrentTime", typeof(DateTime)), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); return result; } public EntityCollection GetData13(Entity entity) { string userID = entity.GetValue("UserID").ToString(), userName = entity.GetValue("UserName").ToString(); SimpleProperty sUserID = new SimpleProperty("UserID", typeof(string)); SimpleProperty sUserName = new SimpleProperty("UserName", typeof(string)); SimpleProperty sCurrentTime = new SimpleProperty("CurrentTime", typeof(DateTime)); SimpleProperty sUserAge = new SimpleProperty("UserAge", typeof(string)); PropertyCollection propertyCollection = new PropertyCollection() { sUserID, sUserName, sCurrentTime }; propertyCollection.Add(sUserAge); EntityCollection entityCollection = new EntityCollection(propertyCollection); Entity result = new Entity(propertyCollection); result.SetValue(sUserID, userID); result.SetValue(sUserName, userName); result.SetValue(sCurrentTime, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); result.SetValue(sUserAge, "男"); entityCollection.Add(result); result = new Entity(propertyCollection); result.SetValue(sUserID, "1002"); result.SetValue(sUserName, "李四"); result.SetValue(sCurrentTime, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); result.SetValue(sUserAge, "女"); entityCollection.Add(result); return entityCollection; } public User GetData14(string userID, string userName) { User user = new User(); user.UserID = Convert.ToInt32(userID); user.UserName = userName; user.UserAge = "男"; return user; } public User GetData15(User user) { User result = new User(); result.UserID = user.UserID; result.UserName = user.UserName; result.UserAge = user.UserAge; return result; } public List<User> GetData16(string userID, string userName) { List<User> list = new List<User>(); User user = new User(); user.UserID = Convert.ToInt32(userID); user.UserName = userName; user.UserAge = "男"; list.Add(user); user = new User(); user.UserID = 1002; user.UserName = "李四"; user.UserAge = "女"; list.Add(user); return list; } }}
[App.config]
<?xml version="1.0"?><configuration> <!-- 部署服务库项目时,必须将配置文件的内容添加到 主机的 app.config 文件中。System.Configuration 不支持库的配置文件。--> <system.serviceModel> <!--配置服务节点Start--> <services> <service name="WCFLibray.Service1" behaviorConfiguration="MetadataBehavior"> <!--服务地址--> <host> <!--基址,访问某服务地址的基址--> <baseAddresses> <add baseAddress="http://192.168.5.168:8123/Service1.svc/"/> </baseAddresses> <!--访问超时时间--> <timeouts /> </host> <!--定义endpoint,指定地址(address),绑定(binding)以及协定(contract)--> <!-- 属性说明: address - 服务端地址,相对于baseAddress的相对地址,如果为空则为baseAddress,也可以设定为绝对地址 binding - 绑定协议,系统的某一协议,basicHttpBinding,mexHttpBinding,wsHttpBinding等 contract - 协议完全限定名(名称空间.类型名) name - 客户端代理类的构造方法中的endpointConfigurationName对应到这个name bindingConfiguration - 指定客户端binding的具体设置,指向<bindings>元素下同类型binding的name --> <endpoint binding="webHttpBinding" bindingConfiguration="webHttpBindingConfig" behaviorConfiguration="AspNetAjaxBehavior" contract="WCFLibray.IService1" /> </service> </services> <!--配置服务节点End--> <!--配置行为节点Start--> <behaviors> <serviceBehaviors> <behavior name="MetadataBehavior"> <serviceThrottling maxConcurrentCalls="100000" maxConcurrentInstances="100000" maxConcurrentSessions="100000"/> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 --> <serviceMetadata httpGetEnabled="True"/> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息--> <serviceDebug includeExceptionDetailInFaults="True"/> <!--maxItemsInObjectGraph指定要序列化或反序列化的最大项数--> <dataContractSerializer maxItemsInObjectGraph="2147483647"/> </behavior> </serviceBehaviors> <!--ajax跨域配置Start--> <endpointBehaviors> <behavior name="AspNetAjaxBehavior"> <webHttp /> </behavior> </endpointBehaviors> <!--ajax跨域配置End--> </behaviors> <!--配置行为节点End--> <!--配置绑定节点Start--> <bindings> <webHttpBinding> <binding name="webHttpBindingConfig" maxBufferPoolSize="524288000" maxReceivedMessageSize="65536000" crossDomainScriptAccessEnabled="true" closeTimeout="01:10:00" openTimeout="01:10:00" sendTimeout="01:10:00" receiveTimeout="01:10:00" > <readerQuotas maxStringContentLength="65536000" /> </binding> </webHttpBinding> </bindings> <!--配置绑定节点End--> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"></modules> </system.webServer></configuration>
三、IIS宿主
首先我们将WCF应用程序发布一下,然后部署在服务器的IIS之上,如下图所示:
修改"WCFLibray.Service1.svc"文件名为"Service1.svc",鼠标右键浏览Service1.svc,在游览器中出现如下图所示,说明服务部署成功。
四、控制台应用程序宿主
(1)在解决方案下新建控制台输出项目 WcfByConsole。
(2)添加 System.ServiceModel.dll 的引用。
(3)添加 WCF 服务类库(WCFLibrary)的项目引用。
(4)创建宿主程序,代码如下:
namespace WcfByConsole{ static class Program { static List<ServiceHost> hostList = new List<ServiceHost>(); static string[] serviceURLs; static string midServiceURL = ConfigurationManager.AppSettings["MidServiceURL"].TryString(); static void Main() { try { StartAllConfiguredServices(); Console.WriteLine("WCF 服务正在运行......"); Console.WriteLine("输入回车键 <ENTER> 退出WCF服务"); Console.ReadLine(); } catch (CommunicationException ce) { Console.WriteLine("Error : {0}", ce.Message); } } private static void StartAllConfiguredServices() { serviceURLs = midServiceURL.Split(','); Configuration conf = ConfigurationManager.OpenExeConfiguration(Assembly.GetEntryAssembly().Location); ServiceModelSectionGroup svcmod = (ServiceModelSectionGroup)conf.GetSectionGroup("system.serviceModel"); foreach (ServiceElement el in svcmod.Services.Services) { string eName = el.Name; string typeName = eName.Substring(0, eName.LastIndexOf('.')); Type svcType = Type.GetType(el.Name + "," + typeName); if (svcType == null) throw new Exception("Invalid Service Type " + el.Name + " in configuration file."); OpenHost(svcType); } } private static void OpenHost(Type t) { List<Uri> uris = new List<Uri>(); foreach (string url in serviceURLs) { Uri uri = new Uri(string.Format("{0}{1}.svc", url, t.Name)); uris.Add(uri); } ServiceHost host = new ServiceHost(t, uris.ToArray()); for (int j = 0; j < host.Description.Endpoints.Count; j++) { CorsEnabledServiceHost.AddPreflightOperations(host.Description.Endpoints[j]); host.Description.Endpoints[j].Behaviors.Add(new EnableCorsEndpointBehavior()); } host.Open(); hostList.Add(host); } }}
[app.config]
<?xml version="1.0"?><configuration> <appSettings> <!--中间层服务地址,注意,必须以/结尾--> <add key="MidServiceURL" value="http://192.168.5.168:8123/" /> </appSettings> <system.serviceModel> <!--配置服务节点Start--> <services> <service name="WCFLibray.Service1" behaviorConfiguration="MetadataBehavior"> <!--定义endpoint,指定地址(address),绑定(binding)以及协定(contract)--> <!-- 属性说明: address - 服务端地址,相对于baseAddress的相对地址,如果为空则为baseAddress,也可以设定为绝对地址 binding - 绑定协议,系统的某一协议,basicHttpBinding,mexHttpBinding,wsHttpBinding等 contract - 协议完全限定名(名称空间.类型名) name - 客户端代理类的构造方法中的endpointConfigurationName对应到这个name bindingConfiguration - 指定客户端binding的具体设置,指向<bindings>元素下同类型binding的name --> <endpoint binding="webHttpBinding" bindingConfiguration="webHttpBindingConfig" behaviorConfiguration="AspNetAjaxBehavior" contract="WCFLibray.IService1" /> </service> </services> <!--配置服务节点End--> <!--配置行为节点Start--> <behaviors> <serviceBehaviors> <behavior name="MetadataBehavior"> <serviceThrottling maxConcurrentCalls="100000" maxConcurrentInstances="100000" maxConcurrentSessions="100000" /> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 --> <serviceMetadata httpGetEnabled="True" /> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息--> <serviceDebug includeExceptionDetailInFaults="True" /> <!--maxItemsInObjectGraph指定要序列化或反序列化的最大项数--> <dataContractSerializer maxItemsInObjectGraph="2147483647" /> </behavior> </serviceBehaviors> <!--ajax跨域配置Start--> <endpointBehaviors> <behavior name="AspNetAjaxBehavior"> <webHttp /> </behavior> </endpointBehaviors> <!--ajax跨域配置End--> </behaviors> <!--配置行为节点End--> <!--配置绑定节点Start--> <bindings> <webHttpBinding> <binding name="webHttpBindingConfig" maxBufferPoolSize="524288000" maxReceivedMessageSize="65536000" crossDomainScriptAccessEnabled="true" closeTimeout="01:10:00" openTimeout="01:10:00" sendTimeout="01:10:00" receiveTimeout="01:10:00"> <readerQuotas maxStringContentLength="65536000" /> </binding> </webHttpBinding> </bindings> <!--配置绑定节点End--> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> </modules> </system.webServer> </configuration>
(5)运行宿主程序[在客户端调用时要先运行宿主程序],如下图所示: