Client Application
Since the Windows Forms application exchanges messages with the ServiceBusSample BizTalk application asynchronously through Service Bus messaging entities, it acts as a client and a service application at the same time. Windows Forms uses WCF and the NetMessagingBinding to perform the following actions:
-
Send request messages to the requestqueue.
-
Send request messages to the requesttopic.
-
Receive response messages from the responsequeue.
-
Receive response messages from the ItalyMilan subscription of the responsetopic.
Let’s start reviewing the configuration file of the client application that plays a central role in the definition of the WCF client and service endpoints used to communicate with the Service Bus.
App.Config
1: <?xml version="1.0"?>
2: <configuration>
3: <system.diagnostics>
4: <sources>
5: <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
6: <listeners>
7: <add type="System.Diagnostics.DefaultTraceListener" name="Default">
8: <filter type="" />
9: </add>
10: <add name="ServiceModelMessageLoggingListener">
11: <filter type="" />
12: </add>
13: </listeners>
14: </source>
15: </sources>
16: <sharedListeners>
17: <add initializeData="C:\ServiceBusQueueClient.svclog"
18: type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
19: name="ServiceModelMessageLoggingListener"
20: traceOutputOptions="Timestamp">
21: <filter type="" />
22: </add>
23: </sharedListeners>
24: <trace autoflush="true" indentsize="4">
25: <listeners>
26: <clear/>
27: <add name="LogTraceListener"
28: type="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Client.LogTraceListener, Client"
29: initializeData="" />
30: </listeners>
31: </trace>
32: </system.diagnostics>
33: <startup>
34: <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
35: </startup>
36: <system.serviceModel>
37: <diagnostics>
38: <messageLogging logEntireMessage="true"
39: logMalformedMessages="false"
40: logMessagesAtServiceLevel="true"
41: logMessagesAtTransportLevel="false" />
42: </diagnostics>
43: <behaviors>
44: <endpointBehaviors>
45: <behavior name="securityBehavior">
46: <transportClientEndpointBehavior>
47: <tokenProvider>
48: <sharedSecret issuerName="owner"
49: issuerSecret="[ISSUER SECRET RETRIEVED FROM THE AZURE MGMT PORTAL]" />
50: </tokenProvider>
51: </transportClientEndpointBehavior>
52: </behavior>
53: </endpointBehaviors>
54: </behaviors>
55: <bindings>
56: <basicHttpBinding>
57: <binding name="basicHttpBinding"
58: closeTimeout="00:10:00"
59: openTimeout="00:10:00"
60: receiveTimeout="00:10:00"
61: sendTimeout="00:10:00">
62: <security mode="None">
63: <transport clientCredentialType="None" proxyCredentialType="None"
64: realm="" />
65: <message clientCredentialType="UserName" algorithmSuite="Default" />
66: </security>
67: </binding>
68: </basicHttpBinding>
69: <basicHttpRelayBinding>
70: <binding name="basicHttpRelayBinding"
71: closeTimeout="00:10:00"
72: openTimeout="00:10:00"
73: receiveTimeout="00:10:00"
74: sendTimeout="00:10:00">
75: <security mode="Transport" relayClientAuthenticationType="RelayAccessToken" />
76: </binding>
77: </basicHttpRelayBinding>
78: <netTcpRelayBinding>
79: <binding name="netTcpRelayBinding"
80: closeTimeout="00:10:00"
81: openTimeout="00:10:00"
82: receiveTimeout="00:10:00"
83: sendTimeout="00:10:00">
84: <security mode="Transport" relayClientAuthenticationType="RelayAccessToken" />
85: </binding>
86: </netTcpRelayBinding>
87: <netMessagingBinding>
88: <binding name="netMessagingBinding"
89: sendTimeout="00:03:00"
90: receiveTimeout="00:03:00"
91: openTimeout="00:03:00"
92: closeTimeout="00:03:00"
93: sessionIdleTimeout="00:01:00"
94: prefetchCount="-1">
95: <transportSettings batchFlushInterval="00:00:01" />
96: </binding>
97: </netMessagingBinding>
98: </bindings>
99: <client>
100: <!-- Invoke BizTalk via Service Bus Queue -->
101: <endpoint address="sb://paolosalvatori.servicebus.windows.net/requestqueue"
102: behaviorConfiguration="securityBehavior"
103: binding="netMessagingBinding"
104: bindingConfiguration="netMessagingBinding"
105: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorRequest"
106: name="requestQueueClientEndpoint" />
107: <!-- Invoke BizTalk via Service Bus Topic -->
108: <endpoint address="sb://paolosalvatori.servicebus.windows.net/requesttopic"
109: behaviorConfiguration="securityBehavior"
110: binding="netMessagingBinding"
111: bindingConfiguration="netMessagingBinding"
112: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorRequest"
113: name="BizTalkServiceBusTopic" />
114: <!-- Invoke BizTalk via Service Bus Relay Service -->
115: <endpoint address="sb://paolosalvatori.servicebus.windows.net/nettcp/calculatorservice"
116: behaviorConfiguration="securityBehavior"
117: binding="netTcpRelayBinding"
118: bindingConfiguration="netTcpRelayBinding"
119: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorService"
120: name="netTcpRelayBindingClientEndpoint" />
121: <!-- Invoke BizTalk directly via WCF Receive Location -->
122: <endpoint address="http://localhost/newcalculatorservice/calculatorservice.svc"
123: binding="basicHttpBinding"
124: bindingConfiguration="basicHttpBinding"
125: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorService"
126: name="basicHttpBindingClientEndpoint" />
127: </client>
128: <services>
129: <service name="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Service.ResponseHandlerService">
130: <endpoint address="sb://paolosalvatori.servicebus.windows.net/responsequeue"
131: behaviorConfiguration="securityBehavior"
132: binding="netMessagingBinding"
133: bindingConfiguration="netMessagingBinding"
134: name="responseQueueServiceEndpoint"
135: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorResponse" />
136: <endpoint address="sb://paolosalvatori.servicebus.windows.net/responsetopic"
137: listenUri="sb://paolosalvatori.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
138: behaviorConfiguration="securityBehavior"
139: binding="netMessagingBinding"
140: bindingConfiguration="netMessagingBinding"
141: name="responseSubscriptionServiceEndpoint"
142: contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorResponse" />
143: </service>
144: </services>
145: </system.serviceModel>
146: </configuration>
Please find below a brief description of the main elements and sections of the configuration file:
-
Lines [3-32] define a custom trace listener called LogTraceListener used by the ResponseHandlerService to write response message to the log control of the Windows Forms application.
-
Lines [33-35] the startup section specifies which versions of the common language runtime the application supports.
-
Lines [45-52] contain the definition of the securityBehavior used by client and service endpoint to authenticate with the Access Control Service. In particular, the TransportClientEndpointBehavior is used to define shared secret credentials. For more information on how to retrieve credentials from the Windows Azure Management Portal, see the box below.
-
Lines [87-97] contain the configuration of the NetMessagingBinding used client and service endpoints to exchange messages with the Service Bus.
-
Lines [101-106] contain the definition of the requestQueueClientEndpoint used by the application to send request messages to the requestqueue. The address of the client endpoint is given by the concatenation of the URL of the service namespace and the name of the queue.
-
Lines [108-113] contain the definition of the requestTopicClientEndpoint used by the application to send request messages to the requesttopic. The address of the client endpoint is given by the concatenation of the URL of the service namespace and the name of the topic
-
Lines [130-135] contain the definition of the responseQueueServiceEndpoint used by the application to receive response messages from the responsequeue. The address of the service endpoint is given by the concatenation of the URL of the service namespace and the name of the queue.
-
Lines [108-113] contain the definition of the responseSubscriptionServiceEndpoint used by the application to send receive response messages from the ItalyMilan subscription for the responsetopic. When you define a WCF service endpoint that uses the NetMessagingBinding to receive messages from a subscription, you have to proceed as follows (for more information on this, see the box below):
-
As value of the address attribute, specify the URL of the topic which the subscription belongs to. The URL of the topic is given by the concatenation of the URL of the service namespace and the name of the topic.
-
As value of the listenUri attribute, specify the URL of the subscription. The URL of the subscription is defined by the concatenation of the topic URL, the string /Subscriptions/, and the name of the subscription.
-
Assign the value Explicit to the listenUriMode attribute. The default value for the listenUriMode is Explicit, so this setting is optional.
-
As value of the address attribute, specify the URL of the topic which the subscription belongs to. The URL of the topic is given by the concatenation of the URL of the service namespace and the name of the topic.
メモ |
|---|
| When you configure a WCF service endpoint to consume messages from a sessionful queue or subscription, the service contract needs to support sessions. Therefore, in our sample, when you configure the responseQueueServiceEndpoint or responseSubscriptionServiceEndpoint endpoints to receive, respectively, from a sessionful queue and subscription, you have to replace the ICalculatorResponse service contract with the sessionful ICalculatorResponseSessionful contract interface. For more information, see the Service Contracts section later in the article. |
メモ |
|---|
| The Service Bus supports three different types of credential schemes: SAML, Shared Secret, and Simple Web Token, but this version of the Service Bus Explorer supports only Shared Secret credentials. However, you can easily extend my code to support other credential schemes. You can retrieve the issuer-secret key from the Windows Azure Management Portal by clicking the View button highlighted in red in the picture below after selecting a certain namespace in the Service Bus section. |
This opens up the modal dialog shown in the picture below where you can retrieve the key by clicking the Copy to Clipboard button highlighted in red.
メモ |
|---|
| By convention, the name of the Default Issuer is always owner. |
メモ |
|---|
| When you define a WCF service endpoint that uses the NetMessagingBinding to receive messages from a subscription, if you make the mistake to assign the URL of the subscription to the address attribute of the service endpoint (as reported in configuration below), at runtime an FaultException like the following will occur: |
The message with To 'sb://paolosalvatori.servicebus.windows.net/responsetopic' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher. Check that the sender and receiver's EndpointAddresses agree."}
Wrong Configuration
<?xml version="1.0"?>
<configuration>
...
<system.serviceModel>
...
<services>
<service name="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Service.ResponseHandlerService">
<endpoint address="sb://paolosalvatori.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
behaviorConfiguration="securityBehavior"
binding="netMessagingBinding"
bindingConfiguration="netMessagingBinding"
name="responseSubscriptionServiceEndpoint"
contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorResponse" />
</service>
</services>
</system.serviceModel>
</configuration>
The error is due to the fact the WS-Addressing To header of the message contains the address of the topic and not the address of the subscription:
<:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">ReceiveResponse</a:Action>
<a:MessageID>urn:uuid:64cb0b06-1622-4920-a035-c27b610cfcaf</a:MessageID>
<a:To s:mustUnderstand="1">sb://paolosalvatori.servicebus.windows.net/responsetopic</a:To>
</s:Header>
<s:Body>... stream ...</s:Body>
</s:Envelope>
To correctly configure the service endpoint to receive messages from a subscription, you have to proceed as follows:
-
As value of the address attribute, you have specify the URL of the topic which the subscription belongs to. The URL of the topic is given by the concatenation of the URL of the service namespace and the name of the topic.
-
As value of the listenUri attribute, you have to specify the URL of the subscription. The URL of the subscription is defined by the concatenation of the topic URL, the string /Subscriptions/ and the name of the subscription.
-
Assign the value Explicit to the listenUriMode attribute. The default value for the listenUriMode is Explicit, so this setting is optional.
See the following page on MSDN for a description of the address, listenUri and listenUriMode attributes.
Correct Configuration
<?xml version="1.0"?>
<configuration>
...
<system.serviceModel>
...
<services>
<service name="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Service.ResponseHandlerService">
<endpoint address="sb://paolosalvatori.servicebus.windows.net/responsetopic"
listenUri="sb://paolosalvatori.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
behaviorConfiguration="securityBehavior"
binding="netMessagingBinding"
bindingConfiguration="netMessagingBinding"
name="subscriptionEndpoint"
contract="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts.ICalculatorResponse" />
</service>
</services>
</system.serviceModel>
</configuration>
To accomplish the same task via API, you have to properly set the value of the Address, ListenUri and ListenUriMode properties of your ServiceEndpoint instance as indicated in this note.
The following example shows the code used by the client application to start the ResponseHandlerService used to read response messages from the responsequeue and the ItalyMilan subscription of the responsetopic. We’ll examine the code of the service in the next section.
StartServiceHost Method
private void StartServiceHost()
{
try
{
// Creating the service host object as defined in config
var serviceHost = new ServiceHost(typeof(ResponseHandlerService));
// Add ErrorServiceBehavior for handling errors encounter by servicehost during execution.
serviceHost.Description.Behaviors.Add(new ErrorServiceBehavior());
foreach (var serviceEndpoint in serviceHost.Description.Endpoints)
{
if (serviceEndpoint.Name == "responseQueueServiceEndpoint")
{
responseQueueUri = serviceEndpoint.Address.Uri.AbsoluteUri;
WriteToLog(string.Format(ServiceHostListeningOnQueue,
serviceEndpoint.Address.Uri.AbsoluteUri));
}
if (serviceEndpoint.Name == "responseSubscriptionServiceEndpoint")
{
responseTopicUri = serviceEndpoint.Address.Uri.AbsoluteUri;
WriteToLog(string.Format(ServiceHostListeningOnSubscription,
serviceEndpoint.ListenUri.AbsoluteUri));
}
}
// Start the service host
serviceHost.Open();
WriteToLog(ServiceHostSuccessfullyOpened);
}
catch (Exception ex)
{
mainForm.HandleException(ex);
}
}
The following picture shows the user interface of the client application.
The radio buttons contained in the Request Method group allow you to choose whether to send the request message to the requestqueue or the requesttopic, whereas the radio buttons contained in the Response Method group allows you to select whether to receive the response from the responsequeue or from the ItalyMilan subscription of the responsetopic. To communicate the selection to the underlying BizTalk application, the application uses a BrokeredMessageProperty object to assign the value of the responseQueueUri or responseTopicUri private fields to the ReplyTo property. The following table contains the code of the method used by the client application. For your convenience, comments have been added to the code to facilitate its understanding.
SendRequestMessageUsingWCF Method
private void SendRequestMessageUsingWCF(string endpointConfigurationName)
{
try
{
if (string.IsNullOrEmpty(endpointConfigurationName))
{
WriteToLog(EndpointConfigurationNameCannotBeNull);
return;
}
// Set the wait cursor
Cursor = Cursors.WaitCursor;
// Make sure that the request message contains at least an operation
if (operationList == null ||
operationList.Count == 0)
{
WriteToLog(OperationListCannotBeNull);
return;
}
// Create warning collection
var warningCollection = new List<string>();
// Create request message
var calculatorRequest = new CalculatorRequest(operationList);
var calculatorRequestMessage = new CalculatorRequestMessage(calculatorRequest);
// Create the channel factory for the currennt client endpoint
// and cache it in the channelFactoryDictionary
if (!channelFactoryDictionary.ContainsKey(endpointConfigurationName))
{
channelFactoryDictionary[endpointConfigurationName] =
new ChannelFactory<ICalculatorRequest>(endpointConfigurationName);
}
// Create the channel for the currennt client endpoint
// and cache it in the channelDictionary
if (!channelDictionary.ContainsKey(endpointConfigurationName))
{
channelDictionary[endpointConfigurationName] =
channelFactoryDictionary[endpointConfigurationName].CreateChannel();
}
// Use the OperationContextScope to create a block within which to access the current OperationScope
using (new OperationContextScope((IContextChannel)channelDictionary[endpointConfigurationName]))
{
// Create a new BrokeredMessageProperty object
var brokeredMessageProperty = new BrokeredMessageProperty();
// Read the user defined properties and add them to the
// Properties collection of the BrokeredMessageProperty object
foreach (var e in propertiesBindingSource.Cast<PropertyInfo>())
{
try
{
e.Key = e.Key.Trim();
if (e.Type != StringType && e.Value == null)
{
warningCollection.Add(string.Format(CultureInfo.CurrentUICulture,
PropertyValueCannotBeNull, e.Key));
}
else
{
if (brokeredMessageProperty.Properties.ContainsKey(e.Key))
{
brokeredMessageProperty.Properties[e.Key] =
ConversionHelper.MapStringTypeToCLRType(e.Type, e.Value);
}
else
{
brokeredMessageProperty.Properties.Add(e.Key,
ConversionHelper.MapStringTypeToCLRType(e.Type, e.Value));
}
}
}
catch (Exception ex)
{
warningCollection.Add(string.Format(CultureInfo.CurrentUICulture,
PropertyConversionError, e.Key, ex.Message));
}
}
// if the warning collection contains at least one or more items,
// write them to the log and return immediately
StringBuilder builder;
if (warningCollection.Count > 0)
{
builder = new StringBuilder(WarningHeader);
var warnings = warningCollection.ToArray<string>();
for (var i = 0; i < warningCollection.Count; i++)
{
builder.AppendFormat(WarningFormat, warnings[i]);
}
mainForm.WriteToLog(builder.ToString());
return;
}
// Set the BrokeredMessageProperty properties
brokeredMessageProperty.Label = txtLabel.Text;
brokeredMessageProperty.MessageId = Guid.NewGuid().ToString();
brokeredMessageProperty.SessionId = sessionId;
brokeredMessageProperty.ReplyToSessionId = sessionId;
brokeredMessageProperty.ReplyTo = responseQueueRadioButton.Checked
? responseQueueUri
: responseTopicUri;
OperationContext.Current.OutgoingMessageProperties.Add(BrokeredMessageProperty.Name,
brokeredMessageProperty);
// Send the request message to the requestqueue or requesttopic
var stopwatch = new Stopwatch();
try
{
stopwatch.Start();
channelDictionary[endpointConfigurationName].SendRequest(calculatorRequestMessage);
}
catch (CommunicationException ex)
{
if (channelFactoryDictionary[endpointConfigurationName] != null)
{
channelFactoryDictionary[endpointConfigurationName].Abort();
channelFactoryDictionary.Remove(endpointConfigurationName);
channelDictionary.Remove(endpointConfigurationName);
}
HandleException(ex);
}
catch (Exception ex)
{
if (channelFactoryDictionary[endpointConfigurationName] != null)
{
channelFactoryDictionary[endpointConfigurationName].Abort();
channelFactoryDictionary.Remove(endpointConfigurationName);
channelDictionary.Remove(endpointConfigurationName);
}
HandleException(ex);
}
finally
{
stopwatch.Stop();
}
// Log the request message and its properties
builder = new StringBuilder();
builder.AppendLine(string.Format(CultureInfo.CurrentCulture,
MessageSuccessfullySent,
channelFactoryDictionary[endpointConfigurationName].Endpoint.Address.Uri.AbsoluteUri,
brokeredMessageProperty.MessageId,
brokeredMessageProperty.SessionId,
brokeredMessageProperty.Label,
stopwatch.ElapsedMilliseconds));
builder.AppendLine(PayloadFormat);
for (var i = 0; i < calculatorRequest.Operations.Count; i++)
{
builder.AppendLine(string.Format(RequestFormat,
i + 1,
calculatorRequest.Operations[i].Operand1,
calculatorRequest.Operations[i].Operator,
calculatorRequest.Operations[i].Operand2));
}
builder.AppendLine(SentMessagePropertiesHeader);
foreach (var p in brokeredMessageProperty.Properties)
{
builder.AppendLine(string.Format(MessagePropertyFormat,
p.Key,
p.Value));
}
var traceMessage = builder.ToString();
WriteToLog(traceMessage.Substring(0, traceMessage.Length - 1));
}
}
catch (Exception ex)
{
// Handle the exception
HandleException(ex);
}
finally
{
// Restoire the defaulf cursor
Cursor = Cursors.Default;
}
}
ビルド日:
メモ