Category: .NET

.NETDevelopmentVisual Studio

Centralizing NuGet Package Versions with Central Package Management

In my Running and Building Azure Functions with Modern .NET talk this week at the Mississauga .NET User Group, one of the topics that consistently gets a reaction from the audience is Central Package Management (CPM). Once I show people what it does, the reaction is almost always the same: “I didn’t know this existed — I need to go add this to all my solutions.”

This post is the written companion to that section of the talk. If you’ve ever dealt with the pain of keeping NuGet package versions in sync across a large solution, CPM is going to be immediately relevant to you.

What is Central Package Management?

Central Package Management is a built-in .NET feature that lets you define all NuGet package versions in a single place — a Directory.Packages.props file at the solution root — rather than scattering Version attributes across individual .csproj files.

Here’s a simple example of what that file looks like:

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

And in your individual .csproj files, you simply reference the package without a version:

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>

The version is automatically resolved from the central file. Clean, simple, and immediately obvious what version you’re on when you look at the central file.

Why This Matters

In a solution with many projects, keeping package versions in sync manually is error-prone. You end up with Project A on Newtonsoft.Json 13.0.1 and Project B on 13.0.3 without anyone really noticing until a subtle runtime difference bites you. CPM solves this by making version drift impossible — there’s one place to look and one place to change.

Benefits at a glance:

  • Single source of truth for all package versions in the solution
  • Eliminates version drift across projects
  • Cleaner .csproj files — no version attributes cluttering your package references
  • Supports both direct and transitive dependency version control

Manually Migrate an Existing Solution to CPM

If you already have a solution with a bunch of projects, migrating to CPM is straightforward but can be tedious to do by hand across many .csproj files. Here’s the approach:

Step 1: Create Directory.Packages.props

At the root of your solution, create a Directory.Packages.props file with ManagePackageVersionsCentrally set to true and consolidate all your package versions as <PackageVersion> entries:

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="PackageName" Version="x.y.z" />
<!-- add all packages here -->
</ItemGroup>
</Project>

Step 2: Remove Versions from Project Files

Strip the Version attribute from every <PackageReference> in your .csproj files. If you have many projects, do this with the CentralisedPackageConverter CLI tool (covered below) — don’t do it by hand.

Step 3: Validate and Build

Build your solution and confirm everything resolves correctly. Pay attention to any packages that may have conflicting versions across projects — those will need a conscious decision about which version to standardize on.

This is great, but there must be a better way. Let’s take a look at a community tool that automates this process.

Using the CentralisedPackageConverter CLI Tool

The best way to handle the migration for an existing solution is the CentralisedPackageConverter CLI tool, which automates the tedious parts:

# Install the tool globally
dotnet tool install CentralisedPackageConverter --global
# Run the conversion against your solution folder
central-pkg-converter /path/to/your/solution/folder

The tool will:

  • Scan all .csproj files in the solution
  • Generate a Directory.Packages.props with all discovered versions
  • Remove version attributes from individual project files

I highly recommend using this over a manual migration. It’s fast and reduces the chance of missing something.

Let’s try this out.

We now have a Directory.Packages.props with all discovered versions:

And if we look in our project files, the versions are removed:

Advanced Features


Once you’re on CPM, there are a few additional capabilities worth knowing about.

Overriding Versions per Project

There are situations where a specific project needs a different version of a package than the rest of the solution — say, a legacy integration that can’t move to the latest version yet. CPM handles this with VersionOverride in the individual project file:

<PackageReference Include="SomePackage" VersionOverride="1.2.3" />

Use this sparingly. Its presence in a project file is a signal that something needs attention.

Different Versions per Target Framework

If you have a multi-targeted project, you can conditionally apply different versions by target framework using standard MSBuild conditions within Directory.Packages.props:

<PackageVersion Include="SomePackage" Version="2.0.0" Condition="'$(TargetFramework)' == 'net10.0'" />
<PackageVersion Include="SomePackage" Version="1.5.0" Condition="'$(TargetFramework)' == 'net8.0'" />

Transitive Pinning

This one quietly solves a very real problem. Version drift caused by transitive dependencies — packages your packages depend on — is easy to miss and can cause subtle compatibility issues. CPM supports pinning transitive dependencies centrally, so you stay in control of the full dependency graph, not just the packages you reference directly.

Summary

Central Package Management is one of those features that makes you wonder how you managed without it once you adopt it. If you’re running a multi-project .NET solution, this is a straightforward improvement with immediate payoff — consistent package versions, cleaner project files, and a single place to make dependency updates.

Enjoy!

References

.NETDevelopmentVisual Studio

The New .slnx Solution File Format — A Better Way to Manage Visual Studio Solutions

In my Running and Building Azure Functions with Modern .NET talk this week at the Mississauga .NET User Group, I closed out the .NET tooling section with a quick look at the new .slnx solution file format. It’s one of those changes that doesn’t get a lot of attention but makes day-to-day .NET development noticeably more pleasant — especially if you’ve ever dealt with a gnarly merge conflict in a .sln file.

This post walks through what .slnx is, why it’s better than the traditional .sln format, and how to migrate.

What’s Wrong with .sln?

If you’ve worked with Visual Studio solutions for any length of time, you’ve almost certainly encountered the pain points with .sln files. They’ve been around since the early 2000s and they work — but they come with a set of frustrations that have never really been addressed:

  • They’re nearly unreadable — the format uses GUIDs and magic identifiers that aren’t intuitive at all
  • Merging is painful — a .sln file being touched by two developers at the same time is a merge conflict waiting to happen
  • Tooling struggles with them — custom scripts or CI tooling that needs to parse a .sln file often resorts to fragile string manipulation

Here’s a snippet of a traditional .sln to illustrate:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFunctions", "src\MyFunctions\MyFunctions.csproj", "{A1B2C3D4-E5F6-1234-ABCD-EF0123456789}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
EndGlobal

Not exactly something you want to review in a pull request.

Introducing .slnx

The .slnx format is a new, XML-based solution file format introduced in the .NET 9.0.200 SDK. It addresses all of the above problems:

<Solution>
<Project Path="src/MyFunctions/MyFunctions.csproj" />
<Project Path="src/MyFunctions.Tests/MyFunctions.Tests.csproj" />
</Solution>

That’s it. No GUIDs. No magic identifiers. No indecipherable global sections. Just a clean XML file that clearly describes what’s in the solution.

Why It’s Better

Better readability — the XML structure is immediately understandable. Any developer can open a .slnx file and know exactly what’s in the solution without reverse-engineering the format.

Simplified merging — because it’s XML with simple, meaningful elements rather than a blob of GUIDs and platform strings, merge conflicts in .slnx files are much easier to resolve. In most cases, merging two developers’ changes to the solution file becomes a trivial diff.

Easier parsing — if you write build scripts, CI automation, or any tooling that needs to know what projects are in a solution, parsing a .slnx file is now just standard XML parsing. Reliable and straightforward.

Converting from .sln to .slnx

The .NET CLI makes the migration trivially easy. From your solution folder, run:

dotnet sln migrate

From Visual Studio 2022

Go to File and save your solution file as…and select the XML Solution File format.

Before You Migrate

A few things to check before running the migration:

Commit your existing .sln file to source control before converting — gives you a clean rollback point if anything looks off.

Make sure your entire team is on a compatible toolchain — .NET SDK 9.0.200 or later, and a recent version of Visual Studio 2022 or Visual Studio 2026. Anyone still on an older setup won’t be able to open the solution until they update.

If you have CI/CD pipelines that parse or manipulate the .sln file directly (not uncommon in larger teams), update those to handle .slnx before you switch over.

Should You Migrate?

My honest take is yes, especially for new solutions. For existing solutions, there’s no urgency — your .sln files aren’t going anywhere — but if you’re already bumping into merge conflict pain or your solution file is getting unwieldy, the migration is trivially easy, and the payoff is immediate.

For new projects, I’d start with .slnx from day one. In fact if you’ve migrated over to Visual Studio 2026 it’s now the default options. It’s the better format, it’s supported by all current tooling, and you’ll thank yourself later.

Summary

The .slnx solution file format is a small change with a noticeably positive impact on day-to-day .NET development. XML-based, human-readable, merge-friendly, and trivially easy to adopt — there’s very little reason not to switch. Combined with Central Package Management and the new Azure Functions FunctionsApplication builder, these three improvements together represent a meaningfully cleaner modern .NET development experience.

Enjoy!

References

.NETAzureAzure Functions

Upgrading Azure Functions to .NET 10 — What You Need to Know

In my Running and Building Azure Functions with Modern .NET talk last week at the Mississauga .NET User Group, the session covered a handful of topics that I think every .NET developer building on Azure Functions should know about — upgrading to .NET 10, centralizing package management, and the new solution file format. This is the first in a short series of posts walking through each of those topics. Let’s start with .NET 10 support in Azure Functions and what’s new.

.NET 10 is Now Supported in Azure Functions

Azure Functions now supports .NET 10 on runtime version 4.x, and it’s a big deal for anyone who cares about building modern, long-lived serverless applications. .NET 10 support runs until November 14, 2028, so you’ve got a solid runway once you’re on it.

A few things to keep in mind before you start your upgrade:

  • Only the isolated worker model supports .NET 10. The in-process model is not receiving a .NET 10 update and reaches end of support on November 10, 2026. If you haven’t started migrating off in-process, that date should be your motivation to get moving.
  • .NET 10 runs on Functions 4.x across most hosting plans. The one exception is Linux Consumption, which will not receive .NET 10 support. If that’s your current plan, Flex Consumption is the migration target.
  • The base container images have shifted from Debian to Ubuntu with .NET 10. If you have custom container builds, verify this against the official release notes before upgrading.

Minimum package versions required for .NET 10:

PackageMinimum Version
Microsoft.Azure.Functions.Worker2.50.0
Microsoft.Azure.Functions.Worker.Sdk2.0.5

Make sure you’re on at least these versions or the runtime will not load correctly.

Before You Upgrade — Quick Checklist

  • [ ] Confirm you’re on the isolated worker model (not in-process)
  • [ ] Confirm your hosting plan supports .NET 10 (see above)
  • [ ] Update Microsoft.Azure.Functions.Worker and Microsoft.Azure.Functions.Worker.Sdk to the minimum versions above
  • [ ] If migrating from in-process: swap Microsoft.NET.Sdk.Functions for Microsoft.Azure.Functions.Worker.Sdk, and replace Microsoft.Azure.WebJobs.* packages with Microsoft.Azure.Functions.Worker.Extensions.* equivalents
  • [ ] Verify your HTTP integration choice (see builder pattern section below)
  • [ ] Test locally with Azure Functions Core Tools v4

The New FunctionsApplication Builder Pattern

The biggest developer-facing change in .NET 10 (and technically available since .NET 8 with certain configurations) is the switch to the FunctionsApplication.CreateBuilder pattern. If you’ve been building with the older HostBuilder approach, this will feel familiar but noticeably cleaner.

Here’s what the old pattern looked like:

var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddSingleton<IMyService, MyService>();
})
.Build();
await host.RunAsync();

And here’s the new pattern:

var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
builder.Services.AddSingleton<IMyService, MyService>();
await builder.Build().RunAsync();

Note: ConfigureFunctionsWebApplication() is for functions apps that use ASP.NET Core HTTP integration — it wires up the ASP.NET Core middleware pipeline. If your app is non-HTTP (queue triggers, timers, Service Bus, etc.) and you don’t need that integration, use ConfigureFunctionsWorkerDefaults() instead. Most starter templates will choose the right one, but it’s worth knowing what each does.

It’s a small surface area change but the intent is meaningful. Let me walk through why this matters.

Alignment with ASP.NET Core

ASP.NET Core has used WebApplication.CreateBuilder(args) since .NET 6. Azure Functions now mirrors this with FunctionsApplication.CreateBuilder(args). This consistency across .NET workloads is genuinely helpful — developers who work on both web APIs and Azure Functions no longer need to context-switch between two different initialization mental models.

Direct Access to the Services Collection

The old pattern required you to register services inside a ConfigureServices callback, which added an extra layer of nesting. With the new pattern, you access builder.Services directly — just like you would in an ASP.NET Core Program.cs. Cleaner, more readable, and easier to reason about.

Modern .NET Host Builder Infrastructure

Under the hood, the new pattern is built on HostApplicationBuilder, the modern hosting infrastructure introduced in .NET 6+. This brings with it better performance, improved configuration ordering, and enhanced hosting abstractions. It’s part of Microsoft’s broader effort to unify .NET across web apps, Azure Functions, Worker Services, and other application types — and honestly, it’s a move in the right direction.

I have a sample application over on my GitHub: calloncampbell/FunctionAppMigration at demo3-migrated-net10

What About Flex Consumption?

If you’re migrating off Linux Consumption — or just evaluating where to run modern Azure Functions — Flex Consumption is where the platform is headed and worth understanding alongside your .NET 10 upgrade.

Flex Consumption is a Linux-based hosting plan built on a new backend internally called Legion. It keeps the serverless pay-for-what-you-use billing model you’re used to, but it adds a lot more control:

  • Scale to hundreds of instances in under a minute
  • Up to 1,000 scale-out instances (note: scale-out instances and per-instance concurrency are separate concepts — you configure concurrency independently)
  • Configurable per-instance concurrency
  • VNET integration with scale-to-zero still supported
  • Always-ready instances that reduce cold-start latency (optional; default is 0, so you pay only when you need them)
  • Multiple memory size options
  • Availability Zones support

If you’re building anything serious on Azure Functions right now, Flex Consumption paired with .NET 10 is where I’d be pointing you.

Summary

.NET 10 support in Azure Functions is a worthwhile upgrade. The migration from in-process to isolated worker model is no longer optional — with end of support coming November 2026 you need a plan. And once you’re on isolated worker with .NET 10, the new FunctionsApplication builder pattern makes initialization cleaner and more aligned with the rest of the .NET ecosystem. Pair that with a move to Flex Consumption and you’ve got a solid, modern foundation for your serverless workloads.

In the next posts in this series I’ll cover Central Package Management and the new SLNX solution file format — two more improvements that make the .NET developer experience noticeably better.

Enjoy!

References

.NET

C# on Microcontrollers? Embedded Systems with .NET nanoFramework will show you how.

José Simões’ new book, Embedded Systems with nanoFramework, is a milestone for anyone who’s ever wanted to bring the power and comfort of C# into the world of microcontrollers. What I love about this work is how it breaks down the traditional barriers of embedded development—complex toolchains, steep learning curves, and hardware‑specific code—and replaces them with a modern, flexible, developer‑friendly approach.

At its core, the book shows how the .NET nanoFramework lets you build IoT and embedded solutions quickly, cleanly, and affordably. You can prototype in hours, adapt to customer needs on the fly, and move across ESP32.

José brings deep experience as the founder of the nanoFramework and a multi‑year Microsoft MVP, and it shows.

For a deeper dive on the book, checkout Sander’s post where he goes more in depth.

Want to learn more?

Learn about the .NET nanoFramework from the best!

You can buy this book from several online bookstores. You can get it from Amazon here.

Enjoy!

.NETAIAzureCloudCommunity

Festive Tech Calendar 2024 YouTube playlist

Enjoy!

Reference

https://festivetechcalendar.com/

.NETAzure

Deploying a Blazor WebAssembly App to Azure App Service — The Code Blogger

In previous articles, we have covered various basic aspects of Blazor WebAssembly application. In this article, we are going to demonstrate how the Blazor WebAssembly app can be deployed in an Azure App Service. What are various options for deploying Blazor Apps ? There are two different types of Blazor applications – Blazor Server Apps…

Deploying a Blazor WebAssembly App to Azure App Service — The Code Blogger
.NET

.NET Conf 2022 Focus on MAUI Recap — IntelliAbb

MAUI finally had its day in the limelight at .NETConf with a dedicated “focused” event. There was a lot of good content shared in a roughly 8 hours long live stream. In addition to live stream, there are recorded sessions that are also available at .NET YouTube channel. Here are some of the highlights from the event that I am excited about.

.NET Conf 2022 Focus on MAUI Recap — IntelliAbb
.NET

Everything You Want to Know About the Record Type in .NET: Performance

Originally posted on dotNetTips.com: In my article titled Everything You Want to Know About the Record Type in .NET 5… But Were Afraid to Ask that I …

Everything You Want to Know About the Record Type in .NET: Performance
.NETBooks

Beginning gRPC with ASP.NET Core 6: Build Applications using ASP.NET Core Razor Pages, Angular, and Best Practices in .NET 6

My good friend Anthony Giretti, a Microsoft MVP in Developer Technologies has recently published his book “Beginning gRPC with ASP.NET Core 6″ and I wanted to share the news with the community.

About the book

Beginning gRPC with ASP.NET Core 6 is your guide to quickly and efficiently getting down to the business of building gRPC applications in the Microsoft .NET ecosystem. Readers will dive in and build an application using gRPC and the latest technologies such Angular and ASP.NET Core Razor Pages. 

This book will teach you how to set up an efficient application using industry best practices such as security, monitoring, logging, testing, and more. You will do this by performing Create, Read, Update, and Delete (CRUD) operations on a SQL Server database with Entity Framework Core. From there you will build web applications using Angular and ASP.NET Core Razor pages combined with gRPC APIs.After reading the book, you’ll be able to take advantage of the full range of developer opportunities with gRPC, and come away with any understanding of which usage scenarios are best suited for your projects. And you will possess a solid understanding of the best way to build APIs with ASP.NET Core.

What You Will Learn

  • Benefit from a new way to design APIs
  • Build modern web applications
  • Migrate easily from WCF to gRPC
  • Become comfortable with latest industry programming standards

Pre-order it now from Amazon.

Enjoy!