0 Comments Posted in:

At the time of writing this post, we're close to the release of .NET 6 and Azure Functions version 4 is available in private preview

For the most part, changes to Azure Functions are evolutionary rather than revolutionary. The vast majority of what you already know about writing Azure Functions still applies.

In-Process and Isolated Modes

However, in this post I want to discuss an important decision you need to make when creating new Azure Function apps using C#, and that's whether to use "in-process" or "isolated" mode.

The "in-process" model is the original way .NET Azure Functions ran. The Azure Functions runtime, which itself runs on .NET, simply loaded your functions into the same process. However, all other languages supported by Azure Functions (such as JavaScript, Python etc) use an out-of-process model where the Azure Functions runtime talks to your functions which are running in a separate process.

Both approaches have their benefits, and this means that there is going to be a period of time where the decision of which model to chose is not obvious, because the new isolated mode still has a few limitations.

Let's quickly look at the benefits of the two modes.

Benefits of Isolated Mode

The Azure Functions roadmap makes it clear that the isolated process model is the future and will be the only choice from .NET 7 onwards. This means that if you do choose isolated mode, the upgrade path will be simpler in the future.

Azure .NET Functions Roadmap

One of the key motivations for isolated mode, is that you are not tied to the same version of the .NET runtime and any Azure SDKs that the Azure Functions runtime happens to be using. For most people this is not an issue, but when it does block you from using something you need it is quite frustrating.

Isolated mode also gives you a quicker route to using the latest C# language features (although there are work-arounds for in-process functions).

Isolated mode also will feel more familiar to ASP.NET Core developers allowing you to set up dependency injection and middleware in the exact same way that you are used to.

Benefits of In-Process Mode

One key advantage of staying with in-process mode is that it currently is required for Durable Functions. To write Durable Functions in isolated mode, you need to wait until .NET 7.

In process-mode also enjoys some performance advantages inherent to the way it works. It can allow you to bind directly to types exposed by the Azure SDKs. By contrast, isolated mode does not allow you to directly use IAsyncCollector or ServiceBusMessage which is a bit of a step backwards and hopefully the shortcoming will be rectified in future versions of Azure Functions. (some of these limitations can be worked around as Sean Feldman discusses here)

My Recommendation

If you are using Durable Functions, or still relying on binding to specific types not supported with isolated mode then I recommend staying with in-process functions on .NET 6. Also if you currently rely on any of the binding types not supported in isolated mode then it is also probably simpler to stay with in-process for now.

Of course, if the limitations don't affect you, feel free to start using isolated mode now. The Azure Functions documentation has a good breakdown of the current differences between in-process and out-of-process functions which should help you make the decision.

Hopefully by the time .NET 7 is released, there will be sufficient improvements to the isolated process model to make it an obvious default choice for all new Azure Function Apps.

Want to learn more about how easy it is to get up and running with Durable Functions? Be sure to check out my Pluralsight course Azure Durable Functions Fundamentals.

0 Comments Posted in:

I wrote recently about the benefits of adopting Dapr in a microservices application. In that post I focused on the "service invocation" building block. In this post, I want to highlight a particularly useful capability that is exposed by the "bindings" building block.

Dapr bindings

The concept of "bindings" in Dapr will be familiar to anyone who has worked with Azure Functions. They expose a simplified way of interacting with a wide variety of third party services.

Bindings can be "input" or "output". An input binding (also called a "trigger") allows Dapr to subscribe to events in external systems and call an endpoint on your service for you to let you know what has happened. Good examples in Azure would be subscribing to events on Event Grid or messages on Service Bus. But there are many supported bindings, including things like Twitter so you can get notified whenever something is tweeted matching your search criteria.

An output binding allows you to send data to an external service. In Azure this might be posting a message to a queue, writing a document to Cosmos DB. Or you could use it to send an SMS with Twilio.

Bindings benefits and weaknesses

One strength of bindings is that they can greatly simplify your application code as they remove a lot of the cumbersome boilerplate typically required to connect to the service.

Another advantage is that they provide a certain level of abstraction. Whilst some bindings can't be swapped out with other alternatives due to the service-specific nature of the data they deal with, the ability to swap out components has potential to be very useful in dev/test environments, where you may not want or need to actually communicate with a real service.

The key weakness of bindings is that they usually expose a fairly limited subset of capabilities of the underlying platform. So if you are a power user, then you may prefer to just use the SDK of the service directly. And of course Dapr doesn't prevent you from doing that - bindings are completely optional.

The cron binding

The binding I want to focus on particularly is a bit of a special case. It's the "cron" binding. Rather than supporting connection to an external system, this makes it easy to set up scheduled tasks.

To set this up, you need to define a component YAML file. You can just copy an example, and customise the schedule to meet your needs. This supports a regular cron syntax and some simplified shortcuts like @every 5m for every five minutes as shown below.

The only 'advanced' thing I've done is limited this component to only apply to a single Dapr service by using the scopes property - in this example the catalog service.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: scheduled
  namespace: default
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 5m"
scopes:
- catalog

Now all we need to do is listen on an endpoint that matches the name of our component. In this example it's called scheduled. Note that this will be made as a HTTP POST request, so in the example below I'm showing how a simple Node.js Express application can receive calls on the /scheduled endpoint and write a message to the console.

app.post('/scheduled', async function(req, res){
  console.log("scheduled endpoint called", req.body)
  res.status(200).send()
});

If we run this, we'll see that the /scheduled endpoint is called every five minutes by the Dapr sidecar.

And that's all there is to it. Of course, Dapr doesn't force you to use any of its building blocks, so if you already have a solution for scheduled tasks, then feel free to keep using it. But if not, it's great that Dapr provides such a simple to use option out of the box.


0 Comments Posted in:

Six years ago I blogged about how to calculate the total size of blobs in an Azure blob storage container.

Since that post, there have been two(!) new versions of the Azure Blob storage C# SDK. This post uses Azure.Storage.Blobs. And I also wanted to break down the storage by access tier.

Here's the code. You just need to provide your connection string and the name of the container:

var service = new BlobServiceClient(myConnectionString);
var containerName = "mycontainer"; 
var containerClient = service.GetBlobContainerClient(containerName);

var sizes = new Dictionary<AccessTier, long>
{
    { AccessTier.Archive, 0 },
    { AccessTier.Hot, 0 },
    { AccessTier.Cool, 0 }
};

var files = 0;

await foreach (var blob in containerClient.GetBlobsAsync()) 
{
    files++;
    sizes[blob.Properties.AccessTier.Value] += blob.Properties.ContentLength.Value;
}

Console.WriteLine($"{files} files");
foreach (var kvp in sizes)
{
    Console.WriteLine($"{kvp.Key}: {SizeSuffix(kvp.Value)}");
}

A few things of note.

First of all, we can make use of the really nice async enumerable support in C# 8 with the await foreach construct. This has been something I've wanted for a long time, and it's great to see libraries making use of this.

Second, this can be quite slow to run on a very large container. It took over three hours for one container with nearly 20 million blobs. So it's not something you necessarily want to do more times than you need to.

As always in the cloud, you should also consider cost implications. I think I'm right in saying that the only endpoint this code will call is the list blobs operation to retrieve pages of blobs which already have the size and access tier information to hand. Although storage operations are pretty cheap, it can add up when the total number of operations runs into the millions.