Wednesday, September 27, 2023

Visual Studio 2022 17.8.0 Preview 1.0: Case Preserving Find and Replace

In this post, let's have a look at some nice feature that got introduced with Visual Studio 2022 17.8.0 Preview 1.0. Right now the latest Visual Studio preview is, Visual Studio 2022 17.8.0 Preview 2.0, so as long as you have a Visual Studio version >= 17.8.0 Preview 1.0, you should be able to experience this feature.

In the Replace popups, either Quick Replace (Ctrl + H) or Replace in Files (Ctrl + Shift + H), you should be able to see an additional option "Preserve Case".

Quick Replace (Ctrl + H)
Replace in Files (Ctrl + Shift + H)
So what does this option do?

Consider the following.
// EmployeeId
// employeeId
// employeeid
// EMPLOYEEID
// employeeId
If you do a replace of employeeId with customerid while Preserving case, you can see all of the above is getting replaced to customerid, and it does so while preserving the case of the original word.  
// CustomerId
// customerId
// customerid
// CUSTOMERID
// customerId
Isn't that handy?

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, September 20, 2023

.NET 8.0: Additional JsonNode Functionality

.NET 8 offers some exciting updates to System.Text.Json.

One of my favorites of them is, now we have some handy additional functionalities on JsonNode. The JsonNode has following new methods:

namespace System.Text.Json.Nodes;

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent object, returns its name.
    public string GetPropertyName();

    // If node is an element of a parent JsonArray, returns its index.
    public int GetElementIndex();

    // Replaces this instance with a new value, updating the parent node accordingly.
    public void ReplaceWith<T>(T value);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

For example, GetValueKind can be a huge win. 

Consider the following example with .NET 8.

using System.Text.Json.Nodes;

JsonNode jsonNode = JsonNode.Parse("""
    {
        "name": "John Doe",
        "age": 42,
        "isMarried": true,
        "addresses":
        [
            { "street": "One Microsoft Way", "city": "Redmond" },
            { "street": "1st Street", "city": "New York" }
        ]
    }
    """
)!;

Console.WriteLine(jsonNode.GetValueKind());
//Object

Console.WriteLine(jsonNode["name"]!.GetValueKind());
//String

Console.WriteLine(jsonNode["age"]!.GetValueKind());
//Number

Console.WriteLine(jsonNode["isMarried"]!.GetValueKind());
//True

Console.WriteLine(jsonNode["addresses"]!.GetValueKind());
//Array

Imagine how much code we needed to write prior to .NET 8 to achieve the same.

Isn't this handy?

Read more:
   What’s new in System.Text.Json in .NET 8

Happy Coding.

Regards,
Jaliya

Tuesday, September 19, 2023

Visual Studio 2022: HTTP Files and Variables

In this post, let's get to know some different ways to manage variables within HTTP Files.

Let's consider the following simple API.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

WebApplication app = builder.Build();

app.MapGet("/users/{userId}", (int userId) =>
{
    return userId;
});

app.Run();

1. Use Inline Variables


You can define the variables in the .http file itself as follows
@HostAddress = http://localhost:5179
@userId = 1

GET {{HostAddress}}/users/{{userId}}
Accept: application/json
In-line variable

2. Use an Environment File


You can define an environment file with the name httpenv.json. The file can be in the same folder as in the .http file or a folder above it. Visual Studio will look for that file in the folder where the HTTP file exists. If it’s not found, Visual Studio will look through the parent directories to find it. When a file is found named httpenv.json, Visual Studio will stop searching for the file. The nearest file to the HTTP file found will then be used.

Here, I am just going to add httpenv.json next to the .http file.
{
  "development": {
    "userId": 2
  },
  "test": {
    "userId": 3
  }
}
Now I am closing and opening up the .http file (in order for the environment file to be picked), removing @userId = 1 defined in the .http file. Now because of the httpenv.json, I can see the environments in a dropdown as follows.
Environments
Now based on the environment we are selecting, I will get different values.
Environment: development
Environment: test

3. Use a user-specific environment file


You can also add a user-specific environment file named httpenv.json.user, next to the httpenv.json file.
{
  "development": {
    "userId": 4
  }
}
And now when you execute the request, you can see the following output.
User-specific environment file
The order of precedence for the variables is below. Once a match is found that value will be used, and other sources ignored.
  1. Variable declared in the .http file
  2. Variable declared in the httpenv.json.user file
  3. Variable declared in the httpenv.json file
Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, September 13, 2023

.NET In-Process Azure Durable Functions: Preserve Stack Order When Passing Between Orchestrators, Activities etc

In this post let's see how we can preserve Stack<T> order when it's getting passed between Orchestrators/Activities in a .NET In-Process Azure Durable Functions.

As you already know, when using Orchestrators/Activities in an Azure Durable Function,  different types of data are being serialized and persisted. Durable Functions for .NET In-Process internally uses Json.NET to serialize orchestration and entity data to JSON.

And when you pass in a Stack<T> to an Orchestrator or an Activity for an example, the order of items will not be preserved when it's serialized. It's actually an issue with Json.NET.

We can reproduce the issue simply by creating a simple .NET In-Process Azure Durable Function, something like below.

public static class Function1
{
    [FunctionName("HttpStart")]
    public static async Task<HttpResponseMessage> HttpStart(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get""post")] HttpRequestMessage req,
        [DurableClient] IDurableOrchestrationClient starter,
        ILogger log)
    {
        string instanceId = await starter.StartNewAsync("Function1");

        log.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);

        return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, TimeSpan.FromSeconds(30));
    }

    [FunctionName("Function1")]
    public static async Task<Stack<string>> RunOrchestrator1([OrchestrationTrigger] IDurableOrchestrationContext context)
    {
        Stack<stringtransitions = new();

        transitions.Push("T0");
        transitions.Push("T1");
        transitions.Push("T2");

        return await context.CallSubOrchestratorAsync<Stack<string>>("Function2", transitions);
    }

    [FunctionName("Function2")]
    public static async Task<Stack<string>> RunOrchestrator2([OrchestrationTrigger] IDurableOrchestrationContext context)
    {
        Stack<stringtransitions = context.GetInput<Stack<string>>();

        transitions.Push("T3");
        transitions.Push("T4");
        transitions.Push("T5");

        return await Task.FromResult(transitions);
    }
}

The expected output should be as follows.

[
    "T5",
    "T4",
    "T3",
    "T2",
    "T1",
    "T0"
]
But it comes as follows.
Incorrect Result
The order can be so messed up when you have complex logic like when having recursive function calls.

This can be addressed by applying a custom serialization to the Durable Function.

First, we need to create a custom JsonConverter to serialize/deserialize Stack<T> correctly preserving the order.

/// <summary>
/// Converter for any Stack<T> that prevents Json.NET from reversing its order when deserializing.
/// https://github.com/JamesNK/Newtonsoft.Json/issues/971
/// https://stackoverflow.com/a/39481981/4865541
/// </summary>
public class StackConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return StackParameterType(objectType) != null;
    }

    public override object ReadJson(JsonReader reader, Type objectTypeobject existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        try
        {
            Type parameterType = StackParameterType(objectType);
            MethodInfo method = GetType().GetMethod(nameof(ReadJsonGeneric), BindingFlags.NonPublic | BindingFlags.Static);

            MethodInfo genericMethod = method.MakeGenericMethod(new[] { parameterType });
            return genericMethod.Invoke(thisnew object[] { reader, objectType, existingValue, serializer });
        }
        catch (TargetInvocationException ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializerException
            throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
        }
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writerobject value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    private static Type StackParameterType(Type objectType)
    {
        while (objectType != null)
        {
            if (objectType.IsGenericType)
            {
                Type genericType = objectType.GetGenericTypeDefinition();
                if (genericType == typeof(Stack<>))
                {
                    return objectType.GetGenericArguments()[0];
                }
            }
            objectType = objectType.BaseType;
        }

        return null;
    }

    private static object ReadJsonGeneric<T>(JsonReader reader, Type objectTypeobject existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        List<T> list = serializer.Deserialize<List<T>>(reader);
        Stack<T> stack = existingValue as Stack<T> ?? (Stack<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        for (int i = list.Count - 1; i >= 0; i--)
        {
            stack.Push(list[i]);
        }

        return stack;
    }
}
Then we can override the default Json.NET serialization settings of the Durable Function using custom implementations of the IMessageSerializerSettingsFactory as follows.
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;

[assembly: FunctionsStartup(typeof(DurableFunctions.StackSerialization.Startup))]
namespace DurableFunctions.StackSerialization;

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddSingleton<IMessageSerializerSettingsFactory, CustomMessageSerializerSettingsFactory>(); // ...
    }

    /// <summary>
    /// A factory that provides the serialization for all inputs and outputs for activities and
    /// orchestrations, as well as entity state.
    /// </summary>
    internal class CustomMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
    {
        public JsonSerializerSettings CreateJsonSerializerSettings()
        {
            return new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.None,
                DateParseHandling = DateParseHandling.None,
                Converters = new List<JsonConverter>
                {
                    new StackConverter()
                }
            };
        }
    }
}
And now if we invoke the function, we can see the correct output.
Correct Result
Sample code:


Hope this helps.

Happy Coding.

Regards,
Jaliya