I just knew 2026 was going to start off this way... A complicated setup to do something that should be simple.
Ok, so you want to do some .NET dev work with WSL? Better not choose Ubuntu 24.04 as your distro. I guess this version doesn't ship with some important packages that are needed to make some interop work between Windows and Linux. Anyways, let's get to the steps!
Configure Git for authentication with Azure DevOps
Make sure you have installed Git For Windows on your host machine. Your git installed in Linux will need to use the credential manager in Windows.
Configure the credential helper: git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"
Configure using HTTP: git config --global credential.https://dev.azure.com.useHttpPath true
Set environment variable in ~/.bashrc to force a dialog to popup for authentication so you don't end up using Device Flow (which some tenants, like mine, disallow)
Install packages that allow the browser to be opened from MSAL This part is crucial. When you run dotnet restore --interactive, MSAL will want to open your system's browser so you can use OAuth to sign in and provide credentials for your Azure DevOps instance. Install these packages, which no longer ship with Ubuntu:
apt update
apt install xdg-utils
apt install wslu
wslu is a discontinued project that consisted of utilities for WSL and xdg-open is a program that will open the system's browser.
Edit: If WSL interop stops working You may need to add a WSL interop config (source)
sudo vim /usr/lib/binfmt.d/WSLInterop.conf
Add this line:
:WSLInterop:M::MZ::/init:PF
Automatic instance deletion - I'm always running multiple shell instances and often I'll forget I already had aspire running somewhere else. This new feature will let me run aspire again from anywhere and stop the other instance instead of getting an error!
If you are using Aspire and EF core to manage the schema of your database, this documentation is a great starting place to make the two work together.
The core concept is that you add a worker service that is dedicated to running migrations, creating the initial database, and seeding data to the database. Essentially any operation that should be performed before the actual web app starts up. I implemented this in the blog this evening with the help of Claude Code while I watched Twin Peaks.
One prerequisite was that I had to move my DbContext and Entity classes to a shared library so that the web app and worker services could both access them. I had actually tried to do this when I first created the blog but ran into issues and decided to simplify the design. I first told Claude to separate out the database entities, migrations, and contexts to a separate project. I was surprised how long this actually took. But in the end, something like 20+ files were touched, but all tests passed and the changes looked good.
Next, I started a new chat with Claude (/new command), and gave it the same link provided in this post.
Follow the instructions at the following url to automatically apply migrations when running locally: https://aspire.dev/integrations/databases/efcore/migrations/
I hate having to update package dependencies in projects. Fortunately there is a handy dotnet tool that will report and update packages that are out of date. I used this to update all the packages in the link-blog source code this evening and was pleasantly surprised it just worked. Only issue I found was that because I create a msbuild property to store the OpenTelemetry version (there are three OTel packages with the same version), the tool updated the PackageVersions directly instead of just updating the property. Not a big deal, and I would have been shocked if it was able to handle a corner case like that.
Now I need to see if I can get this to run as a daily CI task.
13 is bigger than 10 and less than 14 and is a prime number. The versioning scheme signifies a break away from .NET versioning and a direction change in the product to be evergreen (latest version is what matters) and going polyglot
Just upgraded the blog to .NET 10 and wanted to quickly jot down my notes before calling it a night.
First, I was prompted to do this because I saw Heroku had a blog post about supporting .NET 10: https://www.heroku.com/blog/support-for-dotnet-10-lts-what-developers-need-know/. I'm glad they were proactive about supporting it on day 1, but I also don't love that I have to trust that my hosting provider will add the support.
The bulk of the work for upgrading was figuring out where I have references to .NET 9 and replacing them, and then upgrading my Nuget packages. I also upgraded to Aspire 13 (not sure why they went from 9->13 version).
I only ran into one deprecated API (KnownNetworks in my forwarding middleware), but that was an easy fix (just use the new KnownIPNetworks property instead).
Seems like everything is still working. Hoping to dive more into the details of .NET 10 the next couple of weeks and give the blog some more love and updates. Maybe even add a search feature and proper paging for posts.
I've updated this blog to Aspire 9.3. This particular release didn't have anything that I wanted to use right away, but this being such a simple blog website, it doesn't really need many of the fancy deployment features, especially since I deploy to Heroku and not Azure.
If I find any features I can use in the coming days, I'll create another post to highlight those.
I just found that there is a new(ish) command for figuring out where a transitive dependency comes from in your dotnet project (starting with dotnet 8.0.4xx)
dotnet nuget why <PROJECT|SOLUTION> <PACKAGE>
If you have a dependency in your project that has a vulnerability, you can use this to figure out which package is bringing it in. For example, System.Net.Http 4.3.0 has a high severity vulnerability. I've found instances where this package is brought into my projects by other packages. It's very handy to be able to trace it with a built-in tool. Before this was available, I would use the dotnet-depends tool, which is a great tool, but a little clunkier than I'd like, and doesn't seem to support central package management.
I'm always excited when I see a new .NET Aspire release. The 9.2 update has lots of small quality-of-life improvements. The most interesting new feature for me is "Automatic database creation support". This means that if you add a Postgres database, it will get automatically created, but the best part is that you can also provide a custom SQL script. This lets you customize the creation. I might use this to seed the database with some fake data, so I don't have to manually create data.
This project is showing really great promise. The team is actively listening to developer feedback and reacting to it quickly.
I'm very excited about this release of .NET Aspire. It's an absolute game changer for me, specifically in doing dev work on this blog.
This release has a bunch of great stuff, but I'm actually only interested in the bug fix to this issue. I really love the `dotnet watch run` command, which will perform a hot reload of your application when you make changes to the source files. Before this bug fix, sockets would not be freed up in a timely manner if the process ends, meaning that on reload, it would try to rebind to the existing ports and fail. This meant my workflow was 1. make a change 2. kill the running process 3. wait 10-15 seconds 4. run dotnet run again. Sometimes, I would have to rinse and repeat 3 and 4, if I hadn't waited long enough.
Now, I run `dotnet watch run`, make changes to my code (.cs, .css, and .razor files), and all I have to do to see the new changes is refresh my browser page. It's like magic. My dev loop has gone from ~30 seconds to see the changes reflected in the browser to near-instantly (the time it takes me to press F5 in Safari).
I've been very happy with .NET Aspire so far. But because I deploy to Heroku and not Azure, I can't take advantage of any of the deployment features. A side project I've been thinking about is to build a Heroku deployer based on a .NET aspire deployment manifest.