I'm in the process of designing the architecture of an application I’m planning on building and need some advice on the best way to implement a specific windows service component described below. I'll be building the service using .net 4.0 so I can take advantage of the new parallel and task APIs, I’ve also looked at using the MSMQ service however I’m not sure this is appropriate for what I hope to achieve.
The simplest way of explaining my use case is that users can create a number of reminders of different types for a task that they need to complete, which they create using a web-based application built in ASP.NET MVC 2. These reminders can be of various types for example email and SMS, which of cause must be sent at the specified due time. The reminders can be changed up until the point they have been sent to the user, paused and cancelled all together, which I guess makes a queuing based service such as MSMQ not appropriate?
I plan to host a windows service that will periodically (unless there is a more appropriate way?) check to see if there are any reminders due, determine their type and pass them to the specific component to deal with them and send them off. If an exception occurs the reminder will be queued up at a set interval and tried again, this will continue to happen with the interval increasing until they meet a set threshold at which point they are discarded and logged as a failure. To add a final layer of complexity to the service, I hope to specify in a configuration file the concrete implementation of each type (This means I can say change the SMS service due to cost or whatever), which are loaded at service start-up dynamically. Any reminders of an unknown or unavailable type will of cause automatically fail and be logged as before.
Once a reminder has been successfully sent it simply discards it, however with the SMS gateway I’m planning to use, it requires me to call its API to find out whether the message was successfully delivered or not, which means an additional timer is required at a set interval to check for this. It would also be nice to be able to add additional reminder type services that conform to a unified interface at service start-up without the need to change its code.
Finally, I don't know whether this should be posted as a separate question or not but I wondered would it be possible to say build a console application that could be started/stopped at anytime and when running can see what the windows service is currently doing?
This is my first ever question on Stackoverflow, even though I’ve been using the community for a while so I apologise if I’ve done some incorrectly.
Thanks in advance,
Wayne
For the second part of your question, I have been thinking about this and here is a class I put together that helps to create a service which can be run both as a Console application as well as a Windows Service. This is fresh off the press, so there might be some issues to resolve, and some refactoring required esp. around the reflection code.
NB: You should set the Service project Output type to Console Application, this will still work fine as a normal service.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
namespace DotNetWarrior.ServiceProcess
{
public class ServiceManager
{
private List<ServiceBase> _services = new List<ServiceBase>();
public void RegisterService(ServiceBase service)
{
if (service == null) throw new ArgumentNullException("service");
_services.Add(service);
}
public void Start(string[] args)
{
if (Environment.UserInteractive)
{
foreach (ServiceBase service in _services)
{
Start(service, args);
}
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
Thread.Sleep(Timeout.Infinite);
}
else
{
ServiceBase.Run(_services.ToArray());
}
}
public void Stop()
{
foreach (ServiceBase service in _services)
{
Stop(service);
}
}
private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
Stop();
Environment.Exit(0);
}
private void Start(ServiceBase service, string[] args)
{
try
{
Type serviceType = typeof(ServiceBase);
MethodInfo onStartMethod = serviceType.GetMethod(
"OnStart",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new Type[] { typeof(string[]) },
null);
if (onStartMethod == null)
{
throw new Exception("Could not locate OnStart");
}
Console.WriteLine("Starting Service: {0}", service.ServiceName);
onStartMethod.Invoke(service, new object[] { args });
Console.WriteLine("Started Service: {0}", service.ServiceName);
}
catch (Exception ex)
{
Console.WriteLine("Start Service Failed: {0} - {1}", service.ServiceName, ex.Message);
}
}
private void Stop(ServiceBase service)
{
try
{
Type serviceType = typeof(ServiceBase);
MethodInfo onStopMethod = serviceType.GetMethod(
"OnStop",
BindingFlags.NonPublic | BindingFlags.Instance);
if (onStopMethod == null)
{
throw new Exception("Could not locate OnStart");
}
Console.WriteLine("Stoping Service: {0}", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped Service: {0}", service.ServiceName);
}
catch (Exception ex)
{
Console.WriteLine("Stop Service Failed: {0} - {1}", service.ServiceName, ex.Message);
}
}
}
}
To use this, you can rip the standard code out of the Main entry point of the service and replace it with the following.
static void Main(string[] args)
{
ServiceManager services = new ServiceManager();
services.RegisterService(new Service1());
services.Start(args);
}
The services.Start() method will detect that the service is being run as an interactive application and manually invoke the OnStart method of all the registered services, once started the main thread goes to sleep. To stop the services, just press 'Ctrl+C` which will result in the Services being stopped by calling the OnStop method of the service.
Of course is the application is run as a Service by the SCM then everyhing works as a normal service. The only caveat is that the service should not require to be run with 'Allow service to interact with desktop' since this will make the service run in interactively even though it is run as a service. This can be worked around if required, but hey I only just wrote the code.
Monitoring and Starting/Stopping a Service
From the command line you can use the NET.EXE to Start/Stop a service
Start a service
net start <service name>
Stop a service
net stop <service name>
For managing a service from .NET you can use System.ServiceProcess.ServiceController
// Stop a service
System.ServiceProcess.ServiceController sc = new
System.ServiceProcess.ServiceController("<service name>");
sc.Stop();
For general communication with the service other than what is provided through ServiceController I would suggest that you host a WCF service as part of your service, which you can then use to communicate with the service to query internal details specific to your service.
Handling the Scheduling
To be honest, I was hesitant to answer this aspect of the question since there are so many approaches each with there Pros/Cons. So I will just give some high level options for you to consider. You have probably thought this through already, but here are a few things off the top of my head
If you are using SQL Server to store the notifications.
Have an SP that you can call to retrieve the reminders that are due, then process the result to raise the reminders approriately.
With this SP you have some options
Call the SP periodically from your service and process the reminders
Have a SQL Job that periodically runs the SP and adds a reminder to a Service Broker Queue. Your Service can then subscribe to the Queue and process reminders as they appear on the Queue. The advantage of this approach is that you can scale out with multiple servers processing the reminder notification generation without any special coding, just add another server that runs your service and the messages will automatically be distributed between the two servers.
If you are not using SQL Server to store the reminders
You can still use a similar approach as for SQL Server. Of course the Windows Service can query the data store using what ever is approapriate and process the reminders. Getting this to scale is a little harder since you will need to ensure that multiple servers do not process the same reminder etc. but not a train smash.
I think that covers the gist of it, everything else is some variation on the above. Ultimately your decision would depend on the target volumes, reliability requirements etc..
Related
I am new at messaging architectures, so I might be going at this the wrong way. But I wanted to introduce NServiceBus slowly in my team by solving a tiny problem.
Appointments in Agenda's have states. Two users could be looking at the same appointment in the same agenda, in the same application. They start this application via a Remote session on a central server. So if user 1 updates the state of the appointment, I'd like user 2 to see the new state 'real time'.
To simulate this or make a proof of concept if you will, I made a new console application. Via NuGet I got both NServiceBus and NServiceBus.Host, because as I understood from the documentation I need both. And I know in production code it is not recommended to put everything in the same assembly, but the publisher and subscriber will most likely end up in the same assembly though...
In class Program method Main I wrote the following code:
BusConfiguration configuration = new BusConfiguration();
configuration.UsePersistence<InMemoryPersistence>();
configuration.UseSerialization<XmlSerializer>();
configuration.UseTransport<MsmqTransport>();
configuration.TimeToWaitBeforeTriggeringCriticalErrorOnTimeoutOutages(new TimeSpan(1, 0, 0));
ConventionsBuilder conventions = configuration.Conventions();
conventions.DefiningEventsAs(t => t.Namespace != null
&& t.Namespace.Contains("Events"));
using (IStartableBus bus = Bus.Create(configuration))
{
bus.Start();
Console.WriteLine("Press key");
Console.ReadKey();
bus.Publish<Events.AppointmentStateChanged>(a =>
{
a.AppointmentID = 1;
a.NewState = "New state";
});
Console.WriteLine("Event published.");
Console.ReadKey();
}
In class EndPointConfig method Customize I added:
configuration.UsePersistence<InMemoryPersistence>();
configuration.UseSerialization<XmlSerializer>();
configuration.UseTransport<MsmqTransport>();
ConventionsBuilder conventions = configuration.Conventions();
conventions.DefiningEventsAs(t => t.Namespace != null
&& t.Namespace.Contains("Events"));
AppointmentStateChanged is a simple class in the Events folder like so:
public class AppointmentStateChanged: IEvent {
public int AppointmentID { get; set; }
public string NewState { get; set; }
}
AppointmentStateChangedHandler is the event handler:
public class AppointmentStateChangedHandler : IHandleMessages<Events.AppointmentStateChanged> {
public void Handle(Events.AppointmentStateChanged message) {
Console.WriteLine("AppointmentID: {0}, changed to state: {1}",
message.AppointmentID,
message.NewState);
}
}
If I start up one console app everything works fine. I see the handler handle the event. But if I try to start up a second console app it crashes with: System.Messaging.MessageQueueException (Timeout for the requested operation has expired). So i must be doing something wrong and makes me second guess that I don't understand something on a higher level. Could anyone point me in the right direction please?
Update
Everthing is in the namespace AgendaUpdates, except for the event class which is in the AgendaUpdates.Events namespace.
Update 2
Steps taken:
Copied AgendaUpdates solution (to AgendaUpdates2 folder)
In the copy I changed MessageEndpointMappings in App.Config the EndPoint attribute to "AgendaUpdates2"
I got MSMQ exception: "the queue does not exist or you do not have sufficient permissions to perform the operation"
In the copy I added this line of code to EndPointConfig: configuration.EndpointName("AgendaUpdates2");
I got MSMQ exception: "the queue does not exist or you do not have sufficient permissions to perform the operation"
In the copy I added this line of code to the Main methodin the Program class:
configuration.EndpointName("AgendaUpdates2");
Got original exception again after pressing key.
--> I tested it by starting 2 visual studio's with the original and the copied solution. And then start both console apps in the IDE.
I'm not exactly sure why you are getting that specific exception, but I can explain why what you are trying to do fails. The problem is not having publisher and subscriber in the same application (this is possible and can be useful); the problem is that you are running two instances of the same application on the same machine.
NServiceBus relies on queuing technology (MSMQ in your case), and for everything to work properly each application needs to have its own unique queue. When you fire up two identical instances, both are trying to share the same queue.
There are a few things you can tinker with to get your scenario to work and to better understand how the queuing works:
Change the EndPointName of your second instance
Run the second instance on a separate machine
Separate the publisher and subscriber into separate processes
Regardless of which way you go, you will need to adjust your MessageEndpointMappings (on the consumer/subscriber) to reflect where the host/publisher queue lives (the "owner" of the message type):
http://docs.particular.net/nservicebus/messaging/message-owner#configuring-endpoint-mapping
Edit based on your updates
I know this is a test setup/proof of concept, but it's still useful to think of these two deployments (of the same code) as publisher/host and subscriber/client. So let's call the original the host and the copy the client. I assume you don't want to have each subscribe to the other (at least for this basic test).
Also, make sure you are running both IDEs as Administrator on your machine. I'm not sure if this is interfering or not.
In the copy I changed MessageEndpointMappings in App.Config the EndPoint attribute to "AgendaUpdates2" I got MSMQ exception: "the queue does not exist or you do not have sufficient permissions to perform the operation"
Since the copy is the client, you want to point its mapping to the host. So this should be "AgendaUpdates" (omit the "2").
In the copy I added this line of code to EndPointConfig: configuration.EndpointName("AgendaUpdates2"); I got MSMQ exception: "the queue does not exist or you do not have sufficient permissions to perform the operation"
In the copy I added this line of code to the Main methodin the Program class: configuration.EndpointName("AgendaUpdates2"); Got original exception again after pressing key
I did not originally notice this, but you don't need to configure the endpoint twice. I believe your EndPointConfig is not getting called, as it is only used when hosting via the NSB host executable. You can likely just delete this class.
This otherwise sounds reasonable, but remember that your copy should not be publishing if its the subscriber, so don't press any keys after it starts (only press keys in the original).
If you want to publisher to also be the receiver of the message, you want to specify this in configuration.
This is clearly explained in this article, where the solution to your problem is completely at the end of the article.
For a project I have programmed a wcf service library. It can be hosted in IIS and in a self-hosted service.
For all external systems that are connected, I have provided Mock implementations which give some generic data, so such the service (library) keeps running and doing work. It is a classic automaton / finite-state machine.
While bootstrapping, all data sources are connected. In testing mode, the mock implementations are connected. So when I run tests, the service library is "started" from a self-hosted service, not IIS and the the state machine keeps running and processing data packages.
Is there any way to get some kind of "test coverage" from such a run.
I would really appreciate if I could tell which code paths are hit by the example data I provide from the mock objects. And then provide more testdata to get a higher coverage.
If I could do this without having to provide "lots of extra" testing code, it would be great. I think a lot of cases are already covered from the data provided from the mock objects. But right now I have no starting point for that.
Here are some code examples to give a more clear picture of what is meant. Code is strongly simplified of course.
In a very simple console application to start the service (self hosted version)
static void Main(string[] args)
{
using (var host = new ServiceHost(typeof(MyServiceLib.Service.MyServiceLib)))
{
host.Open();
Console.ReadLine();
host.Close();
}
}
In the service library, a constructor is called from that code
public MyServiceLib()
{
Task.Factory.StartNew(this.Scaffold);
}
Which does nothing more than starting the state machine
private void Scaffold()
{
// lots of code deleted for simplicity reasons
var dataSource = new MockDataSource();
// inject the mocked datasource
this.dataManager = new DataManager(dataSource);
// this runs in its own thread. There are parts that are started on a timer event.
this.dataManager.Start();
}
public class DataManager : IDataManager
{
public void Start()
{
while (this.IsRunning)
{
var data = this.dataSource.getNext();
if (data != null)
{
// do some work with the data retrieved
// lots of code paths will be hit from that
this.Process(data);
}
else
{
Thread.Sleep(1000);
}
}
}
public void Process(IData data)
{
switch (data.PackageType)
{
case EnumPackageType.Single:
{
ProcessSingle(data);
break;
}
case EnumPackageType.Multiple:
{
ProcessMultiple(data);
break;
}
// here are lots of cases
default:
{
Logger.Error("unknown package type");
break;
}
}
}
}
What I have tried so far:
OpenCover
with a special test dll that would create the Host as shown above, but the host cannot be created properly, so the testing does not start really. I get a "Host is in fault state" error message. I followed this mini-tutorial. Despite that I get a coverage report with a calculated coverage of about 20%. But the service is just starting, it is not doing any work so far.
Visual Studio Performance Tools
The steps are essentially described in this article. I get a myproject.coverage file, but I cannot view it, because I only have a VS Professional, the coverage seems to be only of use in Test Premium or Ultimate editions.
Besides having tried those two, I will accept any answer showing how to get it up and running with any of those (openCover preferred).
Will accept an answer that shows how to test this setup and get a code coverage while leveraging tools to generate most of the code (as pex would, but after trial I see it does not generate very good code).
It would help to see the operations of the service.
I never tried running such "console kind" application under a coverage tool.
I would suggest writing a test with let's say NUnit (or any other unit testing framework; it's not a unit test, obviously, but the technique fits quite well).
In the test, you open the service host, create a client of the service, let the client execute some operations on your service, and close the service host.
Run this test under a coverage tool, and you should be done.
I've done that with NUnit and NCover about 7 years ago, using their current versions at that time (NCover was free software, if I remember it right).
Looks like with OpenCover you are actually getting the coverage, but the service is entering Faulted state, so to you need to catch the faults from your ServiceHost and adress that.
Basically you need some kind of error log, and the first thing i would try is looking in the system event logs (Win+R, eventvwr.msc, Enter).
You can also try to listen to the Faulted events on your ServiceHost:
host.Faulted += new EventHandler(host_faulted);
Here is the link to another SO answer addressing this issue:
How to find out the reason of ServiceHost Faulted event
I would suggest testing your business logic and not the bootstrap code. I mean testing DataManager class and not the hosting and the initializing code. You can write a unit test, using one of the unit testing frameworks, for example NUnit. Then you can run your tests either in Visual Studio with Resharper Ultimate or in your Continuous Integration with Code Coverage tool, like OpenCover or dotCover to get your code coverage.
[TestFixture]
public class DataManagerTests
{
[Test]
public void Process_Single_Processed()
{
// Arrange
IData data = new SingleData();
DataManager dataManager = new DataManager();
// Act
dataManager.Process(data);
// Assert
// check data processed correctly
}
}
in order to allow your Unit-Test-Framework to determin the coverage you have to host the service within the "runner" of the framework (aka. the process that is executing the tests).
The coverage is calculated by and withing the "runner" what means that you can not get coverage if the service is hosted anywhere else.
Below I'll add an example how to do this.
Greetings
Juy Juka
namespace ConsoleApplication4
{
using System.ServiceModel; // Don't forgett to add System.ServiceModel as Reference to the Project.
public class Program
{
static void Main(string[] args)
{
string arg = ((args != null && args.Length > decimal.Zero ? args[(int)decimal.Zero] : null) ?? string.Empty).ToLower(); // This is only reading the input for the example application, see also end of Main method.
string randomUrl = "net.tcp://localhost:60" + new System.Random().Next(1, 100) + "/rnd" + new System.Random().Next(); // random URL to allow multiple instances parallel (for example in Unit-Tests). // Better way?
if (arg.StartsWith("t"))
{
// this part could be written as a UnitTest and should be
string result = null;
using (ServiceHost host = new ServiceHost(typeof(MyService)))
{
host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(), randomUrl);
host.Open();
IMyService instance = ChannelFactory<IMyService>.CreateChannel(new NetTcpBinding(), new EndpointAddress(randomUrl), null);
result = instance.GetIdentity();
host.Close();
}
// Assert.Equals(result,"Juy Juka");
}
else if (arg.StartsWith("s"))
{
// This part runs the service and provides it to the outside. Just to show that it is a real and working host. (and not only working in a Unit-Test)
using (ServiceHost host = new ServiceHost(typeof(MyService)))
{
host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(), randomUrl);
host.Open();
System.Console.Out.WriteLine("Service hosted under following URL. Terminate with ENTER.");
System.Console.Out.WriteLine(randomUrl);
System.Console.In.ReadLine();
host.Close();
}
}
else if (arg.StartsWith("c"))
{
// This part consumes a service that is run/hosted outoside of the application. Just to show that it is a real and working host. (and not only working in a Unit-Test)
System.Console.Out.WriteLine("Please enter URL of the Service. Execute GetIdentity with ENTER. Terminate with ENTER.");
IMyService instance = ChannelFactory<IMyService>.CreateChannel(new NetTcpBinding(), new EndpointAddress(System.Console.In.ReadLine()), null);
System.Console.Out.WriteLine(instance.GetIdentity());
System.Console.In.ReadLine();
}
else
{
// This is only to explain the example application here.
System.Console.Out.WriteLine("I don't understand? Please use one of the following (Terminate this instance with ENTER):");
System.Console.Out.WriteLine("t: To host and call the service at once, like in a UnitTest.");
System.Console.Out.WriteLine("s: To host the servic, waiting for clients.");
System.Console.Out.WriteLine("c: To contact a hosted service and display it's GetIdenttity result.");
System.Console.In.ReadLine();
}
}
}
// Declaration and Implementation of the Service
[ServiceContract]
public interface IMyService
{
[OperationContract]
string GetIdentity();
}
public class MyService : IMyService
{
public string GetIdentity()
{
return "Juy Juka";
}
}
}
I am completely new to WCF and multithreading. So, I followed this tutorial to set up a selfhosted WCF service. After I right-clicked on the Interface "INews_Service", I clicked on "implement Interface". Then, VS creates a method named DoWork().
In the tutorial above, the DoWork() method is not needed (-> it is deleted). However, in my project, I would like to use this method to run a background worker thread/task.
In my project, the background worker is supposed to permanently load data from an external device and store it in the DataContract-class. The WCF Service, in turn, is supposed to simultaneously provide the instance of that DataContract-class to external WCF consumers.
In reference to the tutorial above, what is the best way to add a background worker method, which constantly changes the variables within an instance of the DataContract-class?
EDIT:
#Brian: Thank you very much! The following example shows the selfhosted service program from the tutuorial above. After I start the host, I would like to run an endless loop that constantly updates my DataContract-class. Can you make an example, how this can be done? I do not need any synchronization, such as SpinLock or Monitor?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF_NewsService
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(News_Service));
host.Open();
Console.WriteLine("Host Open Sucessfully ...");
while (true)
{
//here I want to constantly update my DataContract-class TOInews
}
}
}
}
EDIT2:
Actually, my problem is that I don't know how to access my DataContract-object objtoinews, which is defined in another file (i.e. in NewsService, as in the tutorial). When I run something like objtoinews.ID = 1;, VS complains that objtoinews is not defined in the current context.
while (true)
{
if (host.State != CommunicationState.Opened)
{
throw new Exception("SynchronizationWS Service Host failed.");
break;
}
else
{
objtoinews.ID = 1;
objtoinews.Header = "blabla";
objtoinews.Body = "huhu";
}
}
You don't need to use DoWork in a WCF solution. Basically, the console program described in that tutoral will perform DoWork() when the Host.Open() is called. In other words, Host.Open() will do what you expect DoWork() will do.
The console acts as the executable, but all the work is done asynchronously/multi-threaded in the background by WCF service.
Booz, I'm not sure why you think you need to continously update your DataContract. I don't think you can, actually, while the program's running. If you're worried about people sending different data constructs to your WS host, maybe you need to abstract the structure so that (basically) your clients can send virtually anything.
In any event, here's the code I'm use to loop and check the status of my web service:
while (true) {
//broken connection case
if (wshost.State != CommunicationState.Opened) {
throw new Exception("Service Host failed.");
//dump from loop and throw error
break;
}
Threading.Thread.Sleep(1000); //sleep 1 second
}
I'm new in WCF. I am currently working on a project where I explained in my last question posted here --> How can I implement callbacks to another server in c#
I am trying to study callback with an example. It uses Console Application for hosting the service and Windows Forms for client. I must run my server B with Console application instead of Windows Form. Everything was fine when I run the service alone. But when I run my server B(client), an error was displayed:
"The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error."
It was pointing on this code in my serverB(client) code:
public void NotifySubscribe(string subscriberName)
{
SendOrPostCallback callback =
delegate (object state)
{
Console.WriteLine(String.Format("{0} has subscribe to service", state.ToString()));
Console.WriteLine(subscriberName);
Console.Read();
};
_uiSyncContext.Post(callback, subscriberName);
}
The almost imitated everything from an example that I have.
This was the original code of that code above:
public void NotifyGuestJoinedParty(string guestName)
{
// The UI thread won't be handling the callback, but it is the only one allowed to update the controls.
// So, we will dispatch the UI update back to the UI sync context.
SendOrPostCallback callback =
delegate (object state)
{ this.WritePartyLogMessage(String.Format("{0} has joined the party.", state.ToString())); };
_uiSyncContext.Post(callback, guestName);
}
private void WritePartyLogMessage(string message)
{
string format = this.txtPartyLog.Text.Length > 0 ? "{0}\r\n{1} {2}" : "{0}{1} {2}";
this.txtPartyLog.Text = String.Format(format, this.txtPartyLog.Text, DateTime.Now.ToShortTimeString(), message);
this.txtPartyLog.SelectionStart = this.txtPartyLog.Text.Length - 1;
this.txtPartyLog.ScrollToCaret();
}
Because I'm using Console Application in my project, instead of having it in a textbox, I write it using Console.writeline();
I don't know if it is about the delegate(object state) there in the code.
Please response. I don't know how to fix the bug. Thank you.
I am new with WCF as well.
I believe you cannot perform such action in a console application using a duplex service.
This question was asked 5 months ago, so I suppose you have found the answer you desire.
Please correct me if I am wrong. I'd appreciate it.
I created an application that provides several services. Each service provides a specific processing capabilities, except one service (that is the main service) that returns true or false to the clients which request if the specified processing capabilities is available or not.
Now I would modify the application, leaving the main service unchanged and adding the support for the installation of plugin with new processing capabilities: each plugin should add new processing capabilities without the need of implement a new service, but after installing the new plugin, a new service should be avaible. In this way, a plugin should not handle the communication layer. In other words, I would like to separate the layer of the communication and processing, in order to simplify the creation of new plugins.
Is it possible?
I could create two services: the main service and the service for processing.
The first service may be used by clients to know if a certain feature is present on the server (for example, clients may ask the server if it has installed the plugin that provides the functionality for solving differential equations).
The second service could be used to send a generic task and to receive a general result, for example:
Result executeTask(Task task);
where Result and Task are abstract classes...
For example, if I develop a plugin to solve the differential equations, I first create the classes for transferring data:
public class DifferentialEquationTask : Task
// This class contains the data of the differential equation to be solved.
...
public class DifferentialEquationResult : Result
// This class contains the the result.
...
Therefore, the client should instantiate a new object DifferentialEquationTask and pass it to the method of the second service:
DifferentialEquationTask myTask = new DifferentialEquationTask(...);
...
Result result = executeTask(myTask); // called by basic application
// The second service receives myTask as a Task object.
// This Task object also contains the destination plugin, so myTask is send
// to the correct plugin, which converts it to DifferentialEquationTask
...
myResult = result as DifferentialEquationResult;
// received by the client
Moreover, each plugin should have a version for the application server and a version for the client application.
An alternative would be to include the service in the plugin itself: in this way, a new plugin should implement a new functionality and expose it via an additional service.
In summary, I thought the following two alternatives:
a main service to ask the server if it has a plugin or not, and a second service to deliver tasks at the correct plugin;
a main service to ask if the server has a plugin or not, and various additional services (an additional service for each plugin installed).
In order to choose the best approach, I could use the following requirements:
Which of the two alternatives may provide better performance?
What advantages would be obtained using a new service for each plugin than using a single service that delivers tasks at the correct plugin?
Which of the two alternatives simplifies the development of a new plugin?
Being a novice, I was wondering if there was a better approach...
Thanks a lot!
It seems like the main service could maintain a dictionary of plugins, indexed by name. Then for a client to see if the server provides a particular service, all the main service has to do is look up the name in the dictionary. And to process, the service just has to call a method on the object that's in the value portion of the dictionary entry. An example:
You have three abstract classes: Service, ServiceResult, and ServiceTask. The contents of ServiceTask and ServiceResult aren't really important for this discussion. Service must have a parameterless constructor and a method called Process that takes a ServiceTask as its sole parameter. So your differential equation solver would look like:
public class DiffeqSolver : Service
{
public DiffeqSolver()
{
// do any required initialization here
}
public ServiceResult Process(ServiceTask task)
{
DiffeqTask dtask = task as DiffeqTask;
if (dtask == null)
{
// Error. User didn't pass a DiffeqTask.
// Somehow communicate error back to client.
}
// Here, solve the diff eq and return the result.
}
}
The main service is somehow notified of existing plugins. It maintains a dictionary:
Dictionary<string, Service> Services = new Dictionary<string, Service>();
I assume you have some idea how you're going to load the plugins. What you want, in effect, is for the dictionary to contain:
Key = "DiffeqSolver", Value = new DiffeqSolver();
Key = "ServiceType1", Value = new ServiceType1();
etc., etc.
You can then have two methods for the main service: ServiceIsSupported and Process:
bool ServiceIsSupported(string serviceName)
{
return Services.ContainsKey(serviceName);
}
ServiceResult Process(string serviceName, ServiceTask task)
{
Service srv;
if (Services.TryGetValue(serviceName, out srv))
{
return srv.Process(task);
}
else
{
// The service isn't supported.
// Return a failure result
return FailedServiceResult;
}
}
I've simplified that to some extent. In particular, I'm using a Dictionary, which is not thread safe. You'd want to use a ConcurrentDictionary, or use locks to synchronize access to your dictionary.
The more difficult part, I think, will be loading the plugins. But there are many available examples of creating a plugin architecture. I think you can find what you need.