Yesterday I mentioned how enamored I was with Apple's new (last year) generation of self-made silicon laptops, but the lingering question in my mind was, could I completely get away with not having to run Windows in a VM? So I borrowed an M1-based Mac and gave it a shot. The good news is that it's possible, though it took me about four or five hours of messing around to make it roughly equivalent to the Windows experience.
I've tried to go down this road before, probably in the .NET Core 3.1 days. I got pretty close, and I remember even making a blog post showing I could do it, but the experience wasn't great. Part of the question is whether or not Visual Studio for Mac is a first-class experience, as it has evolved from its Xamarin days. I'm not that interested in answering that question, because on the Windows side, Visual Studio is great but still feels a little incomplete without ReSharper. Or maybe I'm just so dependent on those refactoring bits that I can't live without it. The limitation in those days was working with Azure Functions, and, spoiler alert, that's still the hard part. To be fair, they barely worked right on Windows when a new .NET came out. I'm also not interested in trying to make VS Code work with a complex solution, because while I read it might be possible, the mix of plugins and CLI stuff is too complicated.
To make this as real-world as possible, I of course wanted to be able to work on POP Forums in an unrestricted way. That app has Azure Functions, a Razor Class Library (RCL), TypeScript, embedded and built JS and CSS, Azure Storage, ElasticSearch and Redis, plus SQL Server. Lots of tools, lots of stuff to potentially go wrong!
This time, I started with JetBrain's Rider, the variation on IntelliJ for .NET that appears to share some code with ReSharper. I've been Rider-curious for a long time, in no small part because it's crazy fast compared to Visual Studio, especially Visual Studio with ReSharper. Things are different, but they are for the most part all there. The learning curve is not that big, and it's mostly an issue of looking around and learning some different keyboard shortcuts, though most are the same.
Next, the supporting stuff is easy enough to spin up in Docker containers. Specifically, I use containers for SQL Server, Redis, ElasticSearch and Azureite. The last one is to simulate Azure Storage, and while Rider has some stuff to run it on Node, I find it's easier to just run the container. Those parts are easy enough.
Next was figuring out how to make an old Gulp task run, as it copies a bunch of npm packages and minifies and stuff. It does so in the aforementioned RCL so it can be packed away in a Nuget package. You need Node.js installed, just as you do on Windows, but beyond that, the documentation shows simply how to make the Gulp part of your build.
So about the Azure Functions. As fantastic a resource as these are in Azure, they don't feel coherent with .NET or Azure. They break conventions, have had weird dependencies over the years, and used to lag behind .NET releases. It's better, but not great. They wouldn't even build out of the box on an ARM Mac. The first part, and it was hard to find this, is that you need to install the tooling via Homebrew, as called out on the page that took me hours to find. Prior to this, it wouldn't build because it was looking for some .NET 3.1 stuff, which doesn't even run natively on ARM Macs. I then had to point Rider at the installed new tools in its settings.
Next, the functions couldn't connect with SQL Server, because using a trusted connection string won't work since it's not Windows. (I have this for simplicity.) On the web app, it was easy enough to make a
.gitignore it, and override the connection string with an actual user ID and password. But in the functions app, no, of course it's not the easy. That team wants to force you into stashing your settings in a file called
local.settings.json or else, in order to avoid it being using in actual Azure. That means the environmental settings files are a no-go. You can't specify any arbitrary name, which you'll never know because that's in the source you'll never look at. That's super lame, because a lot of teams use these variation files not for non-local environments, but so they can experiment locally without risking a commit of their config values to source control. So I had to set up an environment variable in Rider, which I guess is fine, but only because I don't need to change it often.
At this point, everything builds, everything runs, and at it's just a matter of seeing if the coding endeavor works the way you're used to in Windows Visual Studio. The big concern I had was the combination of hot reload and what's called browser link on Windows. There are settings for "Hot Reload" in Rider but it never worked for me. There's a plugin called .NET Watch Run Configuration, and it's like magic.
dotnet watch is pretty straight forward, but for reasons I can't explain it watches everything, including CSS and TypeScript, even in the RCL, and it magically reloads the page in the browser, unprompted. I assume it's talking directly to Chrome. I don't even need to save the file, it's already happening when I change a Razor file in the RCL, and it trickles down into the web app. I haven't even been able to get this working consistently on every machine using Windows Visual Studio. I'm beyond excited about this.
The rest is just exploration. There's a structure window that replaces the class member dropdowns in VS, and it also works in HTML and CSS files. If you use the right keyboard shortcut profile, many things are the same, like Ctrl-T-L to run all tests, F2 to rename (good thing the touch bar is gone!), Option-Enter then arrows to introduce a private field, or Ctrl-N to get to anything. Things like extract interface are still in a menu, while others like Generate -> Constructor don't have a keyboard shortcut in every profile. I really struggle between using the "ReSharper" and "ReSharper (Mac)" schemes, the latter of which uses Command instead of Ctrl for most things. There are little trade-offs, but you can of course map anything to anything.
Once I figured all of that out though, I was kind of surprised at how fast and robust everything was. It's not even close racing against my Surface Laptop 4. .NET on Mac is legit, and free of Windows.