Topics.Core Service Hosting

Many commercial applications are collections of web api, web sites and windows services. While websites and web api are typically hosted inside a webserver such as IIS, windows services themselves are executable programs that run in the background. When creating windows services with Visual Studio (VS), a project template is supplied that provides the basic requirements. The class that inherits from ServiceBase provides the ability for the service executable to be installed as a windows service and be controlled by the Service Control Manager (SCM). When the service is started by the SCM, the service base calls a OnStart() method and when the service is shutting down it calls a OnStop() method. Now what actually gets started and stopped is your responsibility to build. Topics.Core provides a standard windows service implementation that uses a Dependency Injection (DI) container to allow you to inject one or more “application services”. These application services are contained in class library assemblies and implement the Topics.Core.Host.IService interface.

public interface IService 
    {
        [CLSCompliant(false)]
        bool Initialize(IApplicationContext _ctx = null);
        [CLSCompliant(false)]
        bool Start(IApplicationContext _ctx = null);
        bool Stop();
        bool AutoStart { get; set; }
    }

By abstracting out the windows service interface, we can now host application services inside other executable programs, not just windows services. For example, Topics.Core also provides a standard Windows Console application that can host application services. This is very handy in development as console applications can be launched directly from VS with the debugger automatically attached, where windows services require one to attach to the running service executable. This makes debugging initialization and startup code very challenging at best.

The Topics.Core.Host.ServiceContainer.SimpleContainer class makes this possible by exposing a Dictionary<string, IService> property into which the DI Container can configure what services will be hosted. Here’s a example of Spring.Net configuration metadata found in:

Topics.Core->Development->Examples->Messaging->FirstService->Config->Services.xml

<objects xmlns="http://www.springframework.net"
         xmlns:nms="http://www.springframework.net/nms">

  <object id="ServiceContainer" type="Topics.Core.Host.ServiceContainer.SimpleContainer, Topics.Core">
    <property name="Services">
      <dictionary key-type="string" value-type="Topics.Core.Host.IService">
        <entry key="FirstService" value-ref="FirstService" />
      </dictionary>
    </property>
  </object>

  <object id="FirstService" type="FirstService.Service, FirstService">
  </object>
  
</objects>

The Services property can contain any number of services that will be managed by the service host. With the SimpleContainer, the services will be started and stopped in the order in which they appear in the Services dictionary. The Topics.Core ConsoleHost and WindowsServiceHost takes care of loading the DI Container and other initialization. In VS, the most common way to use the provided ConsoleHost and ServiceHost is to either import the Topics.Core.Host nuget package or add an assembly reference. See the project:

Topics.Core->Development->Examples->Messaging->FirstService

This is a class library project with a reference to Topics.Core and Topics.Core.Host assemblies as well as Spring.Core. Additional config files have been added for Topics.Core.Host.ConsoleHost.exe.config as well as Topics.Core.Host.WindowsServiceHost.exe.config. These are the config files that are loaded when either of the two service containers are started. If you end up modifying the name of the service host assembly, just remember to modify the names of the exe.config files to match. Inside the Topics.Core.Host.ConsoleHost.exe.config file, you see the spring config section which references the Services.xml configuration meta data listed above.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
    </sectionGroup>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
      <section name="typeAliases" type="Spring.Context.Support.TypeAliasesSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <!-- Main service config file -->
      <resource uri="~/Config/Services.xml" />
    </context>
  </spring>
  
  <common>
    <logging>
      <factoryAdapter type="Common.Logging.Log4Net.Universal.Log4NetFactoryAdapter, Common.Logging.Log4Net.Universal" />
    </logging>
  </common>

  <log4net>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file type="log4net.Util.PatternString" value="c:\Logs\Topics.Messaging.InProcess.UnitTests\UnitTests.txt" />
      <appendToFile value="true" />
      <rollingStyle value="Composite" />
      <datePattern value="yyyyMMdd" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="1MB" />
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Common.Logging.Core" publicKeyToken="af08829b84f0328e" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.4.1.0" newVersion="3.4.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Common.Logging" publicKeyToken="af08829b84f0328e" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.4.1.0" newVersion="3.4.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.8.0" newVersion="2.0.8.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>

</configuration>

To debug your service, just edit the properties of the service project and specify the Topics.Core.Host.ConsoleHost.exe as the external program to start under the Debug tab.

The service can be deployed to run as either a console application or a windows service. To install the windows service, you can use Powershell or the SC command as in:

sc.exe create FirstService binpath=D:\Topics.Core\Development\Examples\Services\FirstService\bin\Debug\Topics.Core.Host.ConsoleHost.exe

What we’ve covered in this article:

  • Topics.Core.Host contains two standard service host applications
  • Application services can be injected into a service host using Spring.Net configuration metadata
  • Service hosts can contain one or more application services

 

 

0 votes

BECOME A COMMENTERS?

You must be logged in to post a comment.