.NET 10 Preview 7: Faster JSON, WebSocketStream, MAUI

.NET 10 Preview 7: Faster JSON, WebSocketStream, Safer APIs, and MAUI XAML Source Gen

Are you sure you’ve squeezed everything out of .NET 10 Preview 7? Most teams don’t – and leave real perf, security, and DX wins on the table.

In this practical guide I’ll unpack the Preview 7 changes that matter for day‑to‑day dev work across libraries, ASP.NET Core, EF Core, and .NET MAUI. You’ll get concise explanations, copy‑pasteable snippets, and notes from trying these features in a real codebase.

What to try now:

  • Zero‑copy JSON: Deserialize directly from PipeReader; stream events in minimal APIs.
  • WebSocketStream = Stream: Replace manual ReceiveAsync loops with a regular Stream.
  • Modern crypto: ML‑DSA (experimental), Composite ML‑DSA, and AES‑KWP (RFC 5649) for key wrapping.
  • Safer web defaults: APIs return 401/403 instead of cookie redirects; .localhost hostnames; cleaner exception diagnostics.
  • EF Core 10: LeftJoin/RightJoin, smarter IN translation for parameter arrays, simpler ExecuteUpdateAsync.
  • .NET MAUI: Opt‑in XAML Source Generator, EXIF in MediaPicker, and SafeArea/layout polish.

How to get Preview 7 quickly

# SDK + runtime
winget install Microsoft.DotNet.SDK.Preview

# Confirm
dotnet --info | findstr ".NET SDKs installed"

# In a repo, lock to preview
echo '{"sdk":{"version":"10.0.100-preview.7"}}' > global.json

Tip: on CI, pin with rollForward":"latestMinor" if minor previews move.

Libraries: faster I/O, simpler websockets, and modern crypto

System.Text.Json now reads directly from PipeReader

If you already process incoming bytes through System.IO.Pipelines, you used to bridge to a Stream just to deserialize. Not anymore.

using System.IO.Pipelines;
using System.Text.Json;

var pipe = new Pipe();
_ = ProduceAsync(pipe.Writer);

// New: deserialize directly from PipeReader
var person = await JsonSerializer.DeserializeAsync<Person>(pipe.Reader);
Console.WriteLine($"Hi {person!.Name}");

static async Task ProduceAsync(PipeWriter writer)
{
    await JsonSerializer.SerializeAsync(writer, new Person("Alice"));
    await writer.CompleteAsync();
}

public record Person(string Name);

Why you care:

  • Fewer copies → less GC pressure on high‑throughput servers.
  • Keep your data in the pipeline from socket → JSON without detours.

WebSocketStream: use WebSockets like a stream

Socket code should be boring. With WebSocketStream, you can read/write as if it were any other Stream and let the framework handle message framing.

using System.Net.WebSockets;
using System.Text;

using var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri("wss://echo.websocket.events"), CancellationToken.None);

await using var wsStream = WebSocketStream.Create(cws, WebSocketMessageType.Text);

// Write
await wsStream.WriteAsync(Encoding.UTF8.GetBytes("ping\n"));
await wsStream.FlushAsync();

// Read
var buffer = new byte[1024];
var read = await wsStream.ReadAsync(buffer);
Console.WriteLine(Encoding.UTF8.GetString(buffer.AsSpan(0, read)));

Why you care:

  • No manual ReceiveAsync loops.
  • Plays nicely with APIs expecting Stream.

Note: the stream boundary corresponds to WebSocket messages; big messages are still framed correctly by the stream abstraction.

Post‑quantum crypto & AES‑KWP made approachable

ML‑DSA and Composite ML‑DSA (experimental) land in the BCL with ergonomic APIs.

using System.Security.Cryptography;

// Sign/verify with ML‑DSA (experimental in Preview 7)
using var signingKey = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
byte[] data = "hello"u8.ToArray();
byte[] sig  = signingKey.SignData(data);

using var pubKey = MLDsa.ImportFromPem(signingKey.ExportSubjectPublicKeyInfoPem());
Console.WriteLine(pubKey.VerifyData(data, sig)); // True

AES‑KWP (RFC 5649) is great when you need to wrap a DEK with a KEK (think CMS EnvelopedData with multiple recipients).

using System.Security.Cryptography;

ReadOnlySpan<byte> kek = RandomNumberGenerator.GetBytes(32); // AES‑256 KEK
ReadOnlySpan<byte> dek = RandomNumberGenerator.GetBytes(24); // 192‑bit DEK

using var aes = Aes.Create();
aes.SetKey(kek);

Span<byte> wrapped = stackalloc byte[dek.Length + 16];
int len = aes.EncryptKeyWrapPadded(dek, wrapped);

Span<byte> unwrapped = stackalloc byte[32];
int dekLen = aes.DecryptKeyWrapPadded(wrapped[..len], unwrapped);

Console.WriteLine($"Same DEK: {dek.SequenceEqual(unwrapped[..dekLen])}");

Also notable:

  • Find certificates by thumbprints other than SHA‑1.
  • .NET client TLS 1.3 on macOS.

Launch Windows processes in a new process group

Long‑running workers sometimes spawn children which ignore your signals. Creating a new process group gives you clean isolation for lifecycle management.

using System.Diagnostics;

var psi = new ProcessStartInfo
{
    FileName = "cmd.exe",
    Arguments = "/c ping 127.0.0.1 -n 10",
    UseShellExecute = false,
    CreateNoWindow = true,
    // Preview 7: isolate the new process group (Windows only)
    CreateNewProcessGroup = true
};

using var p = Process.Start(psi)!;

// Later: terminate cleanly, or kill the entire group/tree if needed
if (!p.WaitForExit(3000))
{
    p.Kill(entireProcessTree: true);
}

Works well with shutdown hooks in Windows Services to avoid orphaned children.

ASP.NET Core: safer defaults, smoother auth, better diagnostics

Cookie auth no longer redirects for known API endpoints

By default, unauthenticated/forbidden requests to API endpoints return 401/403 instead of redirecting to a login page.

What counts as “known API endpoints”?

  • [ApiController] endpoints
  • Minimal APIs that read/write JSON
  • Endpoints returning TypedResults
  • SignalR hubs

If your SPA relied on redirects, re‑enable them explicitly:

builder.Services.AddAuthentication().AddCookie(options =>
{
    options.Events.OnRedirectToLogin = ctx =>
    {
        ctx.Response.Redirect(ctx.RedirectUri);
        return Task.CompletedTask;
    };
    options.Events.OnRedirectToAccessDenied = ctx =>
    {
        ctx.Response.Redirect(ctx.RedirectUri);
        return Task.CompletedTask;
    };
});

Configure suppressing exception handler diagnostics

If your IExceptionHandler marks an exception as handled, diagnostics are now suppressed by default. You can opt back in at the middleware level if your telemetry pipeline expects those events.

Passkeys (FIDO2/WebAuthn) improvements

New templates and Identity endpoints keep smoothing the passkey story. For a Blazor app with individual auth:

dotnet new blazor -au Individual -n PasskeyDemo

Then configure default HTTPS and try registering a passkey in the scaffolded UI. You’ll get a front‑to‑back flow wired for WebAuthn without extra packages.

Support for the .localhost TLD

You can now use hostnames like https://api.myservice.localhost and have them treated as loopback. This is handy for multi‑tenant dev setups, or when you model subdomains locally without editing hosts.

Use PipeReader with JSON in minimal APIs

Pair the new serializer overloads with HttpRequest.BodyReader for high‑throughput endpoints.

app.MapPost("/events", async (HttpRequest req) =>
{
    await foreach (var e in JsonSerializer.DeserializeAsyncEnumerable<Event>(req.BodyReader))
    {
        // process chunked events
    }
    return Results.Accepted();
});

public record Event(string Type, DateTime Timestamp);

Enhanced validation for classes and records

ASP.NET Core validation keeps getting closer to how you actually model data: better alignment with C# record/required members and consistent error metadata in responses (great for UI frameworks and API clients).

EF Core 10 (Preview): simpler LINQ, smarter SQL

Parameterized collections → better default translation

EF now prefers scalar parameters for collection Contains (instead of always JSON arrays), preserving plan cache stability and cardinality info.

int[] ids = [ 10, 20, 30 ];
var blogs = await ctx.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();
// Translates to: WHERE [b].[Id] IN (@ids1, @ids2, @ids3)

Tuning knobs still exist when you know more about your data shape.

First‑class LeftJoin / RightJoin

Stop juggling GroupJoin + DefaultIfEmpty for left joins.

var q = ctx.Students
    .LeftJoin(
        ctx.Departments,
        s => s.DepartmentId,
        d => d.Id,
        (s, d) => new { s.FirstName, s.LastName, Department = d!.Name ?? "[None]" });

Quality‑of‑life

  • ExecuteUpdateAsync now accepts a regular lambda, so you can build updates conditionally without expression‑tree acrobatics.
  • Many small translation & perf tweaks (e.g., DateOnly, COALESCEISNULL in SQL Server).

.NET MAUI: faster builds and richer device features

XAML Source Generator (opt‑in in Preview 7)

Compile XAML to code and surface errors earlier. To try it today:

<!-- .csproj -->
<PropertyGroup>
  <EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>
// AssemblyInfo.cs (or any source-available file)
using Microsoft.Maui.Controls.Xaml;

[assembly: XamlProcessing(XamlInflator.SourceGen)]

Benefits you’ll feel:

  • Faster builds and app startup (less runtime parsing)
  • Better IntelliSense on generated types

MediaPicker gains EXIF support

Quick sample that reads orientation:

using Microsoft.Maui.Media;

FileResult photo = await MediaPicker.PickPhotoAsync();
if (photo is not null)
{
    using var stream = await photo.OpenReadAsync();
    var exif = await ExifReader.ReadAsync(stream); // helper from your lib/wrapper
    Console.WriteLine($"Orientation: {exif.Orientation}");
}

SafeArea improvements + new control APIs

Safer defaults around notches/dynamic islands and handy events on pickers & tabs make polishing layouts less painful.

Practical migration notes (from my project notebook)

  • Pin the SDK in global.json so coworkers/CI don’t drift to a different preview.
  • Feature flags: MAUI’s XAML source gen is opt‑in – keep a branch to flip easily if you hit tooling bugs.
  • Auth flows: if your SPA relied on cookie redirects, switch to reading 401/403 and redirect on the client.
  • WebSockets: the WebSocketStream abstraction cleans up code, but remember: a message boundary still matters (don’t expect arbitrary partial records unless you design for it).
  • Crypto: ML‑DSA types are experimental. Guard with #if or IsSupported checks if you ship cross‑platform.

FAQ: upgrading to Preview 7 without surprises

Does Preview 7 add new C# features?

No – not in this drop. C# 14 features continue to bake, but nothing new landed specifically in Preview 7.

Will my API suddenly stop redirecting to login?

If you use cookie auth: yes for known API endpoints. You’ll now get 401/403. Override cookie events if you need old behavior.

Do I need extra packages for WebAuthn/passkeys?

For new templates with Individual auth, it’s already wired. Existing apps can integrate via Identity’s endpoints or your preferred FIDO2 library.

Is TLS 1.3 automatic on macOS?

Client use is supported; avoid hardcoding SslProtocols. Let the OS choose the best protocol.

Any breaking changes in EF Core?

The collection‑parameter translation default changed. If you’ve tuned a query for JSON‑array mode, review plans; you can still choose the old mode per‑query.

Conclusion: ship something with it this week

Preview 7 isn’t just housekeeping – it’s practical:

  • Stream your JSON directly from pipelines.
  • Simplify real‑time code with WebSocketStream.
  • Get safer defaults in web apps and fewer auth surprises.
  • Try MAUI’s XAML codegen to claw back build time.

Upgrade to Preview 7, pick one area (JSON I/O, WebSocketStream, cookie‑auth behavior, or MAUI XAML Source Gen), and ship a small improvement today. What will you try first – and why? Tell me in the comments; I read them all.

Leave a Reply

Your email address will not be published. Required fields are marked *