|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI work in an environment that utilizes BizTalk to process messages from various sources and distribute to numerous send ports. Now, one could utilize BizTalk as an ESB; however, this poses some issues, mainly a new send port needs to be created for each new application which needs messages routed to it. That is, unless one could guarantee all developers would only peek into a single message queue and never dequeue a message. Honestly, that would lead to more problems than it solves, not to mention it would be terrible practice. This led me to using WCF Callback Services as a single entry point in our ESB (Enterprise Service Bus). BackgroundConsidering WCF (Windows Communication Foundation) supports callback contracts and thus Event Driven Architecture, one could easily create a send port in BizTalk to a message queue (MSMQ, in this case), then expose a WCF Event Driven Web Service which numerous clients could easily subscribe to and receive messages from. Along my journey, I came across a lot of questions and issues. This is the reason I’m writing this entry today, to hopefully stop some of you from wasting too much time looking. Using the CodeLet’s start with the easy part, the service contracts. This includes the callback contract as well as operations to allow clients to subscribe and unsubscribe. I will use the most basic web service I can think of, as to not stray from the actual issues I came across. [ServiceContract(SessionMode=SessionMode.Required,
CallbackContract=typeof(IEventSystemCallback))]
interface IEventSystem
{
[OperationContract(IsOneWay=true)]
void Subscribe();
[OperationContract(IsOneWay = true)]
void Unsubscribe();
}
interface IEventSystemCallback
{
[OperationContract(IsOneWay = true)]
void OnMessageReceived(string message);
}
Now, this is really all it takes to generate an event driven web service in WCF; at least, as far as the contracts are concerned. If you notice, there are two operations which will be exposed through the service ( The Now, let’s get into the hard stuff. We’ll start with the service itself. I’ll show you the entire service implementation, and then we’ll break it down. [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
internal sealed class EventService:IEventSystem
{
public delegate void CallbackDelegate<T>(T t);
public static CallbackDelegate<string> MessageReceived;
#region IEventSystem Members
public void Subscribe()
{
IEventSystemCallback callback =
OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
MessageReceived += callback.OnMessageReceived;
ICommunicationObject obj = (ICommunicationObject)callback;
obj.Closed += new EventHandler(EventService_Closed);
obj.Closing += new EventHandler(EventService_Closing);
}
void EventService_Closing(object sender, EventArgs e)
{
Console.WriteLine("Client Closing...");
}
void EventService_Closed(object sender, EventArgs e)
{
MessageReceived -= ((IEventSystemCallback)sender).OnMessageReceived;
Console.WriteLine("Closed Client Removed!");
}
public void Unsubscribe()
{
IEventSystemCallback callback =
OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
MessageReceived -= callback.OnMessageReceived;
}
#endregion
public static void SendMessage(string Message)
{
try
{
MessageReceived(Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static void NotifyServiceStop()
{
SendMessage("Service Stopping...");
}
}
First, let’s start with the service itself: [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
internal sealed class EventService:IEventSystem
Here, we have implemented the *Note: public delegate void CallbackDelegate<T>(T t);
public static CallbackDelegate<string> MessageReceived;
One of the most difficult things to grasp about a Callback Service is where to store all of the subscribers. This is why I’ve created the static public void Subscribe()
{
IEventSystemCallback callback =
OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
MessageReceived += callback.OnMessageReceived;
ICommunicationObject obj = (ICommunicationObject)callback;
obj.Closed += new EventHandler(EventService_Closed);
obj.Closing += new EventHandler(EventService_Closing);
}
void EventService_Closing(object sender, EventArgs e)
{
Console.WriteLine("Client Closing...");
}
void EventService_Closed(object sender, EventArgs e)
{
MessageReceived -= ((IEventSystemCallback)sender).OnMessageReceived;
Console.WriteLine("Closed Client Removed!");
}
public void Unsubscribe()
{
IEventSystemCallback callback =
OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
MessageReceived -= callback.OnMessageReceived;
}
The Lastly, I create a few methods to actually send the messages to the subscribers. With the actual ESB Service, This is where the MSMQ monitoring would occur and fire all the new messages to the subscribers. public static void SendMessage(string Message)
{
try
{
MessageReceived(Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static void NotifyServiceStop()
{
SendMessage("Service Stopped!");
}
I also added a I have hosted this demo in a console application using a class Program
{
private static int count;
public static int Count
{
get
{
count += 1;
return count;
}
}
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(EventService));
host.Closing += new EventHandler(host_Closing);
host.Open();
Console.WriteLine("Service Started");
Timer timer = new Timer(delegate(object state)
{
string message = "Message " + Count.ToString();
try
{
if (EventService.MessageReceived != null)
{
Console.WriteLine(
EventService.MessageReceived.GetInvocationList().Length.ToString() +
" Subscribers");
EventService.SendMessage(message);
Console.WriteLine("Sent: " + message);
}
else
{
Console.WriteLine("0 Subscribers");
Console.WriteLine("Skipped: " + message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}, null, 5000, 5000);
Console.ReadKey(true);
host.Close();
}
static void host_Closing(object sender, EventArgs e)
{
try
{
EventService.NotifyServiceStop();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
There’s really no magic here. Just your ordinary Onto the configuration … This is where most of the confusion came for me, and in fact, there are still some questions I have that you might be able to help out answering. <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ExposeMexAndThrottleBehavior">
<serviceMetadata httpGetEnabled="true"
httpGetUrl="http://localhost:1111/EventService/Mex"/>
<serviceThrottling maxConcurrentCalls="3"
maxConcurrentInstances="100"
maxConcurrentSessions="100"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ESBService.EventService"
behaviorConfiguration="ExposeMexAndThrottleBehavior">
<endpoint address="net.tcp://localhost:9999/EventService/"
binding="netTcpBinding"
contract="ESBService.IEventSystem"/>
<endpoint address="http://localhost:1111/EventService/Mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
</configuration>
I won’t be going into how to create an endpoint or expose metadata, as there are numerous examples on the web. The interesting thing to look at in this config is the <bindings>
<netTcpBinding>
<binding name="tcpSettings" maxConnections="50"/>
</netTcpBinding>
</bindings>
This setting is still confusing to me actually, because no matter what I set it to, it has no effect on my service. I read that you can “Throttle” the service using either the The solution was presented with the use of the To describe what these throttling settings mean, let’s go into them now. Remember that I used a <serviceThrottling maxConcurrentCalls="3"
maxConcurrentInstances="100"
maxConcurrentSessions="100"/>
The The Default values when not specified (or no
Now, your throttling will be set differently if you use a Client code:public partial class Form1 : Form,ESB.IEventSystemCallback
{
public Form1()
{
InitializeComponent();
}
ESB.EventSystemClient client;
private void Form1_Load(object sender, EventArgs e)
{
try
{
client =
new ESBClient.ESB.EventSystemClient(new InstanceContext(this));
client.Subscribe();
}
catch (Exception ex)
{
listBox1.Items.Add(ex.ToString());
}
}
#region IEventSystemCallback Members
public void OnMessageReceived(string message)
{
listBox1.Items.Add(message);
}
#endregion
* To use the sample code, open the client and service solutions in two separate VS2008 IDEs. Run the Service, then use CTRL+F5 to open multiple clients. Open and close as many clients you want. You can also adjust the throttling settings, then rinse and repeat.
|
|||||||||||||||||||||||||||||||||||||||||||||||