The Perfect Service - Part 1
page 2 of 2
by J. Ambrose Little
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 18345/ 28

Implementing ServiceBroker.IService

ServiceBroker is an assembly that comes with the .NET Service Manager. It provides the engine for the service manager as well as the types needed for the interaction between the service manager and your managed services. The IService interface is detailed in Listing 1.

Listing 1 IService.cs

using System;

 

namespace ServiceBroker

{

/// <summary>

/// Interface that all services using the

/// ServiceBroker must implement on the type specified

/// by the <see cref="ServiceEntryPointAttribute.TypeName"/>.

/// </summary>

public interface IService

{

/// <summary>

/// Starts the service functionality.

/// </summary>

void StartService();

/// <summary>

/// Stops the service functionality.

/// </summary>

void StopService();

}

}

As you can see, the managed service interface is very simple -- it requires only a start and a stop method. This, of course, corresponds to the typical operations you have available from the Services MMC snap-in. Note if your Windows service is "restarted," it will simply call the StopService method and the StartService method, so this is really all we need.

For the purposes of this example, I have implemented a very simple managed service that only serves to illustrate the implementation. It is called SampleService, and the type in that assembly that implements IService is listed next.

Listing 2 Test.cs

using System;

 

namespace SampleService

{

/// <summary>

/// Summary description for Test.

/// </summary>

public class Test : ServiceBroker.IService

{

#region IService Members

 

public void StartService()

{

ServiceBroker.Logger.WriteToLog(AppSettings["StartText"],

System.Diagnostics.EventLogEntryType.Information);

}

 

public void StopService()

{

ServiceBroker.Logger.WriteToLog(AppSettings["StopText"],

System.Diagnostics.EventLogEntryType.Information);

}

 

#endregion

}

}

You will note the reference to an AppSettings member-this has been omitted in this listing for simplicity's sake but will be covered later. What's important here is that we've simply specified that we're implementing the IService interface and then have provided some very basic functionality to be executed in each method-writing a message to the event log.

Of course, if you were implementing a service, it would likely involve the use of one or more System.Threading.Timer instances to schedule some activity to occur on a regular basis, or maybe you'd implement a System.IO.FileSystemWatcher to watch for file system activity. I don't need to tell you what you'd do because if you're reading this, you probably already have a specific need for Windows service functionality and can easily infer from the example how you can use this to your advantage.

Applying the Attribute

Obviously, I could have chosen not to apply an attribute and simply used reflection to look through the types in your assembly for the first one that implemented the ServiceBroker.IService interface. Apart from potentially taking a long time to locate such a type, I decided to go with a custom attribute to also enable you to specify a friendly display name for your managed service. This is used to log items pertaining to that service.

The good news is that it is very simple to apply a custom attribute to your assembly. If you are using Visual Studio, an Assembly.cs (or .vb for VB) file is generated with the code library projects by default, and this is where you are expected to place your assembly-level attributes. You can actually put your assembly-level attributes in any code file as long as they are after any using statements and before any type or namespace declarations. In this sample, the Assembly.cs file is used.

Listing 3 Assembly.cs

[assembly: ServiceBroker.ServiceEntryPoint("SampleService", "SampleService.Test")]

[assembly: System.Reflection.AssemblyVersion("1.0.*")]

 

The typical Assembly.cs file is generated with a ton of comments and a handful of standard assembly-level attributes that address title, description, company, copyright, etc. I've removed everything, including the using (Imports in VB) statements, except for the AssemblyVersionAttribute because I think if you don't set anything else, you should at least set your assembly version.

I also, as I'm sure you've noticed, applied my custom attribute from the ServiceBroker assembly. The constructor for that attribute takes two arguments-the display name of your managed service, and the full type name of the type that you want the service manager to use to start and stop your service. Clearly, the type specified here should implement the IService interface, such as the Test type from Listing 2; hence, we are specifying that the SampleService.Test type be used for our purposes.

If you don't need any configurable applications settings, at this point, you're done. All you'd need to do is compile this and drop it into the .NET Service Manager's process directory (i.e., where the .exe is located), and if it is running, it will automatically load up your assembly, find and instantiate the service entry point type, and call that type's implementation of the IService.StartService method.

If you needed to update, you'd simply make your changes, compile, and drop the new version out in that same directory. If the service manager is running, it will detect the new version, stop and unload the old version, and load and start the new version. Similarly, you can get it to stop and unload by just moving or deleting the DLL file from the same directory.

Extending Flexibility with DLL Config Files

Thanks to the Config class in the ServiceBroker, adding a .config file to your managed service is easy. Unfortunately, Visual Studio is not set up to handle generating the assemblyName.dll.config file using the app.config file like it does for Windows forms and services applications, so there is a bit of extra work to copy the config file around with your assembly. This just means you'll want to copy out your config file before or with your assembly, so it will be there when the assembly is loaded by the service manager.

In any case, the format of the config file is just like your standard app.config file, except that the only things recognized by the ServiceBroker config handler are app settings. You can see the config file being used with the SampleService assembly in the next listing.

Listing 4 SampleService.dll.config

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<appSettings>

<add key="StartText"

value="This is where you would implement logic to start your service." />

<add key="StopText"

value="This is where you would implement logic to stop your service." />

</appSettings>

</configuration>

To use the config file, we just need to add a few lines of code to our test class (the ones I excluded in the first listing); these are now visible with the entire Test class in Listing 5.

Listing 5 The Complete Test.cs

using System;

 

namespace SampleService

{

/// <summary>

/// Summary description for Test.

/// </summary>

public class Test : ServiceBroker.IService

{

internal static ServiceBroker.Config AppSettings =

new ServiceBroker.Config();

 

#region IService Members

 

public void StartService()

{

ServiceBroker.Logger.WriteToLog(AppSettings["StartText"],

System.Diagnostics.EventLogEntryType.Information);

}

 

public void StopService()

{

ServiceBroker.Logger.WriteToLog(AppSettings["StopText"],

System.Diagnostics.EventLogEntryType.Information);

}

 

#endregion

}

}

So all you add is one line to declare a ServiceBroker.Config static (Shared in VB) identifier. You have declared it as internal (Friend in VB) because you only want it to be visible to types within this assembly. Now you can begin accessing your app settings in code by using the Test.AppSettings field, which has a string indexer, much like System.Configuration.ConfigurationSettings.AppSettings does, so the usage is very similar to standard app settings access. You could in fact get even closer by creating a ConfigurationSettings class and putting the AppSettings member there, but then you might be too close for comfort because it could easily conflict with the same class in the System.Configuration namespace, i.e., if you had a using (Imports in VB) statement for that namespace.

And again, that's all there is to it. You now have a XCOPY-deployable, hot-updatable, easily-configurable Windows service. I should also note that unlike other app.config files I could mention, ServiceBroker will monitor the config file for changes and update the cached settings (they are loaded into memory for faster access throughout the application) when the file is updated, and it will do so without restarting the managed service.

Please note that the installer installs the .NET Service Manager to run under the Local System account. I would recommend that you consider changing this to a less-privileged account if that will work for you. This is also a limitation of the .NET Service Manager--all of the managed services will run under the permissions of the Windows service identity. If you need a more fine-grained control, you will still need to develop separate Windows services.

All you need to do to get started creating and deploying your managed services is to download the installer, run it, start the .NET Service Manager Windows service, and you'll be on your way. The next article in this series will cover just how the service manager makes all of this possible.

Related Resources

Creating an Extensible Windows Service - by Rob Chartier
Build a Plug 'n' Play XML Windows Service - by Dan Wahlin
Adding and Removing References - MSDN Library
Implementing Existing Interfaces - MSDN Library
Applying Attributes - MSDN Library
<appSettings> Element - MSDN Library

 


View Entire Article

User Comments

Title: re: Could not load type 'SampleService.Test'   
Name: tom
Date: 2009-04-10 6:49:04 PM
Comment:
I was getting the same error as the other 2 posters and
found a fix that 'works on my machine':

SampleService project - AssemblyInfo.cs

the fix:
[assembly: ServiceBroker.ServiceEntryPoint("SampleService", typeof(SampleService.Test))]

changed:
"SampleService.Test"
to:
typeof(SampleService.Test)

and it worked.
Title: Mr   
Name: Andre
Date: 2009-04-03 7:39:47 AM
Comment:
Tried exactly the same with the same tools and got the Same Error, but with the following stack trace:
StackTrace Information
*********************************************

Server stack trace:
at System.RuntimeTypeHandle._GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, Boolean loadTypeFromPartialName)
at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)
at System.RuntimeType.PrivateGetType(String typeName, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)
at System.Type.GetType(String typeName, Boolean throwOnError)
at ServiceBroker.ServiceEntryPointAttribute..ctor(String serviceName, String entryPointTypeName)
at System.Reflection.CustomAttribute._CreateCaObject(Void* pModule, Void* pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs)
at System.Reflection.CustomAttribute.CreateCaObject(Module module, RuntimeMethodHandle ctor, IntPtr& blob, IntPtr blobEnd, Int32& namedArgs)
at System.Reflection.CustomAttribute.GetCustomAttributes(Module decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes)
at System.Reflection.CustomAttribute.GetCustomAttributes(Assembly assembly, RuntimeType caType)
at System.Reflection.Assembly.GetCustomAttributes(Type attributeType, Boolean inherit)
at ServiceBroker.RemoteServiceHandler.LoadService(String assemblyName)
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
Title: Could not load type 'SampleService.Test'   
Name: Cameron
Date: 2008-11-10 11:03:29 PM
Comment:
Hi.

I followed the article and compiled the source code with no errors, installed the .net Service Manager successfully.
My projects are pointing to framework 2.0 and are compiled using C# 2008.

When I drop in the SampleService.dll and SampleService.dll.config where the service is running the Application event log raises the following (plus stack trace not included).

Exception Type: System.TypeLoadException
Message: Could not load type 'SampleService.Test' from assembly 'ServiceBroker, Version=1.0.604.0, Culture=neutral, PublicKeyToken=null'.

Any ideas?






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-23 12:22:52 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search