Back to Blog
5 min read

Exploring .NET 6 Preview Features for Azure Development

Introduction

.NET 6 is shaping up to be a significant release with its LTS (Long Term Support) status and the promise of unifying the .NET ecosystem. As of June 2021, we’re working with Preview 4, which brings exciting features that directly impact Azure development. Let’s explore what’s new and how it affects your cloud applications.

Minimal APIs

One of the most talked-about features is the introduction of Minimal APIs, reducing boilerplate for simple services:

Traditional Controller Approach

// Controllers/WeatherController.cs
[ApiController]
[Route("[controller]")]
public class WeatherController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55)
        });
    }
}

Minimal API Approach

// Program.cs - The entire application!
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/weather", () =>
    Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55)
    }));

app.Run();

record WeatherForecast(DateTime Date, int TemperatureC);

Minimal APIs with Dependency Injection

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddSingleton<IWeatherService, WeatherService>();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// Inject services into endpoints
app.MapGet("/weather", async (IWeatherService weatherService) =>
    await weatherService.GetForecastAsync());

app.MapGet("/products/{id}", async (int id, AppDbContext db) =>
    await db.Products.FindAsync(id) is Product product
        ? Results.Ok(product)
        : Results.NotFound());

app.MapPost("/products", async (Product product, AppDbContext db) =>
{
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Results.Created($"/products/{product.Id}", product);
});

app.Run();

File-Scoped Namespaces

Reduce indentation with file-scoped namespaces:

// Before
namespace MyCompany.MyApp.Services
{
    public class WeatherService
    {
        // Implementation
    }
}

// After (.NET 6)
namespace MyCompany.MyApp.Services;

public class WeatherService
{
    // Implementation - one less level of indentation
}

Global Using Directives

Eliminate repetitive using statements:

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.EntityFrameworkCore;
global using Azure.Storage.Blobs;
global using Azure.Messaging.ServiceBus;

Or in your .csproj:

<ItemGroup>
  <Using Include="System" />
  <Using Include="System.Collections.Generic" />
  <Using Include="System.Linq" />
  <Using Include="Microsoft.AspNetCore.Mvc" />
</ItemGroup>

Hot Reload

Hot Reload enables code changes without restarting your application:

# Run with Hot Reload enabled
dotnet watch run

Supported changes:

  • Method body modifications
  • Adding new methods and classes
  • Lambda expression changes
  • LINQ query modifications

Hot Reload in Azure Functions

// Changes to this function can be applied without restart
[Function("ProcessOrder")]
public async Task<HttpResponseData> ProcessOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
    var order = await req.ReadFromJsonAsync<Order>();

    // Modify this logic and see changes immediately
    var total = order.Items.Sum(i => i.Price * i.Quantity);

    var response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync(new { Total = total });
    return response;
}

DateOnly and TimeOnly Types

New types for date-only and time-only scenarios:

public class Appointment
{
    public DateOnly Date { get; set; }
    public TimeOnly StartTime { get; set; }
    public TimeOnly EndTime { get; set; }
}

// Usage
var appointment = new Appointment
{
    Date = new DateOnly(2021, 6, 15),
    StartTime = new TimeOnly(9, 30),
    EndTime = new TimeOnly(10, 30)
};

// Parsing
var date = DateOnly.Parse("2021-06-15");
var time = TimeOnly.Parse("09:30");

// Entity Framework Core support coming
modelBuilder.Entity<Appointment>()
    .Property(a => a.Date)
    .HasConversion<DateOnlyConverter>();

Improved LINQ

Chunk Operator

// Process items in batches
var items = Enumerable.Range(1, 100);
foreach (var chunk in items.Chunk(10))
{
    await ProcessBatchAsync(chunk);
}

// Useful for Azure Service Bus batch operations
var messages = await receiver.ReceiveMessagesAsync(maxMessages: 100);
foreach (var batch in messages.Chunk(10))
{
    await ProcessMessageBatchAsync(batch);
}

MaxBy and MinBy

// Find the most expensive product
var products = await dbContext.Products.ToListAsync();
var mostExpensive = products.MaxBy(p => p.Price);
var cheapest = products.MinBy(p => p.Price);

// Azure Cosmos DB query results
var orders = await container.GetItemQueryIterator<Order>(query).ReadNextAsync();
var largestOrder = orders.MaxBy(o => o.Total);

DistinctBy

// Get distinct customers by email
var customers = await dbContext.Customers.ToListAsync();
var uniqueByEmail = customers.DistinctBy(c => c.Email);

Priority Queue

New efficient data structure for priority-based processing:

public class MessageProcessor
{
    private readonly PriorityQueue<Message, int> _queue = new();

    public void EnqueueMessage(Message message, Priority priority)
    {
        // Lower number = higher priority
        _queue.Enqueue(message, (int)priority);
    }

    public async Task ProcessMessagesAsync()
    {
        while (_queue.TryDequeue(out var message, out var priority))
        {
            await ProcessAsync(message);
        }
    }
}

public enum Priority { Critical = 0, High = 1, Normal = 2, Low = 3 }

HTTP/3 Support (Preview)

HTTP/3 with QUIC protocol support:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        listenOptions.UseHttps();
    });
});

System.Text.Json Improvements

IAsyncEnumerable Serialization

app.MapGet("/products/stream", async (AppDbContext db) =>
{
    async IAsyncEnumerable<Product> GetProducts()
    {
        await foreach (var product in db.Products.AsAsyncEnumerable())
        {
            yield return product;
        }
    }

    return Results.Ok(GetProducts());
});

Source Generators

[JsonSerializable(typeof(WeatherForecast))]
[JsonSerializable(typeof(List<WeatherForecast>))]
public partial class AppJsonContext : JsonSerializerContext
{
}

// Usage with improved performance
var json = JsonSerializer.Serialize(forecast, AppJsonContext.Default.WeatherForecast);
var forecast = JsonSerializer.Deserialize(json, AppJsonContext.Default.WeatherForecast);

Improved Nullable Reference Types

Better null-state analysis:

public class CustomerService
{
    private readonly ILogger<CustomerService> _logger;

    public CustomerService(ILogger<CustomerService> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<Customer?> GetCustomerAsync(string id)
    {
        ArgumentNullException.ThrowIfNull(id);

        var customer = await _repository.FindAsync(id);

        if (customer is not null)
        {
            _logger.LogInformation("Found customer {Id}", customer.Id);
        }

        return customer;
    }
}

Azure SDK Integration

Simplified Azure SDK configuration:

var builder = WebApplication.CreateBuilder(args);

// Azure clients are now easier to configure
builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(builder.Configuration["Storage:ConnectionString"]);
    clientBuilder.AddServiceBusClient(builder.Configuration["ServiceBus:ConnectionString"]);

    // Use DefaultAzureCredential for managed identity
    clientBuilder.UseCredential(new DefaultAzureCredential());
});

Performance Improvements

.NET 6 brings significant performance gains:

// Benchmark results for common operations
// String operations: 30% faster
// LINQ: 20-40% faster
// JSON serialization: 40% faster with source generators
// Startup time: 25% reduction

Conclusion

.NET 6 Preview brings substantial improvements for Azure development. Minimal APIs reduce boilerplate for microservices, new language features improve code readability, and performance enhancements benefit cloud applications where efficiency translates to cost savings. Start experimenting with these features today to prepare for the November 2021 release.

References

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.