Wednesday, March 30, 2022

Enabling Tab Completion for .NET CLI in PowerShell

In this post, let's see how we can enable Tab Completion for .NET CLI commands in PowerShell. By default, Tab completion doesn't work for .NET CLI commands.

As you can see in the below image, I am trying tab-completion after typing some letters and it doesn't resolve me the available commands in .NET CLI. Not so much of a friendly experience.
PowerShell: .NET CLI Tab Completion Does Not Work
But we can get this feature enabled in like 2 steps.

First, run the following command.
# Assuming you have VS Code
code $PROFILE
 
# If you don't have VS Code
notepad $PROFILE

Now update your PowerShell profile by adding the following code snippet.

Register-ArgumentCompleter -Native -CommandName dotnet -ScriptBlock {
    param($commandName, $wordToComplete, $cursorPosition)
    dotnet complete --position $cursorPosition "$wordToComplete" | ForEach-Object {
        [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue'$_)
    }
}
Save the file, close PowerShell, and open it back up. Now as you can see here, tab completion is working nicely.
PowerShell: .NET CLI Tab Completion in Action
If this doesn't work, try running the following command and ensure it works.
dotnet complete "dotnet *"

dotnet complete
If this doesn't work, make sure that .NET Core 2.0 SDK or above is installed and dotnet --version command resolves to a version of .NET Core 2.0 SDK and above.

If you want to enable .NET CLI tab completion for other Shells like bash, read more here.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Saturday, March 12, 2022

Azure App Service Supports Windows Containers up to Windows Server Core 2019 and Nano Server 1809

Did you know that as of today, Windows Containers on Azure App Service only supports up to Windows Server Core 2019 and Nano Server 1809?

We were having a Windows Container App running on Azure App Service and recently our deployments started failing with the error" UnsupportedMediaType - The parameter WindowsFxVersion has an invalid value. Cannot run the specified image as a Windows Containers Web App. App Service supports Windows Containers up to Windows Server Core 2019 and Nanoserver 1809. Platform of the specified image: windows, Version: 10.0.20348.587; (CODE: 415)".

Our docker image was based on mcr.microsoft.com/dotnet/aspnet:6.0. Upon inspecting mcr.microsoft.com/dotnet/aspnet:6.0, it's now using Windows OS: 10.0.20348.587 (when it's built on Windows). The change was made on 2022-03-08, likely with the announcement of .NET 6.0.3.

{
    "RepoTags": [
        "mcr.microsoft.com/dotnet/aspnet:6.0"
    ],
    "Created""2022-03-08T18:56:16.0190214Z",
    "Os""windows",
    "OsVersion""10.0.20348.587",
    "..."
}

So to get the things back to work, I had to change the base image to mcr.microsoft.com/dotnet/aspnet:6.0.3-nanoserver-1809 which will enforce underline OS to be Nano Server 1809

While we are here, it's worth specifying the difference between Nano Server and Server Core, so you can choose an appropriate base image that works for you.

  • Nano Server is an ultralight Windows offering for new application development.
  • Server Core is medium in size and a good option for "lifting and shifting" Windows Server apps.

It's always a good practice to be explicit and knowingly update as we go rather than using generic mcr.microsoft.com/dotnet/aspnet:6.0 as Microsoft can update the underline OS anytime.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, March 10, 2022

Custom EF Core Function to Use Transact-SQL AT TIME ZONE

In this post, let's see how we can write a Custom EF Core Function to Use Transact-SQL AT TIME ZONE.

Consider the below example. I have the following Order entity in my DbContext.

public class Order
{
    public int Id { getset; }
 
    public DateTimeOffset OrderDate { getset; }
}

Now I have the following Extension method to Convert a given DateTimeOffset to a given TimeZone.

public static class DateTimeExtensions
{
    public static DateTimeOffset ConvertToTimeZone(this DateTimeOffset dateTimeOffsetstring timeZone)
    {
        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        DateTime datetime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeOffset.UtcDateTime, timeZoneInfo);
 
        return new DateTimeOffset(datetime, timeZoneInfo.GetUtcOffset(datetime));
    }
}

Now consider the below query.

var timeZone = "Eastern Standard Time";
 
var orders = await context.Orders
    .Select(x => new
    {
        OrderDate = x.OrderDate,
        ConvertedOrderDate = x.OrderDate.ConvertToTimeZone(timeZone)
    })
    .ToListAsync();

Note for ConvertedOrderDate, I am using the above CLR extension method. 

And when I run this query, the generated SQL statement is going to look like below.

SELECT [o].[OrderDate]
FROM [Orders] AS [o]

And with the below result.

OrderDate                     | ConvertedOrderDate
------------------------------| ------------------------------
20/02/2022 12:00:00 am +00:00 | 19/02/2022 7:00:00 pm -05:00

In the SQL statements EF generated, you can see it doesn't contain anything on ConvertedOrderDate or TimeZone conversion. That's because EF can't translate my CLR extension method into SQL statements. And if we have used this extension method in a Where statement (instead of Select), EF is going to loudly throw an error saying "Translation of method 'DateTimeExtensions.ConvertToTimeZone' failed". So here basically the TimeZone conversion was done In Memory.

And for these kinds of scenarios, we can use this feature in EF, where we can say how a method should get translated into SQL.

First, I am creating a static class with the following method.

public static class EfFunctions
{
    public static DateTimeOffset ConvertToTimeZone(DateTimeOffset dateTimeOffset, [NotParameterized] string timeZone)
        => throw new NotImplementedException("This method should be implemented in EF.");
}

We don't provide any implementation here.

Now we need to instruct EF on how to translate this method.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    MethodInfo methodInfo = typeof(EfFunctions).GetMethod(nameof(EfFunctions.ConvertToTimeZone));
    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(x =>
        {
            ColumnExpression columnExpression = x.First() as ColumnExpression;
 
            SqlConstantExpression timeZoneExpression = x.Skip(1).First() as SqlConstantExpression;
 
            string timeZoneLiteralValue = timeZoneExpression.TypeMapping.GenerateSqlLiteral(timeZoneExpression.Value);
 
            SqlFragmentExpression valueExpression =
                new($"{columnExpression.Table.Alias}.{columnExpression.Name} AT TIME ZONE {timeZoneLiteralValue} AS DATETIMEOFFSET");
 
            return new SqlFunctionExpression(
                "CAST",
                new List<SqlExpression>() { valueExpression },
                false,
                new List<bool>() { falsefalsefalse },
                typeof(DateTimeOffset),
                null
            );
    });
}

Here I am using Transact-SQL CAST and AT TIME ZONE to generate the proper SQL statements for TimeZone conversion.

And now I am changing my query as follows.

var timeZone = "Eastern Standard Time";
 
var orders = await context.Orders
    .Select(x => new
    {
        OrderDate = x.OrderDate,
        ConvertedOrderDate = EfFunctions.ConvertToTimeZone(x.OrderDate, timeZone)
    })
    .ToListAsync();
This time, for ConvertedOrderDate, I am using the EF Function we just wrote. 

And now when we execute the query, EF is creating the following SQL statement.
SELECT [o].[OrderDate], CAST(o.OrderDate AT TIME ZONE N'Eastern Standard Time' AS DATETIMEOFFSET) AS [ConvertedOrderDate]
FROM [Orders] AS [o]
With the following result as above.
OrderDate                     | ConvertedOrderDate
------------------------------| ------------------------------
20/02/2022 12:00:00 am +00:00 | 19/02/2022 7:00:00 pm -05:00
Hope this helps.


Happy Coding.

Regards,
Jaliya

Tuesday, March 1, 2022

C# 11.0 Preview: Parameter Null Checking

You can now try out some C# 11.0 Preview features if you have the latest RTM version of Visual Studio and .NET SDK (Visual Studio 2022 17.1 and .NET SDK 6.0.200 respectively) installed in your machine. And by the way, Visual Studio 2022 17.2 Preview 1 and .NET 7 Preview 1 are also available if you are interested.

In this post let's have a look at a nice feature that's available with C# 11.0, which is improved Parameter Null Checking.

First, make sure you have set <LangVersion> to preview in your .csproj file.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project>

Consider the below code where you are doing a null check of parameters inside a method.

Prior to C# 10,

static void PrintFullName(Person person)
{
    if (person == null)
    {
        throw new ArgumentNullException(nameof(person));
    }
    
    // Or the following
    //person = person ?? throw new ArgumentNullException(nameof(person));
    
    Console.WriteLine($"FullName: {person.FirstName} {person.LastName}");
}

With C# 10,

static void PrintFullName(Person person)
{
    ArgumentNullException.ThrowIfNull(person);
 
    Console.WriteLine($"FullName: {person.FirstName} {person.LastName}");
}

You have to write some extra code (even though it's just a one-line) to achieve the goal. With C# 11.0, we can basically skip that line by doing something like below.

static void PrintFullName(Person person!!)
{
    Console.WriteLine($"FullName: {person.FirstName} {person.LastName}");
}

Note the bang-bang operator, !! positioned after the parameter name. This will cause the C# compiler to emit null checking code for that parameter behind the scene. When multiple parameters contain the bang-bang operator, !!, null checks will occur in the same order as the parameters are declared.

And if we pass null to this method, we will get the exception we used to throw explicitly before.

Unhandled exception.System.ArgumentNullException: Value cannot be null. (Parameter 'person')
at<PrivateImplementationDetails>.Throw(String paramName)
at<PrivateImplementationDetails>.ThrowIfNull(Object argument, String paramName)
More read: 

Do try out C# 11.0!

Happy Coding.

Regards,
Jaliya

Important Update: 2022-04-13

This feature has been removed from C# 11.0: