.Net 6: Linq enhancements

Linq has got some noticeably enhancements in the .Net 6. In this post, we will briefly visit some of them.

Specify Default Value for FirstOrDefault, LastOrDefault, SingleOrDefault

One of the features I am so relieved to see if the support for specifying default value for FirstOrDefault()LastOrDefault(), and SingleOrDefault(). I never quite understood why it wasn’t included in the first place. Previously the mentioned methods would return default(T) if the source return empty. With .Net 6, you could specify the default value which needs to be returned instead of default(t). For example

var data = GetData(10);

// Prior to .Net 6
var firstOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).First();
var lastOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).Last();
var singleOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).Single();


// With .Net
var firstOrDefault = data.FirstOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));
var lastOrDefault = data.LastOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));
var singleOrDefault = data.SingleOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));

The improvement is quite easy to see. The code has become more cleaner and verbose.

KeySelector for ExceptBy/ IntersectBy/ DistinctBy/ UnionBy/ MaxBy/ MinBy

Previously, if you needed to get the Max using a property using Linq methods, it was painful to say the least

var max = data.OrderByDescending(x => x.Id).First();

Things get even more ugly when we attempt Except(),Intersect(),Distinct() and Union() by a property. MoreLinq library had few methods which helped us in this regard, but with the inbuild Linq methods, this was still unavailable, until .Net 6.

The methods Except,Intersect, Distinct, Union, Max, and Min have got a variant which would now receive a key selector (Func<T,TKey>). Here is a short sample

demoCollection.ExceptBy(secondCollection.Select(x=>x.Name),user=>user.Name);
demoCollection.IntersectBy(secondCollection.Select(x => x.Name), user => user.Name);
demoCollection.DistinctBy(user=>user.Name);
demoCollection.UnionBy(secondCollection,user=>user.Name).;
demoCollection.MaxBy(user=>user.Id);
demoCollection.MinBy(user=>user.Id);

Range for Take

Range was introduced in C# 8. .Net 6 now adds an override to the Take() method which would accept a Range. For example,

demoCollection.Take(10..^10);

Chunk

Third Party Linq add-on libraries had this functionality for a long time now, but Microsoft decided to add chunk to its own set of extension methods of IEnumerable with .Net 6. Chunk allows you to process large collection of data in batches. For example, the following method call would split the demoCollection into chunks of 5, so that you could process them as batches.

demoCollection.Chunk(5);

Zip with IEnumerables

Linq methods previously allowed developers to merge an element in the first collection with an element in the second collection using the Zip method.Previously, if you need to Zip 3 collection, you would need to opt for a solution that might look like following.

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
string[] romans = { "I", "II", "III" };

var numbersAndWords = numbers.Zip(words, (number, word) => (number, word));
var numbersAndWordsAndRoman = numbersAndWords.Zip(romans,(numberWord,roman)=> (numberWord.number, numberWord.word,roman));

foreach (var (number, word, roman) in numbersAndWordsAndRoman)
    WriteLine($"{number}-{word}-{roman}");

The new improvements now allows you to include 3 collections in your operation.

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
string[] romans = { "I", "II", "III" };

var numbersAndWords = numbers.Zip(words,romans);

foreach (var (number,word,roman) in numbersAndWords)
    WriteLine($"{number}-{word}-{roman}");

That definitely has got a lot more cleaner.

TryGetNonEnumeratedCount

There are times when you would like to declare explicitly the capacity of the collection. This is quite useful when you do not want the capacity to double each time count exceeds capacity (not to mention all the copy operations involved). The TryGetNonEnumeratedCount method attempts to obtain the count of the source enumerable without forcing an enumeration. If it doesn’t find a property (say Count from ICollection) then it would return 0;

demoCollection.TryGetNonEnumeratedCount(out var enumCount); 

Those are some of the improvements in .Net 6 for Linq. We will continue exploring the .Net 6 features in the blogs to come. For the complete source of the code in the post, please use my Github.

DependencyPropertyHelper.GetValueSource : Debugging Helper for Dependency Properties

DependencyPropertyHelper.GetValueSource is a great debugging aid in detecting the source of value of a Dependency Property. This is quite useful for WPF developers who might need to figure out the source which provides the current value for the Dependency Property.

The DependencyPropertyHelper.GetValueSource returns a structure ValueSource which has 5 Properties.

  • BaseValueSource
  • IsAnimated
  • IsCoerced
  • IsCurrent
  • IsExpression
DependencyPropertyHelper.GetValueSource(buttonControl,CustomValueProperty)
{System.Windows.ValueSource}
    BaseValueSource: Local
    IsAnimated: false
    IsCoerced: false
    IsCurrent: false
    IsExpression: true

Dependency Property Value Precedence

The value of Dependency Property is calculated in 5 Steps.

  • Calculate the Base Value
  • Evaluate Expression if any
  • Apply Animation if any
  • Coerce Value

The BaseValueSource in the ValueSource structure speficies how the Base Value of the Dependency property is calculated. The base value of Dependency Property is calculated with aid of property value providers. Some ofthe Value Providers in decreasing Order of precedence are

  • Local Value
  • Templated Parent
  • Implicit style
  • Style triggers
  • Template Triggers
  • Style Setters
  • Theme
  • Inheritence
  • Default Value

Once the base value has been determined, the value is then passed through a series of steps depending on how the value is configured. These include Expressions, Animations and coerce callbacks if they are configured. You can follow more details on Dependency Property Value Precedence in the MSDN documentation

The DependencyPropertyHelper.GetValueSource provides an indication on how the current value in Dependency Property has been determined.

Multiple Binding in Azure Function (both In process and Out of Process)

In this post, we will look into how to support multiple output binding in an Azure Function. We will attempt to explore how to support multiple output both in in process functions as well as out of process functions.

Multiple output binding are useful when you have a scenario where you want to response to an HTTP Trigger with an Http Message and at the same time, add a new Item to the Queue. This is handled in difference ways in the in-process functions and out-of-process functions.

In Process Functions

In the conventional In process Azure functions, the function is a class library that run in the same process as the Host Process. With the in process functions, you could easily support multiple output binding by marking the ouput parameter as out.

For example, to support a Queue output along with the Http Response, the following could be done.

[FunctionName("MultioutputSample")]
public static IActionResult MultiOutputDemo([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log,[Queue("SampleQueue")]out string queueOutput)
{

    queueOutput = "New Activity detected";
    return new OkObjectResult($"Hello, Welcome to Isolated functions demo.");
}

In the above example, a string “Hello, Welcome to Isolated functions demo.” would be send back as Http Response, while another string “New Activity detected” is added to the “SampleQueue” that has been marked with the queueOutput out parameter.

Out Of Process Isolated Functions

The out of process isolated functions works as console application and runs independed from the Azure Function Host. As a developer, you are responsible for hosting your own IHost instance.

The value returned by the isolated function is bound to the output binding. This would mean the out of process isolated function has to use another approach for support multiple output. This is done defining a Custom Type and having a property marked with the QueueOutput attribute. For example,

[Function(nameof(MultiOutputSample))]
        public static async Task<CustomOutputType> MultiOutputSample(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
            FunctionContext executionContext)
{
    // Use the FunctionContext.GetLogger to fetch instance of ILogger
    var logger = executionContext.GetLogger(nameof(StaticFunctions));
    logger.LogInformation("Started execution of function");

    var data = await req.ReadFromJsonAsync<UserDto>();

    // Should use HttpResponseData as response
    var response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteStringAsync($"Hello {data.Name}, Welcome to Isolated functions demo.");


    return new CustomOutputType
    {
        UserResponse = response,
        UserTask = $"{data.Name} have logged in"
    };
}

Where the CustomOutputType is defined as

public class CustomOutputType
{
    [QueueOutput("SampleQueue")]
    public string UserTask { get; set; }
    public HttpResponseData UserResponse { get; set; }
}

In the above defined CustomOutput, the UserResponse being of type HttpResponseData would return a http message back to the caller, while at the same time, the string in the UserTask is added to the SampleQueue.

As seen, both in process and out of process Azure Functions supports multiple output binding, but uses different approaches for achieving the results. Both approaches are pretty easy to use.

Introduction to Isolated Azure Functions (.Net 5)

One of the bigger constraints of Azure Function Apps was the tight coupling between the function code and the Azure function runtime. The In-Process Azure Functions ran as a class library in the same process as the host service and was tightly coupled. This forced the function code to use the same version of .Net as the host service. This would also mean you cannot use .Net 5 to develop your function apps.

This restriction was overcome with Isolated Function App, which runs out of process in Azure Functions. The Isolated Function App runs as a console application and could now use .Net 5 for implementation. Additionally, it had fewer conflics due to different versions of same assembly as it runs on a separate process. The other benefits also included the ability to control the function app in a better way, including the application start, configuration and support for dependency injection. One of the biggest limitations of the out-of-process isolated Azure functions is the lack of support for Durable Functions.

Project Template

Let us now proceed to create our Isolated Function.

Isolated Function could be created by choosing the Azure Function Template from Visual Studio.

You need to make sure that you select “.Net 5.0” as the framework. This would generate the Isolated Function template for you.The Isolated Function project is a console application and the template comprises of following files.

  • host.json
  • local.setttings.json
  • C# Project File
  • Program.cs

Start Up and Configuration

Since as a developer you have access to the start up of the Isolated functions, you are responsible for creating, configuring and starting the Host instance. You do so using the HostBuilder class. If you look at the default code generated you could find the following code.

public class Program
{
    public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .Build();

        host.Run();
    }
}

The code above creates an instance of HostBuilder, uses the ConfigureFunctionWorkerDefaults extension method to create teh default configurations and uses the Build extension to initialize the configuration.

It then uses the IHost.Run() method to start the Host Instance.

As mentioned earlier, since you are building your own host instance, you have ability to configure it with the services and middleware you would prefer.

ConfigureFunctionsWorkerDefaults

The ConfigureFunctionWorkerDefaults method adds the default settings which is required to run the function app as __ out of process_. The default configuration includes

  • Default set of converters
  • Default JsonSerializerOptions
  • Integrate Azure Logging
  • Output binding Middlewareand features
  • Function execution middleware
  • Default gRPC support

You could use an override of the ConfigureFunctionWorkerDefaults to custom configure some of the existing services/configuration. For example, the default JsonSerializerOptions configured by the ConfigureFunctionWorkerDefaults is as following.


// Code from IServiceCollection.AddFunctionsWorkerDefaults extension method
services.Configure<JsonSerializerOptions>(options =>
{
    options.PropertyNameCaseInsensitive = true;
});

This configures the default Json configuration to ignore the casing on property names. You could further configure it to ignore the trailing commas in Json (default is false).

var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults(builder=>
            {
                builder.Services.Configure<JsonSerializerOptions>(options =>
                {
                    options.AllowTrailingCommas = true;
                });

            })
            .Build();


Middleware

The isolated functions also allows you to configure your custom middlewares to the invocation pipelines. The ConfigureFunctionsWorkerDefault method again comes in handy for the purpose.

var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults(builder=>
                {
                    builder.Services.Configure<JsonSerializerOptions>(options =>
                    {
                        options.AllowTrailingCommas = true;
                    });

                    builder.UseMiddleware<ExceptionMiddleWare>();
                })
                .Build();

The ExceptionMiddleWare in the above is a custom middleware that can inject custom logic into the invocation pipeline.

public class ExceptionMiddleWare : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        try
        {
            await next(context);
        }
        catch(Exception ex)
        {
            var logger = context.GetLogger(context.FunctionDefinition.Name);
            logger.LogError("Unexpected Error in {0}: {1}",context.FunctionDefinition.Name,ex.Message);
        }
    }

}

In the above code, the logic injected by the Middleware is called after the function exection, but you could also inject logic before the function is called.

Dependency Injection and Services

You can register your services during the start up using the IHostBuilder.ConfigureServices extension method. For example,

var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults(builder=>
            {
                builder.Services.Configure<JsonSerializerOptions>(options =>
                {
                    options.AllowTrailingCommas = true;
                });

                builder.UseMiddleware<ExceptionMiddleWare>();
            })
            .ConfigureServices(serviceCollection =>
            {
                serviceCollection.AddSingleton<IMockDataService>(new MockDataService());
            })
            .Build();

For the sake of this example, I am using a sample Service which emulate by Data Service.

public interface IMockDataService
{
    UserDto CreateUser(string userName);
}

public class MockDataService : IMockDataService
{
    private readonly Random _randomGenerator = new Random();
    public UserDto CreateUser(string userName)
    {
        return new UserDto
        {
            Name = "Anu Viswan",
            Id = _randomGenerator.Next()
        };
    }
}

I could now inject the dependency in my function code.

Functions

Since the Isolated Functions are running out of process, the way we have the Http Request in the Http Trigger functions are difference from the in-process version. Since we do not have access to the original Http Request and response, you would need to use the HttpRequestData and HttpResponseData object instead to access the request and response. While these provides all the Headers,URL, Body of the incoming request, it is not the original request, bur rather a representation of the same. A sample Http Trigger function might look like the following.

[Function(nameof(SayHello))]
public static async Task<HttpResponseData> SayHello(
    [HttpTrigger(AuthorizationLevel.Anonymous,"post")] HttpRequestData req,
    FunctionContext executionContext)
{
    // Use the FunctionContext.GetLogger to fetch instance of ILogger
    var logger = executionContext.GetLogger(nameof(StaticFunctions));
    logger.LogInformation("Started execution of function");

    var data = await req.ReadFromJsonAsync<UserDto>();

    // Should use HttpResponseData as response
    var response = req.CreateResponse(HttpStatusCode.OK);

    await response.WriteStringAsync($"Hello {data.Name}, Welcome to Isolated functions demo.");
    return response;
}

The bindings too are serverly restricted as there is no longer deep integrtion with the Azure function runtime. This would also mean you are restricted from using the ICollection<T> or CloudBlockBlob. Instead your need to relay POCOS or strings (or arrays). The Logger also needs to be fetched using the FunctionContext.GetLogger method.

Earlier,we registered own DataService. We could use Dependency Injection to inject the same and use in our function. For example,

private readonly IMockDataService _mockDataService;
public InstanceFunctions(IMockDataService mockDataService)
{
    _mockDataService = mockDataService;
}

[Function(nameof(CreateUser))]
public async Task<HttpResponseData> CreateUser(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
    FunctionContext executionContext)
{
    var queryStrings = System.Web.HttpUtility.ParseQueryString(req.Url.PathAndQuery);
    var userName = queryStrings["user"];

    // Use the FunctionContext.GetLogger to fetch instance of ILogger
    var logger = executionContext.GetLogger(nameof(StaticFunctions));
    logger.LogInformation("Started execution of function");

    var data = _mockDataService.CreateUser(userName);

    // Should use HttpResponseData as response
    var response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync<UserDto>(data);

    return response;
}

The above method uses Constructor Injection to inject our dependency and later uses the same in the CreateUser method.

Summary

In the this post, we have a peak in the world of Isolated Functions and how to create them. We understood some of the differences between the in-process functions and the new out-of-process isolated functions. We also understood how we have more control over the start up process, which in-turns helps use to configure the function app with the desired services and middlewares. One of the key draw backs of the Isolated function at the moment is the lack of support of Durable Functions.

The complete list of differences is outlined here by Microsoft : Difference with .Net Library Functions.