首页 > 代码库 > Topshelf + ServiceModelEx + Nlog 从头构建WCF

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,这样的文章园子里面有很多,这里就不说明了。

Topshelf + ServiceModelEx + Nlog 从头构建WCF