I am trying to make a WCF Service that both publishes messages to a topic and also subscribes to that topic. The idea is to have my service expose endpoints for managing a customer (i.e. CreateCustomer, EditCustomer, DeleteCustomer, etc). Then I want it to publish a message to a topic after it completes each operation (i.e. OnCustomerCreated, OnCustomerChanged, OnCustomerDeleted, etc.)
For Example, A client application will hit the EditCustomer message on my service. I will immediately publish an OnCustomerChanged message with the customer object provided. My service (the same one the customer hit) will have another contract that takes an OnCustomerChanged and updates my database.
My question is, do I have to make a separate Subscription on my topic for each message type (i.e. OnCustomerChangedSubscription, OnCustomerDeletedSubscription, etc.) so that I can properly route messages of different types to the correct endpoint?
If that is case, I would need a bunch of single-method contracts so that I could configure the wcf endpoints properly:
i.e:
<service name="site.Services.Business.Managers.CustomerManager">
<!-- endpoint that clients will hit -->
<endpoint address="" binding="basicHttpBinding" contract="site.Services.Business.Contracts.ICustomerManager" />
<!-- endpoint that publishes messages-->
<endpoint address="sb://test-site.servicebus.windows.net/Managers/CustomerManager"
binding="netTcpRelayBinding"
contract="site.Services.Business.Contracts.ICustomerManager"
behaviorConfiguration="sbTokenProvider" />
<!-- One Endpoint for each message type (this will get very cumbersome and the contract will only have 1 method on it) -->
<endpoint address="sb://test-site.servicebus.windows.net/Managers/CustomerManager"
binding="netMessagingBinding"
listenUri="sb://test-site.servicebus.windows.net/Managers/CustomerManager/subscriptions/OnCustomerDeleted"
behaviorConfiguration ="sbTokenProvider"
contract="site.Services.Business.Contracts.CustomerManager.IOnCustomerDeleted" />
<endpoint address="sb://test-site.servicebus.windows.net/Managers/CustomerManager"
binding="netMessagingBinding"
listenUri="sb://test-site.servicebus.windows.net/Managers/CustomerManager/subscriptions/OnCustomerCreated"
behaviorConfiguration ="sbTokenProvider"
contract="site.Services.Business.Contracts.CustomerManager.IOnCustomerCreated" />
…etc
</service>
An alternative would be to create a single Subscriber (Allmessages), only having one contract with a HandleMessage(BrokeredMessage message) operation, and then determine inside that method with method to call on my service. That doesn't seem like I am doing the right thing there though either. I am essentially taking in all messages and determining the handler inside the service.
What I am looking for is a way to have a a service that implements 3 contracts, ICustomerPublisher (already have this), ICustomerManager (exposed over http to clients), and ICustomerSubscriber.
ICustomerSubscriber would look like:
[ServiceContract]
public interface ICustomerSubscriber
{
[OperationContract(IsOneWay = true)]
void OnCustomerCreated(ICustomerMessage message);
[OperationContract(IsOneWay = true)]
void OnCustomerDeleted(ICustomerMessage message);
[OperationContract(IsOneWay = true)]
void OnCustomerChanged(ICustomerMessage message);
}
and I would be able to call:
publisher.Publish<OnCustomerChanged>(new CustomerChangedMessage(customer));
and have my OnCustomerChanged method receive that message.
Any help would be appreciated.
First: Yes, if the service has per-request activation, then your SubscriptionClient won't get a chance to run except while the service responds to an external request, and it will have to be created and torn down every time. The only feasible way to keep the SubscriptionClient running would be to change activation to singleton.
But I think the better approach is to pull the SubscriptionClient out of this service entirely and have it run on its own. If you want it to run on-prem, then it could be in a Windows Service or console app; in the cloud, it could be a WebJob or Worker Role. Again, I don't see why your OnMessage method needs to be a WCF Operation.
Second: If there's only one Subscription, then each message can be received and completed by only one client. If multiple clients need to get a copy of each message, then each client needs its own subscription. The subscriptions can share the same filter conditions, or no conditions.
I was not able to find a suitable way to accomplish what I mentioned above. Instead, I did the following:
Created a default subscription to my customer topic with no filter.
In the constructor for my WCF service, I use the SubscriptionClient to register as a subscriber:
public CustomerManager()
{
//set up automapper and IoC
Initializer.Initialize();
Publisher = IoCContainer.GetContainer().Resolve<IPublisher>("CustomerManager");
Publisher.Subscribe("AllMessages");
var client =
SubscriptionClient.CreateFromConnectionString(CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString"), "customertopic", "AllMessages", ReceiveMode.PeekLock);
client.OnMessage(m =>
{
Console.WriteLine("Message Received.");
HandleMessage(m);
});
}
My HandleMessage method takes a BrokeredMessage as a parameter and then determines what internal operation to call based on the message body type.
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void HandleMessage(BrokeredMessage message)
{
var customerMessage = message.GetBody<CustomerMessage>();
switch (customerMessage.EventName)
{
case "OnCreated" :
OnCustomerCreated(customerMessage);
break;
case "OnDeleted" :
OnCustomerDeleted(customerMessage as OnDeleted);
break;
case "OnChanged" :
OnCustomerChanged(customerMessage as OnChanged);
break;
}
message.Complete(); //mark the message as completed
}
I have a couple of concerns though.
First, I use instance per request configuration for my service. Will this cause any kind of message consistency issues? Such as, when I have multiple instances of CustomerManager, will each one try and handle the same message, or will the SubscriptionClient ensure that only 1 CustomerManager will get the message?
Second, I want to be able to subscribe other services to my AllMessages subscription. One example is a notification service. I may want to be able to send a push notification to the account owner when a new customer is created (OnCustomerCreated), or I might want to ask the customer to verify the information that changed on their account (OnCustomerChanged). Can I still subscribe other services to the CustomerTopic AllMessages, or will marking the message complete (as I do at the end of my HandleMessage method) clear out the message for other subscribers also? More technically, will adding another subscriber to the AllMessages subscription result in multicasting or balancing?
Related
The Background
I have a WCF client and service with a one-to-one relationship (i.e. one service host per client). I use a callback contract to pass messages from the service to the client.
[ServiceContract(CallbackContract = typeof(IMessageTarget)]
public interface IMessageService
{
[OperationContract]
void StartMessages();
}
public interface IMessageTarget
{
[OperationContract]
Task SendAsync(Message message);
[OperationContract(IsOneWay = true)]
void Complete();
[OperationContract(IsOneWay = true)]
void Fault(ExceptionDetail exception);
}
On the client, I set up a duplex connection to begin communication. MessageTarget.Completion completes when the Complete() or Fault(ExceptionDetail) methods are called.
IMessageTarget target = new MessageTarget();
var channelFactory = new DuplexChannelFactory<IMessageService>(target, new NetNamedPipeBinding());
IMessageService service = channelFactory.CreateChannel(new EndpointAddress(EndpointUri));
service.StartMessages();
await target.Completion;
The service sends messages to the client as soon as they become available. The client is able to delay further messages by making SendAsync() take a long time to return.
IMessageTarget client = OperationContext.Current.GetCallbackChannel<IMessageTarget>();
// later, whenever a message is created
await client.SendAsync(message);
// finally
client.Complete();
The Problem
I discovered that the client hangs if the service crashes. Unfortunately, WCF provides no reliable way to detect if the service is still up, other than calling a service method and catching CommunicationException. Therefore, I added a new operation
[OperationContract]
Task PingAsync();
so the client can monitor if the service goes down.
service.StartMessages();
while (!target.Completion.IsCompleted)
{
await service.PingAsync();
await Task.Delay(TimeSpan.FromSeconds(0.5));
}
await target.Completion;
The Question
Doesn't this defeat the purpose of using callbacks? I'm struggling to see how this "listen for callbacks while constantly pinging" strategy is better than simply polling for the latest messages (with no duplex required). Did I over-engineer this, or is there some other benefit to callbacks?
Most WCF related exceptions are derived from CommunicationsException, so what you did is good in that sense. As you noted, pinging / polling defeats the purpose of the callback implementation. Bottom line, you need to just call the service and be ready to deal with any exceptions that happen during the service invocation. There's really no reliable way to see if the service is available.
If you are not already doing so, you can also look at the IClientChannel events to monitor what happens with the connection.
client.InnerChannel.Closed += OnChannelClosed;
client.InnerChannel.Opening += OnChannelOpening;
client.InnerChannel.Opened += OnChannelOpened;
client.InnerChannel.Closing += OnChannelClosing;
client.InnerChannel.Faulted += OnChannelFaulted;
client.InnerChannel.UnknownMessageReceived += OnChannelUnknownMessageReceived;
We have one application that publishes messages - PublishApp.
The messages (MessageA and MessageB) are picked up by two different consumer apps (ConsumerA and ConsumerB).
All applications use SQL Server as a transport and Windsor configuration, but the two Consumers have different databases in SQL Server.
How can we configure PublishApp to publish MessageA for ConsumerA and MessageB for ConsumerB?
I have tried using DetermineMessageOwnership as described here, but that doesn't seem to actually be called (no breakpoints hit). I'm a little mystified as to what the string endpoints returned should be.
I was hoping that I could set up an IBus component in Windsor with a specific name, then reference that by name when setting up my MessageB-publishing class. However it's not clear how to set up an IBus in Windsor outside of the magic box that does it all for me.
Fiddling with Windsor configuration leads me to a Windsor error if I try to call Configure.With(new WindsorContainerAdapter(container)) twice, as it is interpreted as registering the IBus interface twice. I can't see an extension point here to give one of the IBus instances a name, and hence differentiate them in Windsor.
Alternatively, trying to reuse the Configure.With... call throws an error telling me I have called .Transport() on the configurer twice, which is also not allowed (but which would let me use a different connection string...)
Adding XML configuration will let me specify different endpoints for my different messages, but not different SQL connection strings.
What I would really like to end up with is something like:
// Set up Bus A
var busA = Configure.With(new WindsorContainerAdapter(container))
.Transport(tc => tc.UseSqlServerInOneWayClientMode("ConnectionStringA"))
.Subscriptions(sc => sc.StoreInSqlServer("ConnectionStringA", "RebusSubscriptions"))
.CreateBus()
.Start();
// Set up Bus B
var busB = Configure.With(new WindsorContainerAdapter(container))
.Transport(tc => tc.UseSqlServerInOneWayClientMode("ConnectionStringB"))
.Subscriptions(sc => sc.StoreInSqlServer("ConnectionStringB", "RebusSubscriptions"))
.CreateBus()
.Start();
// Register Bus A in Windsor
container.Register(Component.For<IBus>()
.Named("BusA")
.Instance(busA));
// Register a class that depends on IBus, and set it to use Bus A
container.Register(Component.For<IPublishA>()
.ImplementedBy<PublishA>()
.DependsOn(Dependency.OnComponent(typeof(IBus), "BusA"));
// And a registration also for IBus B, and for IPublishB to reference named "BusB"
Note: I do not want to listen to multiple buses, only publish events to them. Other applications are monitoring the queues, and each application only listens for one event on one queue.
We resolved this in the end by dropping the WindsorContainerAdaptor. Since we're not handling any messages, only publishing/sending, we don't need any of the 'handler' stuff in the container adaptor and we can switch the registration of the IBus component around to happen outside of the configuration/starting, rather than inside it. This gives us the control to name the IBus registration.
public static void ConfigureAndStartBus(IWindsorContainer container)
{
_RegisterBus(container, "ConnectionStringA" "BusA");
_RegisterBus(container, "ConnectionStringB" "BusB");
}
private static void _RegisterBus(IWindsorContainer container, string connectionString, string busName)
{
var bus = Configure.With(new BuiltinContainerAdapter())
.Transport(tc => tc.UseSqlServerInOneWayClientMode(connectionString))
.Subscriptions(sc => sc.StoreInSqlServer(connectionString, "RebusSubscriptions"))
.CreateBus()
.Start();
container.Register(
Component.For<IBus>()
.Named(busName)
.LifestyleSingleton()
.Instance(bus));
}
Then in class PublishA, we can register it with a dependency on BusA, and PublishB can be registered with a dependency on BusB. The messages go to separate databases, and are picked up by separate subscribers to do work in those different databases.
First off: There's no way (at least at the moment) to ship messages between two SQL Server databases. In order for messages to be sent/published between endpoints, you need to use one single table in one shared database.
Your setup hints at something being off there, since you're using "ConnectionStringA" and "ConnectionStringB" for the transports.
It's not clear to me whether you actually want/need to do pub/sub messaging - pub/sub is what you would usually use when you want multiple recipients of each message, which would usually be some kind of event (i.e. a message whose name is in the past tense, as in: "this and that happened").
If you want one specific recipient for a message, you want to bus.Send that message, and that is when your endpoint mappings will be hit in order to get a destination for the message.
If you tell me some more about exactly what you're trying to achieve, I am sure I can help you :)
I have question about the article in here. The related code is as follow.
public CustomClientChannel(string configurationPath) : base(typeof(T))
{
this.configurationPath = configurationPath;
base.InitializeEndpoint((string)null, null);
}
protected override ServiceEndpoint CreateDescription()
{
ServiceEndpoint serviceEndpoint = base.CreateDescription();
ExeConfigurationFileMap map = new ExeConfigurationFileMap();
map.ExeConfigFilename = this.configurationPath;
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
ServiceModelSectionGroup group = ServiceModelSectionGroup.GetSectionGroup(config);
ChannelEndpointElement selectedEndpoint = null;
......
}
The first function is the constructor of the class and the second one overloads the CreateDescription function in DuplexChannelFactory since the class inherents from DuplexChannelFactory.
However, when I use this class to create an object, the code runs directly into CreateDescription().
Therefore, even though I pass a configurationPath to the constructor, the this.configurationPath is still null.
If I inherent CustomClientChannel class from ChannelFactory, then it works fine.
Please help to see what is wrong and what is the difference between ChannelFactory and DuplexChannelFactory? I am using netTcpBinding. Thank you.
Your constructor is calling into a base constructor that takes a single type.
: base(typeof(T))
Both ChannelFactory<T> and DuplexChannelFactory<T> implement a protected constructor that takes a single type, but they are different.
ChannelFactory<T> wants the channel type.
DuplexChannelFactory<T> want the callback instance type
In other words, they do completely different things and can have completely different code paths. Protected constructors are to be used by derived types only, and generally infer some level of knowledge of how the class hierarchy works.
You would be better picking the public constructor that you would normally use, and calling that instead. If that is not possible then you need to work out the appropriate DuplexChannelFactory constructor to call instead.
Let's start with
what is the difference between ChannelFactory and
DuplexChannelFactory?
First it is necessary to understand the difference between simplex and duplex communications in WCF.
A simplex connection is like sending a text message and then receiving a delivery notification - even though at a lower transport level there may be multiple calls between your phone and the carrier, from a communications level, you are sending the text message in one direction only.
A duplex connection is like making a phone call, you are able to send voice data to your carrier, and your carrier can send voice data in the other direction at the same time. This requires a simplex channel open in both directions.
Both ChannelFactory and DuplexChannelFactory can be used to create a client channel to a service.
Which one you choose is down to whether the service you are calling exposes simplex or duplex service operations, and whether you, as a comsumer, need to consume these operations.
If the service exposes normal (i.e, simplex) operations, for example:
void DoCoSomething(int x);
List<Things> GetThings();
etc...
...then you can consume these operations with ChannelFactory.
If the service exposes a callback contract:
public interface IMyDuplexCallback
{
[OperationContract(IsOneWay = true)]
void NotifyMeOf(string message);
}
for example, in order to send notifications or message of some kind to the consumers, and you as a consumer want to take advantage of this, you can use the DuplexChannelFactory to call the service (after implementing the callback contract on your client of course).
As a general rule, duplex communication in WCF is complex at best, and is therefore best avoided.
Appreciate this does not address your original question.
I am having some trouble implementing the right patterns for a work project and I don't want to precede until I am satisfied with the right design strategy.
The project is based around Genesys Computer Telephony Integration (CTI) Platform. Essentially, utilizing a SDK provided by Genesys, a single client subscribes to a number of Genesys services (or TServers) running remotely. The client then registers a whole heap of Directory Numbers (DN's) associated to a particular TServer and waits for call events. When an event occurs, it is captured by the client and stored in a database. A number of other operations are executed, which is irrelevant at this stage. A lot of the communication work is handled by the Genesys ProtocolManager object, so a single event handler captures event data across all clients, which in turn is handled by a EventBrokerService. Here is a simple code to illustrate the connection process, registration of a single DN and the event function:
EventBrokerService eventBrokerService;
using (var client = new TServerProtocol(
new Endpoint(
new Uri("tcp://tserver01:11234"))))
{
client.Open();
eventBrokerService = BrokerServiceFactory.CreateEventBroker(client);
eventBrokerService.Activate();
eventBrokerService.Register(this.OnEvent);
RequestRegisterAddress requestRegisterAddress =
RequestRegisterAddress.Create("977845873",
RegisterMode.ModeMonitor,
ControlMode.RegisterDefault,
AddressType.DN);
IMessage response = client.Request(requestRegisterAddress);
}
and then we listen for events (there are many different events):
private void OnEvent(IMessage response)
{
switch (response.Id)
{
case EventACK.MessageId:
//do something
break;
case EventLinkConnected.MessageId:
var ev = response as EventLinkConnected;
//Insert event into DB and perform some other operations...
break;
}
}
The Genesys Platform, comes with another component called a Genesys Configuration server. The config server holds all of the TServer details, including the DN information and a whole bunch of other "objects". It is really just a fancy DBMS. The difference is, you can also subscribe to the config server and register for CRUD events (i.e. CreateEvent, UpdateEvent etc...). Without illustrating the code, the concept is similar to the one above. (i.e. You can register to a number of different Configuration Servers and listen for CRUD events).
For the most part, I have covered the above well and I am satisfied with the implementation so far. What I am trying to achieve is as follows:
I am attempting to implement a distributed system. In a nutshell, the system will consist of 2 components. Monitoring Services and Dispatcher Service components (they will all be Windows Services)
Monitoring Service Component
The "Monitoring Service(s)" connect to 1 or many T Servers to monitor for call events
The monitoring service will ALSO subscribe to a dispatcher service
Dispatcher Service Component
The "Dispatcher Service" connects to 1 or more Configuration Servers and waits for CRUD events.
Once an event occurs (i.e. a new DN was added on the config server), the dispatcher captures the creation event, and notifies all monitoring service subscribers. Subsequently, the dispatcher will also update a local database, so the DN information is preserved for redundancy (in case dispatcher can not connect to a Configuration Server).
The monitoring subscriber, to whom the newly created DN belongs (distinguished by a unique DBID and TServerID identifiers) will accept the DN, and register it for listening events (similarly illustrated in the first code snippet). The monitoring subscriber who does not possess the required TServer connection will drop the received request, naturally.
The Dispatcher can also receive newly added TServers, but this time around, it will make the decision which monitoring service it want's to utilize in order for that monitoring service to make ANOTHER connection. This will be determined by factors such as the number of current sessions running on a monitoring service or the how much memory a single service is chewing up at the time.
I have come up with some basic concepts and here is some of the code to illustrate what I have done thus far:
The communication method I have chosen is WCF with NetTcpBinding, so for the simple part, I have exposed an interface:
[ServiceContract(Namespace = "urn:Netwatch",
SessionMode = SessionMode.Required,
CallbackContract = typeof(IDisMonServiceCallback))]
public interface IDisMonService
{
[OperationContract]
bool Subscribe(string uid);
[OperationContract(IsOneWay = true)]
void Unsubscribe(string uid);
}
[ServiceContract(Namespace="urn:Netwatch")]
public interface IDisMonServiceCallback
{
[OperationContract]
bool DNRegistered(int tServerId, string dnEntry);
}
and on the dispatcher, I have implemented it:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DisMonService : IDisMonService
{
private ConcurrentDictionary<string, IDisMonServiceCallback> subscribers = new ConcurrentDictionary<string, IDisMonServiceCallback>();
public IDisMonServiceCallback this[string uid]
{
get
{
IDisMonServiceCallback callback;
if (!subscribers.TryGetValue(uid, out callback))
return null;
return callback;
}
}
public List<IDisMonServiceCallback> GetAllServiceCallbacks()
{
return new List<IDisMonServiceCallback>(subscribers.Values);
}
public bool Subscribe(string uid)
{
IDisMonServiceCallback callback = GlobalHelper.Callback<IDisMonServiceCallback>();
if (!subscribers.ContainsKey(uid))
if (!subscribers.TryAdd(uid, callback))
return false;
return true;
}
public void Unsubscribe(string uid)
{
IDisMonServiceCallback callback;
if (subscribers.ContainsKey(uid))
if (!subscribers.TryRemove(uid, out callback))
return;
return;
}
}
From the code above, it is obvious that each subscribing monitoring service has a unique identifier, that way the right service callback context is retrieved (in case I decide to do some other funky operations).
This is where my dilemma essentially begins. To cut the long story short, my question(s) are as follow:
How do I deal with DisMonService class when attempting to pass on messages to all subscribers from within the Dispatcher service. i.e. new DN has been added, let us call the DisMonService class and notify all subscribers.
What would be the most optimal pattern to implement in dealing with updates to all subscribers from within DisMonServie
At the moment my dummy client connects to the dispatcher, and it registers itself. Moving forward, what is the best way to access the DisMonService class.
I hope I am not confusing anybody at what I am trying to ask. I guess what I am really trying to find is best way to implement the above system, any suggestions and such. Some code samples and snippets would really be helpful.
This is my first post here so I apologise to anybody if I haven't explained myself to the forum's standards.
I am writing a complex distributed application by taking advantage of WCF services.
Requirements
My requirements are the following:
There are many stations (PCs) having the same software running on them (the application I need to develop).
Every station will send messages to other stations (every station has a neighbourhood). The stations will route messages in order to reach the final destination for each message (it is a P2P context where local routing is necessary).
When a message is delivered by a station, that station has to be sure that message reaches the destination (another station somewhere in the world). For performance reasons I cannot create services that route my message using synchronous approaches (a service call would last too much time, polling has never been a good idea). For this reason a feedback messaging is considered: once the message reaches its destination, the destination will send a I-Received-The-Message message back to the sender.
With this approach, I need my stations to implement services in order to route feedback messages. Basically, everytime a message is delivered, a task table is filled with one record indicating that a message delivery needs to be confirmed. If no feedback message for that message reaches the sender station, the sender station will try to send the original message again.
What I cannot do
I know that for P2P scenarios there is a well provided service type, but I cannot use it for some reasons (I will not bother you with these reasons).
Please, accept the requirements I listed above.
My solution
I adopted this solution:
Two service contracts define calls for sending (routing) normal messages and reply/delivery-confirm messages:
/* Routing routines */
[ServiceContract]
public interface IMessageRouting {
/* When a client receives the message, in the MyMessage type
there are some fields that helps the current station to
decide which neighbour station the received packet will
be routed to */
[OperationContract(IsOneWay = true)]
void RouteMessage(MyMessage msg);
}
/* Delivery-Confirm messaging */
[ServiceContract]
public interface IDeliveryConfirmMessageRouting {
/* When the final destination (THE FINAL DESTINATION
ONLY, not an intermediate hop station) obtains a
message, it will route back to the sender a reply message */
[OperationContract(IsOneWay = true)]
void RouteDeliveryConfirmMessage(MyDeliveryConfirmMessage dcmsg);
}
Here are the services implementations:
/* This service will be self-hosted by my application in order
to provide routing functionality to other stations */
[ServiceBehaviour(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Single)]
public class StationMessagingService : IMessageRouting {
/* Constructing the service */
public StationMessagingService() { ... }
// Implementation of serive operations
public void RouteMessage(MyMessage msg) {
...
}
}
And the delivery confirm service...
/* This service will be self-hosted by my application in order
to provide delivery confirm message routing functionality
to other stations */
[ServiceBehaviour(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Single)]
public class StationDeliveryConfirmService : IDeliveryConfirmMessageRouting {
/* This service is particular, I will discuss the following lines
before the constructors in the next paragraph after first
typing all the code */
public delegate void DeliveryMessageReceivedEventHandler(
object sender, String DeliveryMessageReceivedEventArgs);
public event DeliveryMessageReceivedEventHandler DeliveryMessageReceived;
/* Constructing the service */
public StationDeliveryConfirmService() { ... }
// Implementation of serive operations
public void RouteDeliveryConfirmMessage(MyDeliveryConfirmMessage dcmsg) {
...
/* In the end fire the event only if I am the destination
of this message, otherwise I must route this message */
if (...) { /* Omitting condition for clarity */
this.DeliveryMessageReceived(this,
"A delivery confirm message has arrived with this info: " +
dcmsg.Info()); /* Info contains string info */
}
}
}
At this point I am ready to host my services:
/* My program */
public class Program {
// Program's entry point
static void Main(string[] args) {
// Defining the delivery check table (I have a special type/class for this)
DeliveryCheckTable DCT = new DeliveryCheckTable(...);
// Creating services
StationMessagingService SMS = new StationMessagingService();
StationDeliveryConfirmService SDCS = new StationDeliveryConfirmService();
// Event handlers registration (expalinations in the next paragraph)
SDCS.DeliveryMessageReceived += Program.DeliveryMessageReceivedHandler;
// Hosting
Uri MyBaseAddress = new Uri("http://services.myapplication.com/Services/");
using (ServiceHost hostMessagingSvc = new ServiceHost(SMS, MyBaseAddress),
ServiceHost hostDeliveryConfirmSvc = new ServiceHost(SDCS,
MyBaseAddress)) {
// Info on endpoints in config file
// Running services
hostMessagingSvc.Open();
hostDeliveryConfirmSvc.Open();
// ...
// Application's other operations
// For clarity and simplicity, just consider that the code
// here is some kind of infinite loop with actions in it
// where the GUI can commununicate with the user, somewhere
// in the application's code, there is a List where all
// sent messages are inserted and, when a delivery
// confirm arrives, the corresponding item in the list is cleared.
// The list is rendered as a table by the GUI.
// ...
/*** Arriving here somehow when my application needs to be shut down. ***/
// Closing services
hostMessagingSvc.Close();
hostDeliveryConfirmSvc.Close();
}
}
/* Event handlers for the delivery confirm messages
service (please be patient, these lines of code
will be discussed in short) */
static void DeliveryMessageReceivedHandler(object sender,
string DeliveryMessageReceivedEventArgs) {
/* Here I will perform actions on the List
deleting the row containing the ID of the
message sent whose confirm has arrived */
}
} /* Program class */
Some explainations
As you can see by the code (a code that runs and works correctly), I managed to let my hosted service communicate with the hosting application via callbacks.
So the typic flow is the following:
A neighbour of mine calls my application's void RouteMessage(... msg) service routine in order to send me a message.
In the service routine, I will check the message header and look for destination, if the destination is not me, I will route it to another neighbour of mine (closer to the destination), otherwise I will consume the message.
If I consume the message, then I'll have to send back the confirm.
I will call a neighbour of mine's void RouteDeliveryConfirmMessage(... msg) service routine in order to let it route that delivery confirm message.
Every station routes messages and, if a station finds out to be the destination, it consumes the message. but when the message is a confirm, and a station is the destination, that station will consume the confirm and will fire the DeliveryMessageReceived event causing the handler routine to start and deleting the corresponding table entry (so that the sender will have the ack knowing it is no more necessary to resend the message cause it was correctly received).
Application context
As you can see, I did not provide many details about my application, just the necessary in order to understand the code... This happens mainly for these reasons:
I do not want to bother you with my application design issues and targets.
There is much to say about why I chose some approaches, but that would be very context specific, and I would probably goig too deep in an unrelated topic.
Some may ask: "Why do you need to make a station route a message instead of providing direct messaging from the sender to the destination?", "What's the purpose of routing? Do not services let you call directly the destination station's service endpoint?". Well, I need to manage Peers in a network where peers have just a little knowledge of the entire network. New peers joins the existing one and they only have links to some station's endpoints. A peer does not need to have full knowledge of the network, it has a neighbourhood and it uses that. However, consider this as part of my requirements.
The question
OK, time for questioning. Just one question.
What I described here is a solution I managed to develop in order to let a service communicate with its hosting application. This is a problem for which I did not find a correct pattern. So I found this way of coding it...
Is it good?
Is it a best practice?
Is it a bad practice?
Should that be bad practice, what's the correct pattern/way of doing this? How to solve communication issues betwen the service and its hosting application?
Thankyou
Just one question. What I described here is a solution I managed to develop in order to let a service communicate with its hosting application.
What you described here is an approach to delivering messages from one endpoint to another on a network, without getting into any specific details of how you're planning to configure and identify the client endpoints between nodes, or why you wouldn't just send the message directly to the intended recipient. Nowhere did you attempt to discuss the very complicated matter of how your WCF service actually interacts in any way with your GUI application in a thread-safe manner. THAT would be your service communicating with its hosting application.
Although I don't have a full understanding of your application, I think what you're actually trying to accomplish is "plumbing" that is already available as a feature of WCF. I would recommend looking into WCF Discovery:
WCF Discovery
Windows Communication Foundation (WCF) provides support to enable services to be discoverable at runtime in an interoperable way using the WS-Discovery protocol. WCF services can announce their availability to the network using a multicast message or to a discovery proxy server. Client applications can search the network or a discovery proxy server to find services that meet a set of criteria. The topics in this section provide an overview and describe the programming model for this feature in detail.