A Hybrid .NET Core Development Environment

Brian Richardson
6 min readJan 18, 2021

Things have changed a lot. Gone are the days where a huge honkin’ desktop dominates your desk. Desktops are exchanged for notebooks, and in my case, notebooks exchanged for an iPad Pro. I won’t spend too much time dwelling on this, except to note that my needs now involve a solid thin client and cloud desktop. I’ve migrated a lot of the heavy lifting to the cloud.

It is in the course of building this cloud desktop that I’ve been able to really flesh out a good hybrid development environment. By hybrid, I mean both Windows- and Linux-based. And while a cloud desktop can easily be two cloud desktops for about the same price, it’s still better to have everything in one place. By now, you’ve probably heard about Windows Subsystem for Linux. But maybe you don’t know all the things you can do with it. Let’s follow along my build briefly so that I can show you the highlights and some neat tricks.

Linux on Windows

I won’t go into the specifics of installing WSL or updating to WSL2 as the document linked above should cover most of it. I’ll mention briefly that one of the major differences between WSL and WSL2 is that WSL2 really looks a lot more like 2 separate machines than a single machine. WSL seems to have concentrated on integrating Linux and Windows closely, while WSL2 improves performance at the cost of slightly reduced usability. Maybe WSL3 will get the best of both worlds.

The Windows store contains Linux images. I use Ubuntu, though you can also find other Linux images for use with WSL. Once you have installed your image and fired up bash, you can begin.

Networking

The main challenge to overcome here is the networking. I don’t want to have to remember which of my dev machines a particular service belongs to, so we’ll have to configure the Windows server to do a bit of network routing. There are 3 IPs that we need to worry about: the primary IP address of the Windows machine, the primary IP address of the Linux machine, and the secondary IP of the Windows machine that is on the same subnet as the Linux machine. Look at the examples below to see what I mean:

I’ve highlighted the three IP addresses of interest. There’s actually a fourth IP address of interest as well: the local loopback interface, 127.0.0.1. The local loopback interface is interesting as it acts as a sort of bridge between WSL2 and Windows. In this case, our client-facing IP address is the primary address of the Windows machine, 10.0.0.4. Now it is interesting to note the IP address of the Linux machine, but because of the way the local loopback works, we don’t need to use it except to see that it exists and understand the nature of the barrier between the two systems.

To make this work, we need to enumerate the services that will exist on both sides of the fence as it were. For example, in my case, the Windows machine will be making SQL Server available to Linux, and the Linux machine will be making SSH, AMQP(S), HTTP(S), Redis and MySQL available to Windows. What we want to do is set up the Windows machine to accept all the Linux services and redirect them via the local loopback.

Confused yet? I guess it’s time for some examples. This output shows all the services that are being offered by the Windows machine:

PS C:\Users\brian> netstat -an | findstr 10.0.0.4 | findstr LISTENING

TCP 10.0.0.4:22. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:139. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:3306. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:5672. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:5985. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:6379. 0.0.0.0:0. LISTENING

TCP 10.0.0.4:15672. 0.0.0.0:0. LISTENING

So, there’s SSH, SMB, MySQL, AMQP, WinRM, Redis and RabbitMQ Management. Some of them are on Windows and some are on Linux. But the client doesn’t care, because all of those services are available via the Windows machine. Time for the magic! Here’s an example of providing a Windows service to the Linux machine using the netsh portproxy:

netsh interface portproxy add v4tov4 listenaddress=172.31.48.1 listenport=1433 connectaddress=127.0.0.1 connectport=1433 protocol=TCP

This will make SQL Server available on 172.31.48.1. So what? That address changes all the time, so why is it so important to get SQL Server listening there? Because 172.31.48.1 is actually resolved by a known hostname: <machinename>.local. I’m not sure where I learned about this or where it was added, but dev01.local will resolve to a useable address on both Linux and Windows.

Likewise, we provide Linux services to Windows:

netsh interface portproxy add v4tov4 listenaddress=10.0.0.4 listenport=3306 connectaddress=127.0.0.1 connectport=3306 protocol=TCP

Finally, we open up the firewall for all services to be provided:

netsh advfirewall firewall add rule name=”MySQL” dir=in action=allow protocol=TCP localport=3306

That’s it! You now have a Windows/Linux (WSL2) machine that will provide services from both environments via the Windows IP address.

Some quick tidbits before moving on:

  • You can get an X11 server working on the Windows side without enabling public access by forwarding port 6000 from the Windows side as above and setting your DISPLAY variable to <machinename>.local:0.0
  • I know SQL Server runs on Linux as well. Don’t try to install it on WSL as per the documentation.
  • Don’t go too far down the graphical desktop road. WSL2 doesn’t have systemd support (for the most part. See wsl-genie for an idea on how to make it work. But it’s messy). Lacking systemd support will make a lot of things not work well. But firefox does, so good enough I guess.

The Local Loopback

The local loopback is where all the weirdness happens. To be brief, any service that is bound to 0.0.0.0 or :: on the Linux side will have a corresponding listener on 127.0.0.1 on the Windows side. But it only works if you bind to all interfaces. 127.0.0.1 refers to a different loopback on Linux.

The .NET Core Environment

On to the code! At the time I write this, the latest release of .NET Core is .NET 5. This release supports Windows, Linux and macOS. Follow the directions at https://dotnet.microsoft.com to install .NET 5 for both Windows and Linux. .NET 5 is installed with Visual Studio 2019 version 16.8. Once you’ve installed .NET 5, it’s pretty smooth sailing from here.

Testing — Visual Studio Code

Visual Studio Code has the WSL-Remote plugin, which allows us to quickly switch between our Windows and Linux environments in the same code base. Clicking on the little green icon in the lower left of the Visual Studio Code window will allow you to “Open current folder in WSL”. Create or download a small project that runs on .NET, and verify it runs on Windows. Now, open the current folder in WSL, and run it again. Now it runs on Linux using the same debugger.

Visual Studio Code is rapidly becoming a first-class citizen among .NET IDEs. While it is still under development, the community approach to developing the core features, and the extensible architecture all combine to produce a quality IDE that has many important features. You are not able to connect to WSL from Visual Studio or Rider as yet.

Closing Remarks

While full Linux support seems to be a little ways off yet, Microsoft has at least made it possible to develop .NET Core on Linux without too much difficulty. Creatively combining the Windows and Linux services allows us to create a facade of a single machine providing all services. Visual Studio Code is a great way to get started with .NET Core, and the WSL-Remote plugin is a novel way of testing codebases quickly in multiple environments. I enjoyed this build a lot. The gap between Windows and Linux is closing quickly, and .NET Core will be the driving force.

--

--