
Integrate two Dynamics CRM Online tenants with Azure Service Bus and Azure Cloud Service
In this article, I would like to share my experience of using Microsoft Azure to integrate two cloud CRM systems. As part of the task, you need to build a simple cloud application that exchanges messages between two Dynamics CRM Online implementations located in different Office 365 subscriptions. We will examine the specifics of using Azure Service Bus in the context of Dynamics CRM Online, talk a bit about the supported interaction mechanisms, and use a cloud working role for the process of analyzing and processing messages.
When implementing integration that provides two branches of the company with important and relevant data, we need to ensure message delivery, even if one of the systems does not respond to requests. Naturally, in this case, we resort to the services of messaging systems that can provide storage and delayed delivery in the absence of a connection or any other problems.
Speaking about the Microsoft cloud, I would like to note that the corporation today pays enough attention to integrating various products into one homogeneous system, which allows us to simplify and speed up the process of building and deploying solutions, as well as to avoid some annoying mistakes.
If we talk about Dynamics CRM, then this product out of the box supports working with Azure Service Bus, which allows you to send your data to a queue or section without a single line of code.
There is a specificity here. In order to configure the integration between these two systems, Service Bus must know something about CRM, and CRM must correctly authenticate using the appropriate cloud bus services. Today, Dynamics CRM supports authentication through ACS (Azure Active Directory Access Control). You can read more about what ACS is in the following article: What is ACS ?
So, the first thing we need to do is actually create a Service Bus, which we will use to work with our message queue, but unfortunately it will not work to create it through a portal, since in this case Service Bus will not support authentication through ACS. In order to create a Service Bus with ACS support, we will use Azure Power Shell. Read more about what Azure Power Shell is and how to use it in the following article: What is Azure PowerShell ?
The full version of the script I used is available here .
The script is quite simple, the parameters that can be used to create the queue are described in detail in the following article: Azure Service Bus - As I Understand It: Part II (Queues & Messages) . I will add that for our integration to work correctly, it is necessary to have a clear sequence of messages, since we will process messages both for creating and for changing records, and we would not want to process a message about a record change before it was created. As a result, do not forget to put the SupportOrdering field in the corresponding value, in this case the queue will work according to the FIFO principle (First In First Out).
After the script has successfully completed on your screen, you should get something similar

Now, after everything is ready, we can make sure that the queue and bus are correctly created and accessible on the portal.

So, in order to connect Dynamics CRM to the Azure Service Bus, you need to open the Plugin Registration Tool and establish a connection with the CRM system. After the Plugins list opens, select Register and Register New Service Endpoint.

Next, in the window that opens, fill in the connection parameters.

Name is the name of our event. As an example: ContactIntegration.
Description - call description.
Solution Namespace is the name of our service bus. In my case: sb4crm2crm
Path - the name of the queue that will receive messages. In my case: q4depa2depb
Contract- messaging contract. There are several options: Queue, Topic, One - way, two - way, REST. We will consider Queue and Topic. You can read more about each of these contracts in the following article: Write a listener for a Microsoft Azure solution. For our integration, select Persistent Queue.
Claim - as additional information in the context of the message, you can send the user ID.
ID - a unique identifier for the created configuration.
After all the fields are completed, you can proceed to the configuration of ACS. To do this, click on the Save & Configure ACS button.

Management Key - This key can be obtained from the Azure portal. To do this, go to the service tire section.

Select the bus we created and click on the Connection Information button.

A window will open in which you can find all the necessary information.

We need the Default Key from the ACS section.
Certificate File - A public certificate that was used to configure Dynamics CRM for integration with Azure.
Issuer Name - Issuer name. The name must be the same as used when configuring Dynamics CRM for integration with Azure.
Certificate File and Issuer Name can be found in Dynamics CRM under Settings -> Customizations -> Developer Resources. As follows.

We download the certificate, fill out all the necessary fields and click on the Configure ACS button. If everything is correct, then after a short interval of time, you will see the following messages:

After which you can close the window by clicking the Close button.
Next, click on the Save & Verify Authentication button. We
get a message that looks like this: Verifying Authentication: Success
Close the window, click the Save button and you 're done.
Now it remains only to register what specific events we want to process and send to our service bus.
To do this, you need to register the Plugin Step, as you usually do for your plugins. I register Create and Update messages for the Contact entity. To do this, just call the context menu on the newly created Service Endpoint and select Register New Step. Filling is intuitive.

Now our created contacts will be sent to the Service Bus.
In order to track the success or failure of sending messages from Dynamics CRM, just open the system and go to Settings -> System Jobs. Select the entity of interest and load the view.
Below is a screenshot with a potential error:

It remains a small matter, to develop code that will process our messages, upload them to another system and correctly respond to potential errors.
Any workflow must be running somewhere, and in our case, it’s the Azure Cloud Service.
Let's create a new Azure Cloud Service in Visual Studio.

We further indicate that in the context of our Azure Cloud Service, we want to create a Worker Role.

Now that we have the Azure Cloud Service and Azure Worker Role, we can implement code that can receive messages from our queue. The easiest way to receive messages is given below.
Any Worker Role contains three required methods - OnStart, Run, and OnStop. Let's consider their implementation in the most general form. In the OnStart method, we determine the parameters for connecting to our bus, here you can also initiate a connection to the system into which data is to be uploaded.
The Run method is the most interesting, since here we subscribe to receive messages from our queue and configure the method for receiving data.
The code is provided with sufficiently detailed comments, so I will not comment on anything else here.
Well, and lastly, let's see what the OnStop method looks like.
Here we close all possible connections and end the Run function. You can read more about Azure Cloud Service in the following article: Detailed description of development opportunities with Microsoft Azure Cloud Services
It is also worth noting that the publication of a working role can be performed in two variations: Staging and Production. If you publish your role with the Debug assembly type, the result is a Staging deployment, if you use the Release assembly type, then Production. Even if the role is published and located in the cloud, it can still be debugged. In order to learn more about the possibilities of publishing and debugging a working role in the clouds, I suggest referring to the following article: Debugging an Azure cloud service or virtual machine in Visual Studio
In brief, I will describe how the process of processing messages in a queue is arranged, which we developed and applied in the integration of the two systems. Work begins with the CRMQueueProcessor class, its responsibilities include initializing connections, creating and configuring the “CrmMessageProcessor” message processor class, and also subscribing to receive messages from the bus. As soon as the entire initialization process is completed, and a message is received from the bus that needs to be processed, CrmMessageProcessor enters the work.
CrmMessageProcessor is an implementation of the Observer pattern. Its task is to monitor changes in the system and notify their subscribers about these changes. There can be as many subscribers as you like, each subscriber decides to process the message to him or not. All subscribers are inherited from the base class CrmBaseIntegrationHandler. CrmBaseIntegrationHandler being an abstract class, offers several methods for implementation:
getProcessingEntityName () - must be overridden, returns the name of the entity, for example, contact.
getProcessingAction () - must be overridden, returns an action or set of actions to which the handler should respond. For example: this is a record creation.
HandleCrmMessage (string entityLogicalNameValue, string requestNameValue, Entity entity) - accepts the message itself, as well as the essence and type of action, calls an overridden event handler if the event takes place to be
Entity OnProcessCreateEntity (Entity sourceEntity) - Record creation handler, it accepts an entity that came from queues and forms the entity to be created.
Entity OnProcessUpdateEntity (Entity sourceEntity) - The processor for changing a record, it takes an entity that comes from the queue and forms the entity that will be changed.
The CrmMessageProcessor class is as follows:
If none of the handlers processed the message, then it is placed in the queue of unprocessed messages with the corresponding mark. If an error occurs during the processing of the message, then we will unblock the message in the queue and try to process it again, and so until the limit of attempts is reached, after which the message falls into the queue of unprocessed messages. Next is an excerpt from the CrmQueueProcessor class.
In order to get the path to messages from the raw message queue, you need to call the FormatDeadLetterPath method on an existing instance of the QueueClient object and pass the name of the work queue as an argument:
QueueClient.FormatDeadLetterPath (queueName)
This line will form the corresponding path, then you can safely subscribe to receive messages and process them.
In the example we have analyzed, the queue is used and all messages are processed by one workflow. As an alternative to using a queue, you can also use sections (topics), they can be configured so that messages from different entities are processed by different threads within the same working role or in different ones. Each subscriber will have its own queue, and a correctly configured filter will receive only those messages that should be processed by this instance. If at the same time you need to synchronize the work of several work roles, then you can use Blob leasing for this, you can read more about synchronizing work roles in Azure in the following article: Preventing Jobs From Running Simultaneously on Multiple Role Instances.
List of articles referenced:
What is ACS ?
What is Azure PowerShell ?
How to create service bus queues, topics and subscriptions using a powershell script .
Azure Service Bus - As I Understand It: Part II (Queues & Messages) A
detailed description of development options with Microsoft Azure Cloud Services
Debugging an Azure cloud service or virtual machine in Visual Studio
Preventing Jobs From Running Simultaneously on Multiple Role Instances.
When implementing integration that provides two branches of the company with important and relevant data, we need to ensure message delivery, even if one of the systems does not respond to requests. Naturally, in this case, we resort to the services of messaging systems that can provide storage and delayed delivery in the absence of a connection or any other problems.
Speaking about the Microsoft cloud, I would like to note that the corporation today pays enough attention to integrating various products into one homogeneous system, which allows us to simplify and speed up the process of building and deploying solutions, as well as to avoid some annoying mistakes.
If we talk about Dynamics CRM, then this product out of the box supports working with Azure Service Bus, which allows you to send your data to a queue or section without a single line of code.
1. Configure Azure Service Bus to work with Dynamics CRM Online.
There is a specificity here. In order to configure the integration between these two systems, Service Bus must know something about CRM, and CRM must correctly authenticate using the appropriate cloud bus services. Today, Dynamics CRM supports authentication through ACS (Azure Active Directory Access Control). You can read more about what ACS is in the following article: What is ACS ?
So, the first thing we need to do is actually create a Service Bus, which we will use to work with our message queue, but unfortunately it will not work to create it through a portal, since in this case Service Bus will not support authentication through ACS. In order to create a Service Bus with ACS support, we will use Azure Power Shell. Read more about what Azure Power Shell is and how to use it in the following article: What is Azure PowerShell ?
[CmdletBinding(PositionalBinding=$True)]
Param(
# [Parameter(Mandatory = $true)]
# [ValidatePattern("^[a-z0-9]*$")]
[String]$Path = "q4depa2depb", # required needs to be alphanumeric
[Bool]$EnableDeadLetteringOnMessageExpiration = $True , # optional default to false
[Int]$LockDuration = 30, # optional default to 30
[Int]$MaxDeliveryCount = 10, # optional default to 10
[Int]$MaxSizeInMegabytes = 1024, # optional default to 1024
[Bool]$SupportOrdering = $True, # optional default to true
# [Parameter(Mandatory = $true)]
# [ValidatePattern("^[a-z0-9]*$")]
[String]$Namespace = "sb4crm2crm", # required needs to be alphanumeric
[Bool]$CreateACSNamespace = $True, # optional default to $false
[String]$Location = "West Europe" # optional default to "West Europe"
)
# Create Azure Service Bus namespace
$CurrentNamespace = Get-AzureSBNamespace -Name $Namespace
if ($CurrentNamespace)
{
Write-Output "The namespace [$Namespace] already exists in the [$($CurrentNamespace.Region)] region."
}
else
{
Write-Host "The [$Namespace] namespace does not exist."
Write-Output "Creating the [$Namespace] namespace in the [$Location] region..."
New-AzureSBNamespace -Name $Namespace -Location $Location -CreateACSNamespace $CreateACSNamespace -NamespaceType Messaging
$CurrentNamespace = Get-AzureSBNamespace -Name $Namespace
Write-Host "The [$Namespace] namespace in the [$Location] region has been successfully created."
}
$NamespaceManager = [Microsoft.ServiceBus.NamespaceManager]::CreateFromConnectionString($CurrentNamespace.ConnectionString);
if ($NamespaceManager.QueueExists($Path))
{
Write-Output "The [$Path] queue already exists in the [$Namespace] namespace."
}
else
{
Write-Output "Creating the [$Path] queue in the [$Namespace] namespace..."
$QueueDescription = New-Object -TypeName Microsoft.ServiceBus.Messaging.QueueDescription -ArgumentList $Path
$QueueDescription.EnableDeadLetteringOnMessageExpiration = $EnableDeadLetteringOnMessageExpiration
if ($LockDuration -gt 0)
{
$QueueDescription.LockDuration = [System.TimeSpan]::FromSeconds($LockDuration)
}
$QueueDescription.MaxDeliveryCount = $MaxDeliveryCount
$QueueDescription.MaxSizeInMegabytes = $MaxSizeInMegabytes
$QueueDescription.SupportOrdering = $SupportOrdering
$NamespaceManager.CreateQueue($QueueDescription);
Write-Host "The [$Path] queue in the [$Namespace] namespace has been successfully created."
}
The full version of the script I used is available here .
The script is quite simple, the parameters that can be used to create the queue are described in detail in the following article: Azure Service Bus - As I Understand It: Part II (Queues & Messages) . I will add that for our integration to work correctly, it is necessary to have a clear sequence of messages, since we will process messages both for creating and for changing records, and we would not want to process a message about a record change before it was created. As a result, do not forget to put the SupportOrdering field in the corresponding value, in this case the queue will work according to the FIFO principle (First In First Out).
After the script has successfully completed on your screen, you should get something similar

Now, after everything is ready, we can make sure that the queue and bus are correctly created and accessible on the portal.

2. Connect Dynamics CRM Online to the Azure Service Bus.
So, in order to connect Dynamics CRM to the Azure Service Bus, you need to open the Plugin Registration Tool and establish a connection with the CRM system. After the Plugins list opens, select Register and Register New Service Endpoint.

Next, in the window that opens, fill in the connection parameters.

Name is the name of our event. As an example: ContactIntegration.
Description - call description.
Solution Namespace is the name of our service bus. In my case: sb4crm2crm
Path - the name of the queue that will receive messages. In my case: q4depa2depb
Contract- messaging contract. There are several options: Queue, Topic, One - way, two - way, REST. We will consider Queue and Topic. You can read more about each of these contracts in the following article: Write a listener for a Microsoft Azure solution. For our integration, select Persistent Queue.
Claim - as additional information in the context of the message, you can send the user ID.
ID - a unique identifier for the created configuration.
After all the fields are completed, you can proceed to the configuration of ACS. To do this, click on the Save & Configure ACS button.

Management Key - This key can be obtained from the Azure portal. To do this, go to the service tire section.

Select the bus we created and click on the Connection Information button.

A window will open in which you can find all the necessary information.

We need the Default Key from the ACS section.
Certificate File - A public certificate that was used to configure Dynamics CRM for integration with Azure.
Issuer Name - Issuer name. The name must be the same as used when configuring Dynamics CRM for integration with Azure.
Certificate File and Issuer Name can be found in Dynamics CRM under Settings -> Customizations -> Developer Resources. As follows.

We download the certificate, fill out all the necessary fields and click on the Configure ACS button. If everything is correct, then after a short interval of time, you will see the following messages:

After which you can close the window by clicking the Close button.
Next, click on the Save & Verify Authentication button. We
get a message that looks like this: Verifying Authentication: Success
Close the window, click the Save button and you 're done.
Now it remains only to register what specific events we want to process and send to our service bus.
To do this, you need to register the Plugin Step, as you usually do for your plugins. I register Create and Update messages for the Contact entity. To do this, just call the context menu on the newly created Service Endpoint and select Register New Step. Filling is intuitive.

Now our created contacts will be sent to the Service Bus.
In order to track the success or failure of sending messages from Dynamics CRM, just open the system and go to Settings -> System Jobs. Select the entity of interest and load the view.
Below is a screenshot with a potential error:

3. Development of Worker Role for message processing.
It remains a small matter, to develop code that will process our messages, upload them to another system and correctly respond to potential errors.
Any workflow must be running somewhere, and in our case, it’s the Azure Cloud Service.
Let's create a new Azure Cloud Service in Visual Studio.

We further indicate that in the context of our Azure Cloud Service, we want to create a Worker Role.

Now that we have the Azure Cloud Service and Azure Worker Role, we can implement code that can receive messages from our queue. The easiest way to receive messages is given below.
Any Worker Role contains three required methods - OnStart, Run, and OnStop. Let's consider their implementation in the most general form. In the OnStart method, we determine the parameters for connecting to our bus, here you can also initiate a connection to the system into which data is to be uploaded.
public override bool OnStart()
{
Trace.WriteLine("Creating Queue");
string connectionString = "*** provide your connection string here***";
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
// Инициализация подключения к служебной шине
Client = QueueClient.CreateFromConnectionString(connectionString, QueueName);
return base.OnStart();
}
The Run method is the most interesting, since here we subscribe to receive messages from our queue and configure the method for receiving data.
public override void Run()
{
OnMessageOptions options = new OnMessageOptions();
options.AutoComplete = true; // Сообщение будет автоматически помечаться как отработанное и удаляться из очереди после завершения выполнения метода receivedMessage
options.MaxConcurrentCalls = 1; // Указывает максимальное число одновременных вызовов функции обратного вызова
options.ExceptionReceived += LogErrors; // Обработчик ошибок
// Start receiveing messages
Client.OnMessage((receivedMessage) => // Данный метод будет вызываться для каждого полученного сообщения
{
try
{
// Выполняем обработку сообщения
Trace.WriteLine("Processing Service Bus message: " + receivedMessage.SequenceNumber.ToString());
}
catch
{
// Перехватываем ошибки возникшие в процессе обработки сообщения
}
}, options);
CompletedEvent.WaitOne();
}
The code is provided with sufficiently detailed comments, so I will not comment on anything else here.
Well, and lastly, let's see what the OnStop method looks like.
public override void OnStop()
{
Client.Close();
CompletedEvent.Set(); //Завершаем выполнение Run функции
base.OnStop();
}
Here we close all possible connections and end the Run function. You can read more about Azure Cloud Service in the following article: Detailed description of development opportunities with Microsoft Azure Cloud Services
It is also worth noting that the publication of a working role can be performed in two variations: Staging and Production. If you publish your role with the Debug assembly type, the result is a Staging deployment, if you use the Release assembly type, then Production. Even if the role is published and located in the cloud, it can still be debugged. In order to learn more about the possibilities of publishing and debugging a working role in the clouds, I suggest referring to the following article: Debugging an Azure cloud service or virtual machine in Visual Studio
4. Architecture of integration of two CRM systems.
In brief, I will describe how the process of processing messages in a queue is arranged, which we developed and applied in the integration of the two systems. Work begins with the CRMQueueProcessor class, its responsibilities include initializing connections, creating and configuring the “CrmMessageProcessor” message processor class, and also subscribing to receive messages from the bus. As soon as the entire initialization process is completed, and a message is received from the bus that needs to be processed, CrmMessageProcessor enters the work.
CrmMessageProcessor is an implementation of the Observer pattern. Its task is to monitor changes in the system and notify their subscribers about these changes. There can be as many subscribers as you like, each subscriber decides to process the message to him or not. All subscribers are inherited from the base class CrmBaseIntegrationHandler. CrmBaseIntegrationHandler being an abstract class, offers several methods for implementation:
getProcessingEntityName () - must be overridden, returns the name of the entity, for example, contact.
getProcessingAction () - must be overridden, returns an action or set of actions to which the handler should respond. For example: this is a record creation.
HandleCrmMessage (string entityLogicalNameValue, string requestNameValue, Entity entity) - accepts the message itself, as well as the essence and type of action, calls an overridden event handler if the event takes place to be
Entity OnProcessCreateEntity (Entity sourceEntity) - Record creation handler, it accepts an entity that came from queues and forms the entity to be created.
Entity OnProcessUpdateEntity (Entity sourceEntity) - The processor for changing a record, it takes an entity that comes from the queue and forms the entity that will be changed.
public class ContactIntegrationHandler : CrmBaseIntegrationHandler
{
public override string getProcessingEntityName()
{
return "contact";
}
public override CrmMessageType getProcessingAction()
{
return CrmMessageType.Create | CrmMessageType.Update;
}
public override Entity OnProcessCreateEntity(Entity sourceEntity)
{
Entity output = new Entity("contact");
output["new_integrationid"] = sourceEntity.Id.ToString();
output["firstname"] = sourceEntity.GetAttributeValue("firstname");
output["lastname"] = sourceEntity.GetAttributeValue("lastname");
output["jobtitle"] = sourceEntity.GetAttributeValue("jobtitle");
return output;
}
public override Entity OnProcessUpdateEntity(Entity sourceEntity)
{
Entity output = new Entity("contact");
output.Id = sourceEntity.Id;
if (sourceEntity.Contains("firstname"))
{
output["firstname"] = sourceEntity.GetAttributeValue("firstname");
}
if (sourceEntity.Contains("lastname"))
{
output["lastname"] = sourceEntity.GetAttributeValue("lastname");
}
if (sourceEntity.Contains("jobtitle"))
{
output["jobtitle"] = sourceEntity.GetAttributeValue("jobtitle");
}
return output;
}
}
The CrmMessageProcessor class is as follows:
public class CrmMessageProcessor
{
List integrationSubscribers;
public CrmMessageProcessor(List subscribers)
{
this.integrationSubscribers = subscribers;
}
public void Subscribe(CrmBaseIntegrationHandler observer)
{
integrationSubscribers.Add(observer);
}
public void Unsubscribe(CrmBaseIntegrationHandler observer)
{
integrationSubscribers.Remove(observer);
}
public bool ProcessMessage(BrokeredMessage receivedMessage)
{
object entityLogicalNameValue, requestNameValue;
ExtractCrmProperties(receivedMessage, out entityLogicalNameValue, out requestNameValue);
if (entityLogicalNameValue == null || requestNameValue == null)
{
return false;
}
var context = receivedMessage.GetBody();
Entity entity = (Entity)context.InputParameters["Target"];
foreach (var handler in integrationSubscribers)
{
var status = handler.HandleCrmMessage((string)entityLogicalNameValue, (string)requestNameValue, entity);
if (status.ProcessMessgae)
{
switch (status.MessageType)
{
case CrmMessageType.Create:
{
CrmConnector.Instance.CreateEntity(status.EntityToProcess);
return true;
}
case CrmMessageType.Update:
{
var guid = CrmConnector.Instance.checkEntityForExistance(status.EntityToProcess);
if (guid != Guid.Empty)
{
status.EntityToProcess.Id = guid;
CrmConnector.Instance.UpdateEntity(status.EntityToProcess);
return true;
}
break;
}
default:
{
break;
}
}
}
}
return false;
}
///
/// Извлекает специфичные для CRM параметры сообщения
///
/// Сообщение пришедшее из шины
/// out: Название сущности
/// out: Тип действия
private void ExtractCrmProperties(BrokeredMessage receivedMessage,
out object entityLogicalNameValue, out object requestNameValue)
{
string keyRoot = "http://schemas.microsoft.com/xrm/2011/Claims/";
string entityLogicalNameKey = "EntityLogicalName";
string requestNameKey = "RequestName";
receivedMessage.Properties.TryGetValue(keyRoot + entityLogicalNameKey, out entityLogicalNameValue);
receivedMessage.Properties.TryGetValue(keyRoot + requestNameKey, out requestNameValue);
}
}
If none of the handlers processed the message, then it is placed in the queue of unprocessed messages with the corresponding mark. If an error occurs during the processing of the message, then we will unblock the message in the queue and try to process it again, and so until the limit of attempts is reached, after which the message falls into the queue of unprocessed messages. Next is an excerpt from the CrmQueueProcessor class.
public void OnMessageRecieved(BrokeredMessage receivedMessage)
{
try
{
if (processor.ProcessMessage(receivedMessage))
receivedMessage.Complete();
else
receivedMessage.DeadLetter("Canceled", "No event handler found");
}
catch (Exception ex)
{
receivedMessage.Abandon();
logger.LogCrmMessageException(receivedMessage, ex);
}
}
In order to get the path to messages from the raw message queue, you need to call the FormatDeadLetterPath method on an existing instance of the QueueClient object and pass the name of the work queue as an argument:
QueueClient.FormatDeadLetterPath (queueName)
This line will form the corresponding path, then you can safely subscribe to receive messages and process them.
5. Conclusion
In the example we have analyzed, the queue is used and all messages are processed by one workflow. As an alternative to using a queue, you can also use sections (topics), they can be configured so that messages from different entities are processed by different threads within the same working role or in different ones. Each subscriber will have its own queue, and a correctly configured filter will receive only those messages that should be processed by this instance. If at the same time you need to synchronize the work of several work roles, then you can use Blob leasing for this, you can read more about synchronizing work roles in Azure in the following article: Preventing Jobs From Running Simultaneously on Multiple Role Instances.
List of articles referenced:
What is ACS ?
What is Azure PowerShell ?
How to create service bus queues, topics and subscriptions using a powershell script .
Azure Service Bus - As I Understand It: Part II (Queues & Messages) A
detailed description of development options with Microsoft Azure Cloud Services
Debugging an Azure cloud service or virtual machine in Visual Studio
Preventing Jobs From Running Simultaneously on Multiple Role Instances.