Tuesday, April 26, 2022

C# 11.0: Raw String Literals

In this post, let's have a look at one of the nicest features coming in C# 11.0. And this is one of my favorites.

You can also try out this feature now by setting the LangVersion to preview in your .csproj file.
<LangVersion>preview</LangVersion>
Say you need to declare a variable with a JSON string, something like this.
{
  "name""John Doe",
  "address": {
    "addressLine1""Address Line 1",
    "addressLine2""Address Line 2",
    "city""City",
    "state""State",
    "postalCode""12345-6789",
    "country""Country"
  }
}
And prior to C# 11.0, in order to get this into a variable, we need to modify the JSON string to escape the double-quotes.
string jsonString =
    @"{
    ""name"": ""John Doe"",
    ""address"": {
        ""addressLine1"": ""Address Line 1"",
        ""addressLine2"": ""Address Line 2"",
        ""city"": ""City"",
        ""state"": ""State"",
        ""postalCode"": ""12345-6789"",
        ""country"": ""Country""
    }
}";
And now say you want to use string interpolation for some of the values. And for that you need to escape the curly braces, something like this.
string name = "John Doe";
string jsonString =
    @$"{{
    ""name"": ""{name}"",
    ""address"": {{
        ""addressLine1"": ""Address Line 1"",
        ""addressLine2"": ""Address Line 2"",
        ""city"": ""City"",
        ""state"": ""State"",
        ""postalCode"": ""12345-6789"",
        ""country"": ""Country""
    }}
}}";
And that's a lot of work.

With Raw String Literals in C# 11.0, you can do something like below.
string name = "John Doe";
string jsonString =
    $$"""
    {
        "name": "{{name}}",
        "address": {
            "addressLine1": "Address Line 1",
            "addressLine2": "Address Line 2",
            "city": "City",
            "state": "State",
            "postalCode": "12345-6789",
            "country": "Country"
        }
    }
    """;
And note, here I didn't escape double quotes nor the curly braces. I only had to change the value of the name property to use the string interpolation. So basically it's just copying and pasting the JSON as it is and doing a minor change if we are using string interpolation which we will have to do anyway.

A couple of important notes here:
  • Raw string literals start and end with at least three double-quotes.
string jsonString =
    """
    {
        "name": "John Doe",
    }
    """;
  • Within these double quotes, single " are considered content and included in the string
  • Any number of double quotes less than the number that opened the raw string literal are treated as content. So, in the common case of three double quotes opening the raw string literals, two double quotes appearing together would just be content.
  • If you need to output a sequence of three or more double-quotes, then open and close the raw string literal with at least one more quote than that sequence, something like below.
string jsonString =
    """"
    {
        "name": "John Doe",
        "description": "Some Description with """ quotes "
    }
    """";
  • Raw string literals can be interpolated by preceding them with a $. The number of $ that prefixes the string is the number of curly brackets that are required to indicate a nested code expression.
string name = "John Doe";
string someString = $""" His name is "{name}".""";
  • If a raw string literal is prefixed with $$, a single curly bracket is treated as content and it takes two curly brackets to indicate nested code (as shown in the jsonString with string interpolation code snippet above). Just like with quotes, you can add more $ to allow more curly brackets to be treated as content.
Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, April 20, 2022

C# 11.0: Parameter Null Checking (Revisited)

C# 11.0 was initially planned to have the bang-bang operator for improved Parameter Null Checking. If you don't know what that is, I have written this post a while back.
   C# 11.0 Preview: Parameter Null Checking

But unfortunately, the C# Lang team has deferred this feature and we won't be having this feature in C# 11.0. 

With C# 10.0 and .NET 6, we still have the ArgumentNullException.ThrowIfNull method which is the recommended/preferred approach for going forward. So while we are here, maybe it's worth having a closer look at ArgumentNullException.ThrowIfNull.

Basically, with this feature syntax is something like below.
static void PrintFullName(Person person)
{
    ArgumentNullException.ThrowIfNull(person);
 
    Console.WriteLine($"FullName: {person.FirstName} {person.LastName}");
}
Now If I call this method supplying a null value, I am going to get an exception something like below.
Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'person')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at Program.<<Main>$>g__PrintFullName|0_0(Person person) in C:\Users\Jaliya\Desktop\ConsoleApp1\ConsoleApp1\Program.cs:line 3
   at Program.<Main>$(String[] args) in C:\Users\Jaliya\Desktop\ConsoleApp1\ConsoleApp1\Program.cs:line 8

So here in the exception, we have the following details.

And these Caller Info attributes were introduced in C# 5.0 back in 2012.

If you notice the exception, you should see we also have the name of the parameter in the exception. But when doing ArgumentNullException.ThrowIfNull, we haven't included the parameter name. So what happened here?

Enter CallerArgumentExpressionAttribute.

This new attribute was introduced with C# 10.0, and this allows us to capture the expressions passed to a method. If you examine the ArgumentNullException.ThrowIfNull method, you will see it's using this new attribute as below and that's how we are getting parameter name in the exception.

public static void ThrowIfNull([NotNull] objectargument, [CallerArgumentExpression("argument")] stringparamName = null);

Reference: ArgumentNullException.cs

Let's consider the below code.

static void PrintFullName(Person person)
{
    ThrowIfNull(person);
}
 
static void ThrowIfNull(object argument, [CallerArgumentExpression("argument")] stringexpression = default)
{
    if (argument is null)
    {
        throw new ArgumentNullException(expression);
    }
 
    Console.WriteLine($"Expression: {expression}");
}

Now if I call these methods as follows, I am getting these outputs.

PrintFullName(new Person("John""Doe"));
// Expression: person
ThrowIfNull(new Person("John""Doe")); // Expression: new Person("John", "Doe")
PrintFullName(null);
// System.ArgumentNullException: Value cannot be null. (Parameter 'person')
ThrowIfNull(null);
// System.ArgumentNullException: Value cannot be null. (Parameter 'null')

Hope this helps.

Happy Coding.

Regards,
Jaliya

Friday, April 15, 2022

ASP.NET Core: Custom Controller Action Parameter Binding using TryParse in Minimal APIs

.NET 7 Preview 3 is out and in this post let's see how we can customize Controller Action parameter binding using TryParse in Minimal APIs.

Let's consider the following code.

// /employees/search?searchCriteria={"name":"John"}
app.MapGet("/employees/search",
    async (EmployeeContext dbContext,
        EmployeeSearchCriteria searchCriteria,
        CancellationToken cancellationToken) =>
{
    return await dbContext.Employees
        .Where(x => x.Name == searchCriteria.Name)
        .ToListAsync(cancellationToken);
})
.Produces<List<Employee>>(StatusCodes.Status200OK);
 
public class EmployeeSearchCriteria
{
    public string Name getset; }
}

Here let's say, I want to bind the query parameter searchCriteria that I am sending to the searchCriteria object in the action. Here above code won't work, because the runtime has no knowledge of translating the query parameter to the searchCriteria objectWe can instruct the runtime on how it should get translated by using TryParse.

I am updating the EmployeeSearchCriteria class by introducing the following TryParse method.

public class EmployeeSearchCriteria
{
    public string Name { getset; }
 
    public static bool TryParse(string valueout EmployeeSearchCriteria result)
    {
        if (value is null)
        {
            result = default;
            return false;
        }
 
        JsonSerializerOptions options = new()
        {
            PropertyNameCaseInsensitive = true
        };
        result = JsonSerializer.Deserialize<EmployeeSearchCriteria>(value, options);
 
        return true;
    }
}

ASP.NET Core will look for a TryParse method in a complex object parameter when trying to do the parameter binding. The TryPrase method signature should be one of the following.

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

Now moving back to the code sample, if I run the updated code, I can see the query parameter searchCriteria is correctly bound to the object.

Customize Action Parameter Binding using TryParse
Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, April 6, 2022

In-Process Azure Function, EF Core Logging using ILogger

In this post, let's see how we can configure EF Core logging using ILogger in an In-Process Azure Function.

First, I am registering my DbContext in the Startup.cs as follows.

Startup.cs

[assembly: FunctionsStartup(typeof(FunctionApp1.Startup))]
namespace FunctionApp1;
 
public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        IConfiguration configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();
 
        builder.Services.AddDbContext<MyDbContext>(options =>
            options
                .UseSqlServer(configuration.GetValue<string>("MyDbContext_ConnectionString")));
    }
}

Then in my DbContext, I am overriding the OnConfiguring method as follows.

MyDbContext.cs

public class MyDbContext : DbContext
{
    private readonly IHostingEnvironment _hostingEnvironment;
    private readonly ILogger<MyDbContext> _logger;
 
    public MyDbContext(DbContextOptions options, 
        IHostingEnvironment hostingEnvironment, 
        ILogger<MyDbContext> logger) : base(options)
    {
        _hostingEnvironment = hostingEnvironment;
        _logger = logger;
    }
 
    public DbSet<Employee> Employees { getset; }
 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Only logging in Development environment to avoid unnecessary noice in Production Environment
        if (_hostingEnvironment.IsDevelopment())
        {
            optionsBuilder
                .LogTo(action =>
                {
                    _logger.LogInformation(action);
                    // TODO: Customize logging, use any LogTo Overload
                });
        }
    }
}

Here I am using DbContextOptionsBuilder.LogTo Method which was introduced in EF Core 5.0.

And now I am invoking the following function to generate some logs.

public class Function1
{
    private readonly MyDbContext _myDbContext;
 
    public Function1(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }
 
    [FunctionName("Employees")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest request)
    {
        string requestBody = await new StreamReader(request.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        string name = data?.name;
 
        Employee employee = new()
        {
            Name = name
        };
        await _myDbContext.Employees.AddAsync(employee);
        await _myDbContext.SaveChangesAsync();
 
        return new OkObjectResult(employee);
    }
}

And now I can see EF Core Logging is getting triggered.

I am using App Insights, and I can see the logs there.

Hope this helps.

Happy Coding.

Regards,
Jaliya