![]() |
Quick Replace (Ctrl + H) |
![]() |
Replace in Files (Ctrl + Shift + H) |
// EmployeeId
// employeeId
// employeeid
// EMPLOYEEID
// employeeId
// CustomerId
// customerId
// customerid
// CUSTOMERID
// customerId
![]() |
Quick Replace (Ctrl + H) |
![]() |
Replace in Files (Ctrl + Shift + H) |
// EmployeeId
// employeeId
// employeeid
// EMPLOYEEID
// employeeId
// CustomerId
// customerId
// customerid
// CUSTOMERID
// customerId
.NET 8 offers some exciting updates to System.Text.Json.
One of my favorites of them is, now we have some handy additional functionalities on JsonNode. The JsonNode has following new methods:
namespace System.Text.Json.Nodes;
public partial class JsonNode
{
// Creates a deep clone of the current node and all its descendants.
public JsonNode DeepClone();
// Returns true if the two nodes are equivalent JSON representations.
public static bool DeepEquals(JsonNode? node1, JsonNode? node2);
// Determines the JsonValueKind of the current node.
public JsonValueKind GetValueKind(JsonSerializerOptions options = null);
// If node is the value of a property in the parent object, returns its name.
public string GetPropertyName();
// If node is an element of a parent JsonArray, returns its index.
public int GetElementIndex();
// Replaces this instance with a new value, updating the parent node accordingly.
public void ReplaceWith<T>(T value);
}
public partial class JsonArray
{
// Returns an IEnumerable<T> view of the current array.
public IEnumerable<T> GetValues<T>();
}
For example, GetValueKind can be a huge win.
Consider the following example with .NET 8.
using System.Text.Json.Nodes;
JsonNode jsonNode = JsonNode.Parse("""
{
"name": "John Doe",
"age": 42,
"isMarried": true,
"addresses":
[
{ "street": "One Microsoft Way", "city": "Redmond" },
{ "street": "1st Street", "city": "New York" }
]
}
""")!;
Console.WriteLine(jsonNode.GetValueKind());
//Object
Console.WriteLine(jsonNode["name"]!.GetValueKind());
//String
Console.WriteLine(jsonNode["age"]!.GetValueKind());
//Number
Console.WriteLine(jsonNode["isMarried"]!.GetValueKind());
//True
Console.WriteLine(jsonNode["addresses"]!.GetValueKind());
//Array
Imagine how much code we needed to write prior to .NET 8 to achieve the same.
Isn't this handy?
Read more:
What’s new in System.Text.Json in .NET 8
Happy Coding.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.MapGet("/users/{userId}", (int userId) =>
{
return userId;
});
app.Run();
@HostAddress = http://localhost:5179
@userId = 1
GET {{HostAddress}}/users/{{userId}}
Accept: application/json
{
"development": {
"userId": 2
},
"test": {
"userId": 3
}
}
|
Environments |
|
Environment: development |
|
Environment: test |
{
"development": {
"userId": 4
}
}
|
User-specific environment file |
In this post let's see how we can preserve Stack<T> order when it's getting passed between Orchestrators/Activities in a .NET In-Process Azure Durable Functions.
As you already know, when using Orchestrators/Activities in an Azure Durable Function, different types of data are being serialized and persisted. Durable Functions for .NET In-Process internally uses Json.NET to serialize orchestration and entity data to JSON.
And when you pass in a Stack<T> to an Orchestrator or an Activity for an example, the order of items will not be preserved when it's serialized. It's actually an issue with Json.NET.
We can reproduce the issue simply by creating a simple .NET In-Process Azure Durable Function, something like below.
public static class Function1
{
[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
string instanceId = await starter.StartNewAsync("Function1");
log.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, TimeSpan.FromSeconds(30));
}
[FunctionName("Function1")]
public static async Task<Stack<string>> RunOrchestrator1([OrchestrationTrigger] IDurableOrchestrationContext context)
{
Stack<string> transitions = new();
transitions.Push("T0");
transitions.Push("T1");
transitions.Push("T2");
return await context.CallSubOrchestratorAsync<Stack<string>>("Function2", transitions);
}
[FunctionName("Function2")]
public static async Task<Stack<string>> RunOrchestrator2([OrchestrationTrigger] IDurableOrchestrationContext context)
{
Stack<string> transitions = context.GetInput<Stack<string>>();
transitions.Push("T3");
transitions.Push("T4");
transitions.Push("T5");
return await Task.FromResult(transitions);
}
}
The expected output should be as follows.
[
"T5",
"T4",
"T3",
"T2",
"T1",
"T0"
]
|
Incorrect Result |
This can be addressed by applying a custom serialization to the Durable Function.
First, we need to create a custom JsonConverter to serialize/deserialize Stack<T> correctly preserving the order.
/// <summary>
/// Converter for any Stack<T> that prevents Json.NET from reversing its order when deserializing.
/// https://github.com/JamesNK/Newtonsoft.Json/issues/971
/// https://stackoverflow.com/a/39481981/4865541
/// </summary>
public class StackConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return StackParameterType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
try
{
Type parameterType = StackParameterType(objectType);
MethodInfo method = GetType().GetMethod(nameof(ReadJsonGeneric), BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericMethod = method.MakeGenericMethod(new[] { parameterType });
return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer });
}
catch (TargetInvocationException ex)
{
// Wrap the TargetInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private static Type StackParameterType(Type objectType)
{
while (objectType != null)
{
if (objectType.IsGenericType)
{
Type genericType = objectType.GetGenericTypeDefinition();
if (genericType == typeof(Stack<>))
{
return objectType.GetGenericArguments()[0];
}
}
objectType = objectType.BaseType;
}
return null;
}
private static object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
List<T> list = serializer.Deserialize<List<T>>(reader);
Stack<T> stack = existingValue as Stack<T> ?? (Stack<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
for (int i = list.Count - 1; i >= 0; i--)
{
stack.Push(list[i]);
}
return stack;
}
}
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
[assembly: FunctionsStartup(typeof(DurableFunctions.StackSerialization.Startup))]
namespace DurableFunctions.StackSerialization;
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IMessageSerializerSettingsFactory, CustomMessageSerializerSettingsFactory>(); // ...
}
/// <summary>
/// A factory that provides the serialization for all inputs and outputs for activities and
/// orchestrations, as well as entity state.
/// </summary>
internal class CustomMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
{
public JsonSerializerSettings CreateJsonSerializerSettings()
{
return new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None,
DateParseHandling = DateParseHandling.None,
Converters = new List<JsonConverter>
{
new StackConverter()
}
};
}
}
}
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 { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"<ConnectionString>");;
}
}
public class Customer(string name)
{
public int Id { get; init; }
public string Name { get; init; } = name;
}
public class Order
{
public int Id { get; init; }
public Customer Customer { get; set; }
public decimal Amount { get; set; }
}
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<int> orderIds = 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 Id, string CustomerName, decimal 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);
}
Happy Coding.
Regards,
Jaliya
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 |
@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
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
</Project>
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<string, string> keyValuePairs) =>
{
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();
@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
In this post, let's see how you can install SQL Server Express LocalDB in GitHub Workflows. This is useful when you want to run integration tests in a Workflow.
Technically you can use this approach anywhere as long as the Agent is Windows, as this is just a set of PowerShell commands.
name: Build and deploy
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- ...
- name: Install MSSQLLocalDB
run: |
Import-Module BitsTransfer
Start-BitsTransfer `
-Source https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SqlLocalDB.msi `
-Destination SqlLocalDB.msi
Start-Process `
-FilePath "SqlLocalDB.msi" `
-ArgumentList "/qn", "/norestart", "/l*v SqlLocalDBInstall.log", "IACCEPTSQLLOCALDBLICENSETERMS=YES"; `
-Wait
sqlcmd -l 60 -S "(LocalDb)\MSSQLLocalDB" -Q "SELECT @@VERSION;"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
using System.ComponentModel;
Console.WriteLine(nameof(NameOfHelper.StaticInteger.MinValue));
internal class NameOfHelper
{
public string MyString { get; } = "SomeValue";
public static int StaticInteger;
public string NameOfLength { get; } = nameof(MyString.Length);
[Description($"Returns {nameof(MyString.Length)} of given String")]
public int StringLength(string s) => s.Length;
}