Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 83

Processing Dataverse and Service Bus Messages with C# Azure Functions

Recommended resources

Hello! Welcome back to our module on Azure and Power Platform Integration Patterns.

In our last lesson, we focused on the "sending" side of integration, configuring Dataverse to push event data to an external endpoint using webhooks. Now, it's time to build the "receiving" end.

Your goal for this lesson is to learn how to develop a C# Azure Function to process data received from a Dataverse webhook or Service Bus message. Given your extensive background in C# and .NET, this lesson will feel very natural, extending your existing plugin development skills into the serverless world of Azure.

We will cover two primary patterns:

  1. Creating an HTTP-triggered function to act as a webhook endpoint.
  2. Creating a Service Bus-triggered function to process messages from a queue.

In both cases, you'll learn how to parse the incoming Dataverse execution context and prepare to act on it.

1. Architectural Overview: Webhooks vs. Service Bus

From the perspective of the C# developer building the receiver, the main difference between handling a webhook and a Service Bus message is the trigger mechanism that invokes your code.

  • Webhook Pattern: Dataverse sends a direct HTTP POST request. Your Azure Function uses an HTTP Trigger to activate, and you read the data from the HTTP request body.
  • Service Bus Pattern: Dataverse places a message in a queue. Your Azure Function uses a Service Bus Trigger that continuously listens to the queue. When a message arrives, the function is activated, and the message content is passed directly as a parameter.

The diagram below illustrates the Service Bus pattern, where a Dataverse plugin (an "Azure Aware Plugin") sends a message to a Service Bus Queue, which in turn triggers an Azure Function for processing.

Dataverse to Azure Function Asynchronous Integration via Service Bus
This diagram shows the asynchronous integration pattern using Azure Service Bus. An update in Dataverse sends a message to a queue, which an Azure Function then processes.

The core payload for both patterns is the same: a JSON string representing the RemoteExecutionContext, which contains all the familiar data from your plugin development days (Target, PreEntityImages, PostEntityImages, etc.).


2. Developing an HTTP-Triggered Function for Webhooks

Let's start with the direct follow-up to our previous lesson: building the C# function to receive the webhook call. We will use the Microsoft Learning practice lab as a guide. While the lab's end goal is a custom connector, the initial steps of creating and coding the HTTP-triggered function are identical to what you need for a webhook receiver.

Practice Lab 9 – Azure

This practice lab from Microsoft Learning will guide you through creating an HTTP-triggered Azure Function directly in the Azure Portal. We'll use it to build our webhook receiver.

Please follow the steps in Exercise 4: Create an Azure Function. This will guide you through creating the necessary Azure resources (Storage Account, Function App) and an HTTP-triggered C# function. Pay close attention to Task 4.3: Create an Azure Function in the Azure Portal, especially step 3 where you edit the function's C# code. The provided code is a perfect starting point for receiving and inspecting the webhook payload.

The key piece of code from that lab exercise is:

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    
    // Deserialize the JSON into a dynamic object to inspect it
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    
    // For debugging, log the formatted JSON to see the structure
    string indentedJson = JsonConvert.SerializeObject(data, Formatting.Indented);
    log.LogInformation(indentedJson);

    // In a real scenario, you'd process the data here.
    // For now, we just acknowledge receipt.
    return new OkObjectResult("Request received successfully.");
}

Deserializing the Execution Context

While deserializing to a dynamic object is great for quick inspection, for robust, production-grade code, you should deserialize to a strongly-typed object. The Microsoft.Xrm.Sdk assembly, which you are familiar with, contains the RemoteExecutionContext class for this purpose.

To use it, you would first need to add a reference to the Microsoft.CrmSdk.CoreAssemblies NuGet package in your function project. Then, you could modify the code:

// Add this using statement at the top
using Microsoft.Xrm.Sdk;
using Newtonsoft.Json;

// ... inside the Run method ...
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

// Deserialize into the strongly-typed RemoteExecutionContext
RemoteExecutionContext context = JsonConvert.DeserializeObject<RemoteExecutionContext>(requestBody);

// Now you can access properties in a familiar, type-safe way
if (context.MessageName == "Update")
{
    Entity target = context.InputParameters["Target"] as Entity;
    Entity postImage = context.PostEntityImages["PermitImage"]; // Using the alias you registered
    log.LogInformation($"Processing update for permit: {postImage.GetAttributeValue<string>("contoso_name")}");
}

This approach directly translates your plugin development experience to Azure Functions.


3. Developing a Service Bus-Triggered Function

Now, let's tackle the second pattern: processing messages from an Azure Service Bus queue. This pattern is often preferred for more critical or high-volume integrations due to the reliability and queuing features of Service Bus.

A core concept here is Azure Function Bindings. Bindings are a declarative way to connect to services like Service Bus from your function, drastically reducing the boilerplate code you need to write.

Azure Functions Bindings Tutorial | Service Bus | Storage

This video provides an excellent conceptual overview of what Azure Function bindings are and how they simplify code. It will help you understand the 'magic' behind the Service Bus trigger.

Please watch the first four minutes of the video: What are Bindings? (00:00 - 01:30): The video explains how bindings replace manual SDK clients and connection code. Configuring Bindings (01:30 - 04:24): Observe how C# attributes ([ServiceBus]) and NuGet packages are used in Visual Studio to configure an output binding. The same principle applies to input triggers.

With that conceptual foundation, let's walk through a complete, Dataverse-specific example of building a Service Bus-triggered function using Visual Studio, which is the standard for professional development.

Dataverse + Azure Service Bus Queue + Azure Function for ...

This blog post by developer Temmy Raharjo is a perfect end-to-end guide. It shows how to create a Service Bus-triggered function in Visual Studio, deserialize the RemoteExecutionContext, and connect back to Dataverse using best practices like dependency injection.

Please read the sections titled 'Create Azure Functions' and the subsequent code blocks. Focus on how a new 'Service Bus Queue trigger' project is created in Visual Studio. Note the required NuGet packages, especially Microsoft.PowerPlatform.Dataverse.Client. Study the Function1.cs code. Pay attention to how the [ServiceBusTrigger] attribute is used to define the function's trigger. Analyze the logic inside the function: it deserializes the incoming queue message string into a RemoteExecutionContext, just like in the webhook example.

The essential part of the function code from the article looks like this:

// The 'ServiceBusTrigger' attribute does all the work of connecting to the queue
[FunctionName("ProcessQueueFunction")]
public void Run([ServiceBusTrigger("demo-queue", Connection = "ServiceBusConnectionString")]string myQueueItem, ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");

    try
    {
        // The queue item is the JSON string of the context
        var context = JsonConvert.DeserializeObject<RemoteExecutionContext>(myQueueItem);

        // From here, it's just like writing a plugin
        if (context.PostEntityImages.Contains("PostImage") && context.PostEntityImages["PostImage"] is Entity)
        {
            var postImage = context.PostEntityImages["PostImage"];
            // ... your business logic here ...
        }
    }
    catch (Exception ex)
    {
        log.LogError(ex.ToString());
        throw; // Re-throw to have Service Bus retry the message
    }
}
Test your understanding!

In your C# Azure Function (triggered by either a webhook or Service Bus), you've deserialized the RemoteExecutionContext. The event was an Update on the opportunity entity. Your step was registered with a Pre-Image and a Post-Image, both with the alias "OpportunityImage".

How would you write the C# code to retrieve the estimatedvalue from before the update and the actualvalue from after the update?

Show answer
// Assuming 'context' is your deserialized RemoteExecutionContext object

// Retrieve the Pre-Image using the registered alias
Entity preImage = context.PreEntityImages["OpportunityImage"];

// Retrieve the Post-Image using the registered alias
Entity postImage = context.PostEntityImages["OpportunityImage"];

// Get the 'estimatedvalue' from the Pre-Image
Money estimatedValueBefore = preImage.GetAttributeValue<Money>("estimatedvalue");

// Get the 'actualvalue' from the Post-Image
Money actualValueAfter = postImage.GetAttributeValue<Money>("actualvalue");

// You can now use these values in your logic
log.LogInformation($"Opportunity value changed from {estimatedValueBefore.Value} to {actualValueAfter.Value}");

This demonstrates how accessing context images in an Azure Function is identical to how you would do it in a standard Dataverse plugin.


4. Connecting Back to Dataverse

A common requirement is for the Azure Function to perform a follow-up action in Dataverse, such as creating a task record or updating a related entity. The blog post you reviewed (LINK) demonstrates the best practice for this: using Dependency Injection (DI) to provide an instance of the ServiceClient.

By setting up DI, you can simply request a ServiceClient in your function's constructor, and the Functions runtime will provide a ready-to-use, authenticated client. This is a cleaner and more testable approach than manually creating a new ServiceClient in every function execution. Once you have the client, using it is exactly the same as in any other .NET application you've built.


Conclusion

In this lesson, you have successfully bridged your C# and Dataverse SDK skills with the Azure serverless platform. You are now equipped to build the backend logic for powerful, event-driven integrations.

Key Takeaways:

  • Azure Functions can be triggered by HTTP requests (for webhooks) or Service Bus messages, using the HttpTrigger and ServiceBusTrigger attributes respectively.
  • The payload from Dataverse is a JSON representation of the RemoteExecutionContext, which can be deserialized into a strongly-typed object for familiar, type-safe access to context data.
  • Azure Function Bindings declaratively manage connections to Azure services, significantly reducing boilerplate code.
  • For professional development, building functions in Visual Studio and using Dependency Injection to manage the ServiceClient are recommended best practices.

What's Next?

So far, we've focused on writing custom C# code for our integrations. However, Azure offers a powerful, low-code alternative for orchestrating workflows. In our next lesson, we will develop an Azure Logic App to orchestrate a business process involving Power Platform and other Azure services. This will give you another valuable tool for designing complex, multi-step integration solutions.