Esporta (0) Stampa
Espandi tutto
Informazioni
L'argomento richiesto è visualizzato di seguito, ma non è incluso in questa libreria.

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:

  1. Send request messages to the requestqueue.

  2. Send request messages to the requesttopic.

  3. Receive response messages from the responsequeue.

  4. 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.

noteNota
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.

noteNota
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.

Pulsante Visualizza del portale di gestione della piattaforma Windows Azure

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.

Finestra di dialogo della chiave predefinita del portale di gestione della piattaforma Windows Azure
noteNota
By convention, the name of the Default Issuer is always owner.

noteNota
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.

Client coda applicazione Service Bus

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;
    }
}


Data di compilazione:

2013-10-23

Aggiunte alla community

Mostra:
© 2014 Microsoft