Wednesday, August 23, 2023

EF Core 8.0 Preview: Raw SQL Queries for Unmapped Types

This is one of my favorite features in EF Core 8.0 (or EF 8). Actually, it's likely this is my all-time favorite in EF Core and I have been waiting so long for this one. 

With EF Core 8.0, now you can write SQL queries that return unmapped types. With EF Core 7.0, we are able to query ONLY scalar (non-entity) types.

Let's have a look at this feature with an example code.

Consider the following Entities and the DbContext.

public class MyDbContext : DbContext
{
    public DbSet<Customer> Customers { getset; }

    public DbSet<Order> Orders { getset; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(@"<ConnectionString>");;
    }
}

public class Customer(string name)
{
    public int Id { getinit; }

    public string Name { getinit; } = name;
}

public class Order
{
    public int Id { getinit; }

    public Customer Customer { getset; }

    public decimal Amount { getset; }
}

Now let's get some data seeded.

using var context = new MyDbContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

Customer customer = new("John Doe");
await context.Customers.AddAsync(customer);

List<Order> orders = new()
{
    new() { Customer = customer, Amount = 200 },
    new() { Customer = customer, Amount = 300 }
};
await context.Orders.AddRangeAsync(orders);

await context.SaveChangesAsync();

With EF Core 7.0, we were able to do something like below.

IQueryable<intorderIds = context.Database
    .SqlQuery<int>($"""
        SELECT
            Id
        FROM Orders
    """
);

foreach (int orderId in orderIds)
{
    Console.WriteLine(orderId);
}

decimal sumOfAmounts = context.Database
    .SqlQuery<decimal>($"""
        SELECT 
            SUM(Amount) AS Value
        FROM Orders
    """
)
    .Single();

Console.WriteLine(sumOfAmounts);

Note: Here in RelationalDatabaseFacadeExtensions.SqlQuery<TResult> Method<TResult> needs to be a scalar type. 

Now let's move to EF Core 8.0. With EF Core 8.0, we no longer have the restriction where <TResult> needs to be a scalar type. 

I am declaring a Custom DTO, something like the one below.

public record OrderDto(int Idstring CustomerNamedecimal Amount);

And now I can write a query to map data to the above type.

IQueryable<OrderDto> orderDetails = context.Database
    .SqlQuery<OrderDto>($"""
        SELECT
            o.Id,
            c.Name AS CustomerName,
            o.Amount
        FROM Orders o
        INNER JOIN Customers c ON o.CustomerId = c.Id
    """
);

foreach (OrderDto orderDto in orderDetails)
{
    Console.WriteLine(orderDto);
}

Output
Isn't it just great?


Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, August 16, 2023

ASP.NET Core 8.0 Preview: All New Identity Endpoints

In this post let's have a look at brand new Identity endpoints which is available with ASP.NET 8 Preview 7.

To get started, let's create a new ASP.NET Core Web API project targeting .NET 8. Now install the latest previews of the following packages.

<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0-preview.7.23375.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0-preview.7.23375.4" />

And update the Program.cs as follows.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add authentication
builder.Services
    .AddAuthentication()
    .AddBearerToken(IdentityConstants.BearerScheme);

// Add Authorization
builder.Services.AddAuthorizationBuilder();

// Add DbContext
builder.Services
    .AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("AppDb"));

// Map Identity System
// Specify the DbContext for the identity store
// Opt-in to use the new endpoints
builder.Services
    .AddIdentityCore<MyUser>()
    .AddEntityFrameworkStores<AppDbContext>()
    .AddApiEndpoints();

builder.Services
    .AddEndpointsApiExplorer()
    .AddSwaggerGen();

WebApplication app = builder.Build();

// Map Identity API Endpoints to Middleware
app.MapIdentityApi<MyUser>();

app
    .MapGet("/", (ClaimsPrincipal user) => $"Hello {user.Identity!.Name}")
    .RequireAuthorization();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.Run();

class MyUser : IdentityUser { }

class AppDbContext : IdentityDbContext<MyUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

I have added inline comments, so it is easy to follow.

And now when I run the application, I can see all the Identity endpoints.

Swagger UI - Identity API
You can use the following .http file to test the main endpoints.

@url=https://localhost:7015
@user=user1
@password=P@ssw0rd!
@email=user1@test.com

# Register User
POST {{url}}/register
Content-Type: application/json

{
  "username": "{{user}}",
  "password": "{{password}}",
  "email": "{{email}}"
}

# Login
POST {{url}}/login
Content-Type: application/json

{
  "username": "{{user}}",
  "password": "{{password}}"
}

# Call secured endpoint
@token=<your token here>
GET {{url}}
Authorization: Bearer {{token}}

David Fowler (@davidfowl) has created a nice example that you can fork and try out.
   davidfowl/IdentityEndpointsSample

Hope this helps.

Happy Coding.

Regards,
Jaliya

Tuesday, August 15, 2023

ASP.NET Core 8.0 Preview: Improved Debugging Experience on HttpContext

I have just noticed this with ASP.NET 8.0 Preview, and it's fantastic.

I am going to debug and inspect HttpContext.

Following is when the project is targeting .NET 7.
HttpContext
HttpContext.Request
HttpContext.User
Now let's have a look at the same when the project is targeting .NET 8.
HttpContext
HttpContext.Request
HttpContext.User
Look how simplified it is. Now it's really easy to find important information without even expanding items.

One more reason to go for .NET 8.

Happy Coding.

Regards,
Jaliya

Monday, August 7, 2023

ASP.NET Core 8.0 Preview: All New AddBearerToken Extensions

With ASP.NET Core 8.0 Preview, we now have some new AddBearerToken extension methods on Microsoft.AspNetCore.Authentication.AuthenticationBuilder. It seems to have been introduced as part of ASP.NET Core 8.0 Preview 4: Microsoft.Extensions.DependencyInjection.BearerTokenExtensions)

 Note: this is for Bearer Tokens and not JSON Web Tokens (JWT).

Let's go by an example. Make sure your project's Target Framework is .NET 8.0.
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>

</Project>
I can now add bearer token authentication as follows.
using Microsoft.AspNetCore.Authentication.BearerToken;
using System.Security.Claims;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddAuthentication()
    .AddBearerToken();
builder.Services.AddAuthorization();

WebApplication app = builder.Build();

app.UseHttpsRedirection();

app
    .MapPost("/login", (Dictionary<stringstringkeyValuePairs) =>
    {
        IEnumerable<Claim> claims = keyValuePairs.Select(x => new Claim(x.Key, x.Value));
        ClaimsPrincipal claimsPrinciple = 
            new(new ClaimsIdentity(claims, BearerTokenDefaults.AuthenticationScheme));

        return Results.SignIn(claimsPrinciple);
    });

app
    .MapGet("/", (ClaimsPrincipal claimsPrincipal) =>
    {
        return Results.Ok(claimsPrincipal.Claims.Select(x => new { x.Type, x.Value }));
    })
    .RequireAuthorization();

app.Run();
Note the AddBearerToken() on AuthenticationBuilder.

I can try it out using the following .http file.
@hostname=localhost
@port=7285
@host={{hostname}}:{{port}}

POST https://{{host}}/login
Content-Type: application/json

{
    "name": "John",
    "role": "administrator"
}

# Replace the token below with the access_token returned from the previous request
GET https://{{host}}
Authorization: Bearer CfDJ8HM03LdIurJAspnEgIvCgGvRg4CERoTTGFrsX-2P_XJaCwdzo8bH6DoKZgL51KM_W8Qr1iB8U3XKatYAVMLubrqXJmkPWOATrGudmGEcGINZwl04m1Eue6U-fyYTevKGZG-dSyKPmtaYBbHqSiknhLe07VlUTFgLHDGw1Yd6m5A4N4KFZkj9fB7Ciyfn3YoBanEyXQTqGxOntz_hnVazocL6xONaIThfGmHx3kgLMjG72Vfte8cp0cV89u1cXzkTTapPz2k9yXuCBjgO-Oks49OVRCTETQcdd5vIyV1xJTkKWyGaQOxkImtwDoUqPE1rwA
.http
Isn't it handy? 

Hope this helps.

Happy Coding.

Regards,
Jaliya