Topshelf + ServiceModelEx + Nlog 从头构建WCF
前言
Topshelf可以很方便的构建windows service,而且在本地开发时也可以构建Console宿主,因此很方便WCF的开发。
ServiceModelEx则提供了很多便利的方法来配置wcf的behavior。
Nlog是.NET中记录日志类库和log4net提供的功能一样。
构建solution
好了,现在开始从头构建解决方案:
以上的Host和Client为Console,Contract和Service为class librariy。
构建Contract
Contract里面定义的是wcf对外提供之服务,这里将其分为一下三个部分:
- 请求Request
- 回复Response
- 行为Action
其中的Request和Response可以看作为Data Transfer Object也就是我们说的DTO,这里就将其放置与DTO的文件夹下面。
现在构建Request:
[DataContract] public class JulyLuoRequest { [DataMember] public string Greeting { get; set; } [DataMember] public string Name { get; set; } }
构建Response:
[DataContract] public class JulyLuoResponse { [DataMember] public string Greeting { get; set; } [DataMember] public string ClientName { get; set; } [DataMember] public string ServiceName { get; set; } }
最后构建我们的接口:
[ServiceContract] public interface IJulyLuoIntroduce {
[OperationContract] JulyLuoResponse Introduce(JulyLuoRequest request); }
整个的Contract工程如下:
构建Service
Service是最终实现接口的地方,因此其需要引用Contract project,这里就简单的实现:
public class JulyLuoIntroduce : IJulyLuoIntroduce { public JulyLuoResponse Introduce(JulyLuoRequest request) { return new JulyLuoResponse() { Greeting = request.Greeting, ClientName = request.Name, ServiceName = "JulyLuo" }; } }
构建Host
Host需要引用以上的Contract和Service工程。
Host这里我们就需要TopShelf和Nlog的第三方类库,可以在NuGet上获取:
最后的引用如下:
Topshelf的最新版本网上提到不支持.NET 4.0, .NET4.5,因此用Nuget的时候可能不成功,解决办法就是使用低版本的Topshelf,或者从Topshelf官网下载对应的dll直接引用。
ServiceModeEx的类库在Nuget上获取不到,大家可以在网上下载自己再添加引用。
现在构建一个WcfHost类封装wcf提供的服务:
public class WcfHost { private ServiceHost<JulyLuoIntroduce> _service; internal WcfHost() { _service = new ServiceHost<JulyLuoIntroduce>(new Uri[] { }); } public void Start() { _service.Open(); } public void Stop() { try { if (_service != null) { if (_service.State == CommunicationState.Opened) { _service.Close(); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
最后通过TopShelf 配置Nlog,并宿主刚刚定义的WcfHost:
class Program { private static Logger logger = LogManager.GetLogger("JulyLuo.Host"); public static readonly LogFactory Instance = new LogFactory(new XmlLoggingConfiguration(GetNLogConfigFilePath())); private static string GetNLogConfigFilePath() { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NLog.config"); } static void Main(string[] args) { try { const string name = "JulyLuo-Service"; const string description = "JulyLuo-Introduce"; var host = HostFactory.New(configuration => { configuration.UseNLog(Instance); configuration.Service<WcfHost>(callback => { callback.ConstructUsing(s => new WcfHost()); callback.WhenStarted(service => service.Start()); callback.WhenStopped(service => service.Stop()); }); configuration.SetDisplayName(name); configuration.SetServiceName(name); configuration.SetDescription(description); configuration.RunAsLocalService(); }); host.Run(); } catch (Exception ex) { logger.Error("Pdf Generator Service fatal exception. " + ex.Message); } } }
配置Config
现在要配置Nlog的配置文件,以及Host控制台的wcf配置。
Nlog这里需要配置两个target,一个是作为Console时Nlog写入Console,一个是作为windows service是Nlog写入本地的文件
<target xsi:type="Console" layout="${longdate}[${level}]${message}" name="Console"/> <target name="TopShelfCSV" xsi:type="File" fileName="${basedir}/Logs/TopShelf-${shortdate}.csv" archiveFileName="${basedir}/Archive/TopShelf-{#}.csv" archiveNumbering="Date" archiveEvery="Day" maxArchiveFiles="7" archiveDateFormat="yyyy-MM-dd" > <layout xsi:type="CsvLayout"> <column name="time" layout="${longdate}" /> <column name="message" layout="${message} ${exception:format=tostring}" /> <column name="logger" layout="${logger}"/> <column name="level" layout="${level}"/> </layout> </target>
Nlog的rule配置在Console时配置如下:
<logger name="*" minlevel="Debug" writeTo="Console" />
在widows service的配置如下:
<logger name="*" minlevel="Debug" writeTo="TopShelfCSV" />
现在在Host控制台下添加app.config文件,并配置wcf service节点:
<system.serviceModel> <services> <service name="JulyLuo.Service.JulyLuoIntroduce" behaviorConfiguration="mexServiceBehavior"> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:50129/PdfGenerator" /> </baseAddresses> </host> <endpoint address="" binding="netTcpBinding" contract="JulyLuo.Contract.IJulyLuoIntroduce" /> <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mexServiceBehavior"> <serviceMetadata /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
这里的wcf用的是Tcp的绑定,要主要就是service中name和 endpoint中的contract,其对应的就是以上创建的service和contract的名称。
一切设置完毕之后,设置Host为启动项目,现在可以直接运行,因为传入的参数为空,topshelf将设置为Console,界面如下:
构建Client端
这里的Client端只需要应用Contract工程即可,引用添加之后新创建一个类封装调用wcf:
public class WcfProxy<TContract> : IDisposable where TContract : class { public TContract Service { get; private set; } public WcfProxy() { try { var factory = new ChannelFactory<TContract>(typeof(TContract).Name + "_Endpoint"); factory.Open(); Service = factory.CreateChannel(); } catch (Exception ex) { Console.WriteLine("Could not create proxy: {0}", ex.Message); Service = null; } } public void Dispose() { if (Service != null) { var internalProxy = Service as ICommunicationObject; try { if (internalProxy != null) { if (internalProxy.State != CommunicationState.Closed && internalProxy.State != CommunicationState.Faulted) internalProxy.Close(); } } catch (Exception ex) { Console.WriteLine("Could not close proxy: {0}", ex.Message); try { if (internalProxy != null) internalProxy.Abort(); } catch (Exception exInternal) { Console.WriteLine("Could not abort proxy: {0}", exInternal.Message); } } if (internalProxy is IDisposable) { try { if (internalProxy.State != CommunicationState.Faulted) (internalProxy as IDisposable).Dispose(); } catch (Exception ex) { Console.WriteLine("Could not dispose proxy: ", ex.Message); } } } } }
因为是调用wcf,这里的Client端也需要添加app.config并设置如下:
<system.serviceModel> <client> <endpoint address="net.tcp://localhost:50129/Introduce" binding="netTcpBinding" contract="JulyLuo.Contract.IJulyLuoIntroduce" name="IJulyLuoIntroduce_Endpoint"> </endpoint> </client> </system.serviceModel>
所有的准备完毕之后,我们就可以在Client端开始编写代码调用wcf:
Console.WriteLine("Press enter to send the introduction request"); Console.ReadLine(); using (var proxy = new WcfProxy<IJulyLuoIntroduce>()) { Console.ForegroundColor = ConsoleColor.Blue; var request = new JulyLuo.Contract.DTO.JulyLuoRequest { Greeting = "Hello", Name = "world" }; Console.WriteLine("Sending: {0}", request); Console.ForegroundColor = ConsoleColor.Green; var response = proxy.Service.Introduce(request); Console.WriteLine("Received: {0} {1} {2}", response.Greeting, response.ClientName, response.ServiceName); } Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Press enter to exit"); Console.ReadLine();
然后设置整个solution将client和host 工程都设置为启动项目:
最后的运行结果如下:
总结
通过以上的步骤,我们成功的整合了几个类库来开发wcf,topshelf还可以宿主为windows service,这样的文章园子里面有很多,这里就不说明了。