Thursday, August 26, 2021

EF Core 6.0: Introducing Migration Bundles

In this post, let's have a look at a new feature that's coming with EF Core 6 in November, 2021. That's EF Core Migration Bundles. So far when we are using EF Core, most of the time we are using dotnet ef migrations script and database update to update the database.

The EF team has come up with this new approach to apply migrations that is by creating an executable with the migrations scripts and once we execute it, it will update the database.

To try out EF Core Migration Bundles, you will need at least the EF Core 6 Preview 7 Version of Tools.

# install
dotnet tool install --global dotnet-ef --version 6.0.0-preview.7.21378.4

# if you have already have it installed, then upgrade
dotnet tool update --global dotnet-ef --version 6.0.0-preview.7.21378.4

Once you have this version installed, you should see a new command.

dotnet ef migrations bundle
dotnet ef migrations bundle
You can see a variety of options that you can use, almost all of them are self-described.

Now let's see things in action. Consider I have the following code.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContextservices) =>
    {
        services
            .AddDbContext<MyDbContext>(options =>
            {
                options.UseSqlServer(hostContext.Configuration.GetConnectionString(nameof(MyDbContext)));
            });
    })
    .Build();

await host.RunAsync();

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

    public DbSet<Category> Categories { getset; }

    public DbSet<Product> Products { getset; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Category>(builder =>
        {
            builder
                .HasMany(x => x.Products)
                .WithOne()
                .HasForeignKey(x => x.CategoryId);
        });
    }
}

public class Category
{
    public int Id { getset; }

    public string Name { getset; }

    public ICollection<Product> Products { getset; }
}

public class Product
{
    public int Id { getset; }

    public string Name { getset; }

    public string Description { getset; }

    public int CategoryId { getset; }
}

And I have added some migrations for this DbContext. 

Then I just have to run the bundle command in CLI,

dotnet ef migrations bundle

dotnet ef migrations mundle

Or if you are using VS, run the following command in Package Manager Console. (Note as today with the latest bits, it doesn't work, an issue is already logged: #25555)

Bundle-Migration

Once I do that, it will create an executable named bundle.exe. (EF Core 6 RC 1 will have an option --output that will give us the ability to change the output)

bundle --help

Then I just need to executable the bundle.exe. By default, it will pick up the connection string from appsettings.json. Or you can pass in the connection as an option by doing something like this,

.\bundle.exe --connection "<ConnectionString>"

You can also pass in a target migration if you want to, something like below.

.\bundle.exe 20210825094417_Initial --connection "<ConnectionString>"

So what's the advantage of using migrations bundle.

  • Can be made a self-contained executable with everything needed to run a migration. For example in DevOps pipelines, you don't need to explicitly install dotnet ef.
  • It doesn’t require you to copy source code or install the .NET SDK (only the runtime) and can be integrated as a deployment step in your DevOps pipeline.
Read More:

Sample Code:

Hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, August 23, 2021

.NET 6 Preview 7: Introducing Implicit Namespaces

If you have created a Console Application Project, an ASP.NET Core Web App/Web API project, or a Worker Service Project using Visual Studio 2022 latest preview (17.0.0 Preview 3.1) targetting the latest .NET 6 Preview (.NET 6 Preview 7), you will see a significant change to the template code/boilerplate code that is generated.

All of them now uses Top-Level statements and most importantly you won't see all the usings that were previously there. 

Console Application

Console Application Project (Microsoft.NET.Sdk)

ASP.NET Core Web API

ASP.NET Core Web API Project (Microsoft.NET.Sdk.Web)

Worker Service

Worker Service Project (Microsoft.NET.Sdk.Worker)
But all the projects are compiling fine, so how is this possible, in this post, we are going to find it out.

This is all made possible via Global Usings that got introduced as part of C# 10. The .NET SDK  now implicitly includes a set of default namespaces for C# projects which targets   .NET 6 or later and use one of the following SDKs:

  • Microsoft.NET.Sdk
  • Microsoft.NET.Sdk.Web
  • Microsoft.NET.Sdk.Worker
Microsoft.NET.Sdk
// <autogenerated />
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
Microsoft.NET.Sdk.Web
// <autogenerated />
// Microsoft.NET.Sdk related global usings plus the following
global using global::System.Net.Http.Json;
global using global::Microsoft.AspNetCore.Builder;
global using global::Microsoft.AspNetCore.Hosting;
global using global::Microsoft.AspNetCore.Http;
global using global::Microsoft.AspNetCore.Routing;
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;
Microsoft.NET.Sdk.Worker
// <autogenerated />
// Microsoft.NET.Sdk related global usings plus the following
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;
You can find these global usings inside in the projects obj directory in {ProjectName}.ImplicitNamespaceImports.cs file.

You can use DisableImplicitNamespaceImports property to disable this feature completely,
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
Or alternatively, you can disable only a set of implicit namespaces, using one of the following SDK-specific properties.
<!--Disable implicit namespaces in Microsoft.NET.Sdk-->
<DisableImplicitNamespaceImports_DotNet>true</DisableImplicitNamespaceImports_DotNet>

<!--Disable implicit namespaces in Microsoft.NET.Sdk.Web-->
<DisableImplicitNamespaceImports_Web>true</DisableImplicitNamespaceImports_Web>

<!--Disable implicit namespaces in Microsoft.NET.Sdk.Worker-->
<DisableImplicitNamespaceImports_Worker>true</DisableImplicitNamespaceImports_Worker>
or you can add or remove individual namespaces using the <Import> item group. For example, let's in a project that targets Microsoft.NET.Sdk, I need to remove System.Net.Http and add System.Text.Json to Implicit Namespaces.
<ItemGroup>
  <Import Remove="System.Net.Http" />
  <Import Include="System.Text.Json" />
</ItemGroup>
Then the implicit usings would be as follows.
// <autogenerated />
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Threading;
global using global::System.Threading.Tasks;
global using global::System.Text.Json;
Hope this helps.

More read:

Happy Coding.

Regards,
Jaliya

Wednesday, August 18, 2021

C# 10.0: Introducing File Scoped Namespaces

C# 10.0 is expected to ship with .NET 6 in November, 2021 during .NET Conf 2021 (November 9-11), but some of the features are already available with C# preview LangVersion. In this post let's have a look at File Scoped Namespaces that got shipped with .NET 6 Preview 7 SDK last week.

To try out this feature you will need to use the latest Visual Studio 2022 Preview, which includes the latest .NET 6.0 preview SDK, or alternatively, you can install .NET 6 Preview 7 SDK and use the latest Visual Studio Code.

You can set the LangVersion as preview in your csproj file as follows.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    ...
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

</Project>

Now, this is how we are defining namespaces.

namespace ConsoleApp1.Models
{
    
public class SomeClass
    {
    }
}

So what's the issue with this. Well, there is no technical issue, but this is adding unnecessary indentation to the code and you might have experienced this: when the class becomes longer, you sometimes lose track of indentation. So less indentation is always the best.

Starting with C# 10, you can do something like this.

namespace ConsoleApp1.Models;

public class SomeClass
{
}

Notice the introduction of semicolon after the namespace and the removal of curly braces after the namespace. That's just one less indentation.  

Personally, this is one of my favorite features in C# 10. Hope this helps.

Happy Coding.

Regards,
Jaliya

Tuesday, August 17, 2021

.NET 6 Preview 7: Introducing Static Results Utility Class for Producing Common HTTP Responses as IResult Implementations

Last week .NET 6 Preview 7 was released and one of the new features that got introduced to the world of ASP.NET Core is a new static Results utility class to produce common HTTP responses as IResults. IResult is a new return type that got introduced with Minimal APIs. 

If you are new to Minimal APIs in ASP.NET Core or need to refresh your memories, you can read these posts.

Previously we had to use/maintain our own class to Map IActionResult to IResult.  With this new Results utility class, we no longer have to do that.

If you explore the Results class, it has IResult types for almost all the regularly used HTTP responses.
IResult
You can find the sample code here,
      (PR for Updating packages to .NET Preview 7 and introduce use of static Results utility class)

Hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, August 2, 2021

Running Puppeteer inside .NET Azure Function

I wanted to check whether Puppeteer can run inside a .NET Azure Function as code and not in a Docker Container (because with Docker it's definitely possible).

Puppeteer is a Node library that provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

I have created an Azure Function Project with a single HTTP trigger function. So to make sure Puppeteer can run inside a .NET Azure Function App, I am going to call my function with a query parameter of a URL, and I am expecting my function to run Puppeteer, navigate to that URL, download its content and return. So it proves my requirement.

First, we need to install PuppeteerSharp NuGet package, which is the official .NET port of the official Puppeteer. And it's depending on .NETStandard 2.0, so we are all good for .NET Core 2.x, 3.x, and .NET 5 compatibility.

using PuppeteerSharp;

public static class Function1
{
    [FunctionName("Function1")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string url = req.Query["url"];
        if (string.IsNullOrEmpty(url))
        {
            return new BadRequestObjectResult($"Missing 'url' query parameter.");
        }

        var browserFetcher = new BrowserFetcher(new BrowserFetcherOptions
        {
            Path = Path.GetTempPath()
        });

        await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision);

        Browser browser = await Puppeteer.LaunchAsync(new LaunchOptions
        {
            Headless = true,
            ExecutablePath = browserFetcher.RevisionInfo(BrowserFetcher.DefaultChromiumRevision.ToString()).ExecutablePath
        });

        using Page page = await browser.NewPageAsync();
        await page.GoToAsync(url);

        string responseMessage = await page.GetContentAsync();

        return new OkObjectResult(responseMessage);
    }
}

Here the code itself is self-descriptive, I am downloading the chromium browser, launching that through Puppeteer, navigating to the URL, reading the content, and finally returning the content.

I ran the function app locally and the results looked promising.
Working Locally

Then I have deployed this to an Azure Function App with following configuration.
Azure Function App Configuration: Publish, Runtime stack and Version
Azure Function App Configuration: Operating System
Once deployed, tested whether it's working.
Working on Azure
Works like a charm.

Hope this helps.

Happy Coding.

Regards,
Jaliya