Understanding the .NET ecosystem: The evolution of .NET into .NET 7
andrewlock.net
andrewlock.net
Coming from NodeJS, the amount of stuff that could be added with a single line from an official package was great. No more worrying about hundreds of unvetted dependencies.
It allows you to just build things rapidly without worrying much about patterns/naming because once your vague ideas solidify and you realize where you went wrong, it's really easy to just Ctrl+Shift+R and make any codebase-wide adjustments instantly.
It's also great about picking up sub-optimal implementations or patterns with helpful warnings, and you just Ctrl+. to have it auto-fix for you. Using these Rider features combined with Github Copilot, I was able to pretty easily learn intermediate level C# because it's like having a mentor working along side you.
edit: been using Rider on Linux since a few years now
No, it uses Resharper as the backend.
But I use a .editorconfig tuned stylecop and roslynator on all projects so maybe that overrides some defaults.
Mind sharing which Java style rules are you seeing ?
Most of that disk size is supposedly in the toolchain (requiring 2-60GB of disk space alone[1]). Why is this so big? Modern toolchains from other vendors are not this large.
[1] https://learn.microsoft.com/en-us/visualstudio/releases/2022...
It turns out native code for all kinds of OS workloads take some space.
Go see how much GB, Apple or Android development stacks require.
$ pacman -Qi emacs | grep Size
Installed Size : 111,46 MiBSo what exactly does VS save you? Hell, I can’t even imagine what’s inside that.
[0] https://learn.microsoft.com/en-us/windows-server/administrat...
https://learn.microsoft.com/en-us/dotnet/core/install/linux
Also VS Code, an open source IDE with first class support for .NET languages:
It turned me right off C# outside of work. There are plenty of other languages that are better suited for hobby development.
The cynic in me suspects all the drama was directly related to Scott Hanselman suddenly losing interest in bloggin.
I'm not sure why they do not open source the debugger. I suppose it is obvious to some extent that some companies (JetBrains) are able to charge for high quality tooling. Microsoft makes a trickle of money from Visual Studio professional.
Though the objective function and decision variables are somewhat opaque, this is clearly an optimization problem.
We're moving to .Net, and I was surprised by how poor the built-in DB stuff is. It's like either assembly or Python, but nothing in the middle.
That said I've also been impressed about how nice it is to get stuff going. I used C# back in the .Net 1.1 days and yeah massive difference in ergonomics.
Dapper seems to be in the middle and it is pretty popular
Important concepts: .NoTracking and setting the EntityState correctly ( mark as deleted, updated, ... )
Additionally, sometimes it's easier to update things granularly, instead of all at once.
I suppose the annoyance comes from updating in bulk on a POST and trying to map everything?
[1]: https://linq2db.github.io/#update
[2]: https://linq2db.github.io/articles/sql/Join-Operators.htmlRight now EF Core is probably the best ORM that has ever existed. What exactly is missing?
Although for performance you would probably reach for something like Dapper but that is not an ORM.
We're not used to something like EF, perhaps it would work for us. But debugging generated queries due to performance issues is something we'd like to avoid. For now the decision was made to not use EF.
To be honest you should keep all ORM queries fairly simple if you can. Where clauses fine. Inserts, updates, deletes, ORMs save so much code, and so much pain when you add new properties/remove them.
But if a query is more than a few includes or joins you should be handcrafting it with FromSQL() or loading it piecemeal using Load().
And don't even think about using it to make complicated reports, that is not a good idea. Make a stored procedure or view.
And that's especially true if you are using anything other than SQL Server. I've seen abysmal performance myself on MySQL/Maria on moderately complex EF queries. I've not really looked since EF 6, but it used to love making nested selects instead of JOINs, which were fine in SQL Server but terrible performance-wise in MySQL. Postgre I've never used with EF in anger so can't comment.
You can use EF with Hot chocolate to make a GraphQL endpoint really easily, but I'd imagine that's an easy way to saddle yourself with serious performance problems unless you limit the levels it can go. I'd be interested to hear if anyone's using it and how they find it?
Also the lazy-loading and the in-memory provider for tests are both kind of misfeatures.
If you don't want to debug difficult queries, then extend it to use Dapper and use the best of both worlds.
There is one gigantic footgun in EF Core, that is the decision between single query and split query. If you choose single query in the wrong situation you can end up with truly pathological queries. I might blame EF Core here a bit for a dangerous default, but to be honest the other choice would be dangerous in a different way, so there is no obvious good default choice here. This is a part that you need to understand to use this ORM, and fortunately it generates warnings now and kind of forces you to choose the strategy.
The one other aspect that helps to generate good queries with EF Core is to use "Select()" for any case where you want to request fewer columns than available in your tables. I find it quite natural to write queries this way in any case.
The Interpolated family of methods take nice, clean string interpolation like $"Select * from Table where Id = {Id}" and make sure that is properly parametric SQL queries (ie, avoiding things like SQL injection attacks).
It's a killer feature and I have some idea why it lives in the EF side of the house rather than being generally applied across all of ADO.NET, but it should still probably be a more reusable library of its own beyond just EF.
I usually call .SaveChanges() when i % 20 == 0
Works faster than with .Tracking. The SQL script runs in your transaction, so rollback runs as usual.
The i%20==0 condition with .SaveChanges() is used with .Tracking(), yes.
Wish I could agree but they would have to fix the very slow time to first query when using big models (+500 tables in our case). Compiled models is not a solution for us since our model changes a lot and the compilation is just as slow. It's disappointing because it used to work fine under the ancient Linq2SQL library.
Also maybe you might find a benefit from splitting your context into multiples. I am considering this option for one of my code bases
I can't split the model without massive refactorings, and even then, some tables are common across all modules and would need to be duplicated. Your advice is unfortunately the standard answer in my case, I guess EF Core is just not for me, really disappointing but "c'est la vie".
edit: ha you probably mean commit it in source control so other devs can also use it? I guess it's a compromise, would still slow down our DbContext refresh command a lot.
Are you relying on model conventions or spelling out everything in modelBuilder calls?
* Support for "FOR UPDATE" and "SKIP LOCKED"; it's easy enough to tag the queries and modify the SQL in an interceptor though.. Yuck.
* Transaction attributes ala Spring; Though I can make do without them it's nice being able to specify what sorta transaction party a method is interested in.
* Better support for "bulk updates". SQL Alchemy, as rough an onboarding experience as it is, has really good support for executing db-side update logic.
That said, I largely agree with you and nobody should overlook LinqPad. Anyone interested in babysitting the generated SQL should be using it; too bad it's not on Linux :| :( ;(
The optimizations they cover in the docs should all be done by default IMHO; optimizing models, polling db contexts, and etc. I also open and close a db connection at app start which further reduced the first request latency after putting a process in rotation.
Having used it on an actual product and dealing with some of the pain points, it's my go-to since the typescript version (v5) came out.
The ORM uses Knex.js internally, which is very simple to drop into if you just want a query builder. Having Knex be accessible also makes it simple to just write your query in plain sql as well, or as the Lucid ORM has available, just fragments of your query (say the join statement) as raw sql: https://docs.adonisjs.com/reference/database/query-builder#w...
Along with debugging, printing out the sql, and support via the Adonisjs REPL "Ace", it makes for a very nice experience.
Which features would you say are the key ones that make Lucid seem more like a query mapper (Dapper, Knex) than an ORM (ActiveRecord, EF)?
Specifically, in my Adonis projects, I'm mostly working with the Model objects through the ORM methods, and only dropping to Knex/SQL when necessary (complex CTE, etc). Since it's such a Model-centric seeming way of development, it naturally seems like an Object-Relational Mapping to me.
Selecting complex dtos, this isn’t query building. A lot of magic turns this into sql.
TopPaidMayors = Cities
.where(c => c.state.govoner.party ==‘dem’)
.select(c =>
c.name,
highestPaid = c.mayors
.orderByDesc(m => salary)
.take(10))
.orderBy(c => highestPaid.First().salary)It's not all magic as well. Looking into the internals of EF, ActiveRecord, Hibernate, or other ORMs reveal patterns that once familiarized can help reason about the behavior of complex queries. I only state this to try to work against the commonly found wisdom of "big frameworks are magic" that tends to scare away learning developers from hoping to understand them.
There are intersections and disjunctions of feature sets between the various ORMs, with some features for EF still only available via extensions (or nonexistent). I don't think this makes the Lucid ORM any less of an ORM.
Again, I like EF Core. I simply think that as far as node-based ORMs go, that Lucid is the one I've had the best experience with, so wanted to highlight it.
There are a ton of joins and sub-selects in that query. 8 very succinct lines. Can you do anything close in Lucid? I don't think so.
However, you seem set on making this a combative conversation rather than a collaborative discussion, so I'll end my participation here.
Ideally I'd like to supply some selects, fill up some DataTables with master-detail data, manipulate it and commit changes.
But yeah, maybe I gotta check out the latest EF stuff and see if I can't convince the others...
Will def look at it more carefully.
Definitely check it out if you haven't recently!
Internal error handling was sometimes abysmal too, a misconfiguration of certain dependencies in `AppModule` could leave the application in a broken state on startup where it wouldn't bind to its port and no error messages were printed to console. On a few occasions I had to spend an hour or more reading and understanding NestJS source code to resolve those issues, which could have been avoided if they had better internal validation and error logging in place.
That's not to say it was all terrible, some aspects of it were genuinely good, but the overall experience and many hours of needless pain it caused left a really bad taste in my mouth. Back then, at least, it felt like a Lego set where the pieces didn't all quite fit together.
Depressingly enough, it seemed like NestJS was the best that Node.js world had to offer which made me quit the ecosystem altogether.
I prefer next.js for the frontends.
A simple HTTP server? Maybe I'm missing something, but when I needed it I hadn't found one.
I believe, the closest it has is System.Net.HttpListener which is a very different thing from your typical Golang's net/http.Server or python's http.server.HTTPServer.
I believe at some point they had switched from HTTP.sys to Kestrel, so at least it doesn't need the admin privileges anymore. But this whole thing is so much related to ASP.NET it's pretty hard to figure out how to create a simplest HTTP server without anything else forced upon you (no services, no "web applications", no router, just plain and simple bare HTTP protocol handler). So my impression is that maybe .NET can make certain complex things easy, but it has some issues with keeping simple things simple.
Productive devs want the request/response wrapper objects and routing constructs to handler methods to get work done and can still drop down into fine-grained request/response crafting as and when required.
That's exactly what I don't do, and what I want to see available in a standard library.
But I quite frequently implement custom request handling before any routing happens (if there's even any routing). That's super easy to do in Go, Python or Rust, but when I needed something comparable in C# I haven't found any similar composable independent pieces that I can join together in a way I see fit.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-...
It’s .NET 101 stuff.
And this stuff is ASP.NET Core, not a bare .NET [Core], isn't it? What I'm talking about is something comparable to just Kestrel, except that I failed to find any documentation on using it "raw" without the whole ASP.NET thing (maybe I misunderstood what it is and it's tightly coupled with the whole framework?).
(This partly reflects the classic HTTP.SYS role in IIS/Windows as well, because Window's HTTP.SYS is surprisingly high level for a "raw" kernel component for hosting web servers. From my understanding, most of "Kestrel" under-the-hood is just a cross-platform semi-recreation of the HTTP.SYS abstraction machine on top of things like but maybe not exactly libuv/ioring. So yes, everything is "naturally" higher level in .NET than Python's lowest level just because it assumes a higher-level "OS server" base.)
Also, yes, the boundary between "Kestrel" and ASP.NET is really hard to define at this point. Almost all of ASP.NET is just "Express-style" (though much of these middleware patterns in ASP.NET I believe predate Express) middleware that is cumulatively stacked on top of each other as you add more high-level ASP.NET features, and at this point all of them are just about optional depending on what you are looking to do.
Even many alternatives to ASP.NET at this point are built on top of the core basics like WebApplicationBuilder, they just diverge at which sets of middleware stack on top of that.
As others point out the recently expanded "Minimal APIs" experience is most tuned for "Flask-like" out-of-the-box behavior: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m...
That's as low level as it gets in .NET, but not so much because of "strong coupling" but because "everything is middleware" in .NET.
[1] https://learn.microsoft.com/en-us/dotnet/core/extensions/gen...
I don't have any issues with the IHost and builder patterns. I actually like those - although I've only used the very basics, so I don't really know about the intricacies and possible drawbacks.
Thanks for clearing my misunderstanding about the coupling. I really thought Kestrel was something different, have not expected it to be this high level. It being a replacement of HTTP.sys totally makes sense, of course.
I've found and read https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m... and it started to make more sense now.
There's a long issues thread on HttpListener should be more strongly marked deprecated [2] to avoid people accidentally using it despite today's recommendations to use Kestrel/the "Most Core" parts of ASP.NET. One fun part of the thread is an example repo of the absolute most "bare-bones" and "raw" Kestrel bootup possible [3], including a "TODO: implement TLS handshake here" bit.
[1] https://learn.microsoft.com/en-us/dotnet/api/system.net.http...
[2] https://github.com/dotnet/platform-compat/issues/88
[3] https://github.com/davidfowl/BasicKestrel/tree/master/BasicK...
Its not. Keep reading.
Disclaimer: looking from sysadmin's POV.
If for some reason you don’t want the framework to handle things like routing or request/response deserialization/serialization then you can go bare bones and implement everything via custom middleware: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m...
The only bit of framework-specific / hidden magic debugging I really had to do was the realization of AsSplitQuery() when creating objects composed of independent datasets, AsNoTracking() for a decent perf bump, and single group-by's are fine but when you start nesting them it gets really hairy really quickly -- they usually ended up becoming views.
Otherwise change-tracking worked wonderfully for batch-updating (but everything I did was short-lived) and outside of group-bys, the LINQ -> SQL mapping (and vice-versa) was extremely predictable, in both EF Core generating the SQL I expected, and creating the correct LINQ from the SQL I knew I wanted.
9/10 would use again; inline sql is for nerds
I don’t know why one would have an issue with not using the features they’re not looking for anyways. Keeping everyone aligned on patterns/usage is half the point of code review, and it was managed there without much trouble (you can’t really do the truly magical incantations without quite a bit of setup)
System.Environment.GetEnvironmentVariable("NAME") ?? ...1. appsettings.json
2. appsettings.{env}.json
3. user secrets (only in Development environment)
4. environment variables
5. command line args
You can full customize the setup if you desire, there are packages to support things like external secret stores. If you Google ‘Asp.Net core configuration” there’s a MS page that goes into great detail on all of this.
Anyway, your env vars must match the name as you’d structure it in a json object, but with the periods replace with double underscores. So ConnectionStrings.MyConnection becomes CONNECTIONSTRINGS__MYCONNECTION, FeatureFlags.Product.EnableNewIdFormat becomes FEATUREFLAGS__PRODUCT__ENABLENEWIDFORMAT, etc.
One nitpick: environment vars don't have to be capitalized. You can do ConnectionStrings__MyConnection so the casing matches what you see in appsettings.json.
And a word of warning: make sure to understand how the configuration overrides work. If you have appsettings.json define an array of auth servers with 5 elements, then in appsettings.Production.json (or env variables) define an array of auth servers with only 1, auth servers 2-5 from the default appsettings.json will still be there! (something similar to this may or may not have caused a scare in the past)
I tend to avoid using arrays in config because of the unexpected behavior, and the risk of overriding something you didn't mean to. Anywhere you'd use an array you can usually use an object and bind it to a Dictionary<string, Whatever>, then ignore the keys.
builder.Configuration.AddEnvironmentVariables();
Then you can use an env variable like "Foo__Bar=X" to override Foo.Bar from your appsettings json.
So much better than spring and NodeJS.
I've been keeping a close eye on Next.js, which is very well done, but I love how versatile .NET is. With one language, I can write all of the above, and my dependencies are minimal thanks to .NET's rich standard library - a refreshing change from the NodeJS apps I've written and maintained.
With native AOT, .NET can even be compiled to native binaries. Recently, I wrote a Windows password filter DLL in C#, which would have been unthinkable some years ago.
It's a really enjoyable stack to work with. Kudos to Microsoft for what they've been doing with it.
I'm more shocked by you sticking with Javascript. Surely with your adoration of C# you would have moved to its closely related sibling Typescript. Although I guess there is that slight initial hump to move to TS from a purely vanilla project.
Is it somehow different than using any other language for your backend? Seems to me you're describing any frontend/backend split, nothing specific to either Nextjs or .NET here.
> I'm more shocked by you sticking with Javascript. Surely with your adoration of C# you would have moved to its closely related sibling Typescript. Although I guess there is that slight initial hump to move to TS from a purely vanilla project.
Some people prefer to stick with vanilla JS because that's what the browser ends up running anyways. Personally, TypeScript tends to get more in the way than be helpful for certain type of projects, while for others, TypeScript helps a lot but tends to be when the codebase involves a lot of contributors of varying skill-levels rather than a small circle of contributors with relatively high knowledge of programming and JavaScript in particular.
I'm not so sure experts of JS would avoid TS just because they're experts in JS. The opposite even, being experts they know how many footguns JS has, and TS just comes with far too many benefits for any project that is to be maintained for longer than a week.
It makes a lot of sense to decouple them, and just re-write the frontend to whatever technology is the best for its time. It’s also not uncommon to have multiple frontends, for example a web application and also a native android/iOS app.
But yeah, it's definitely a huge plus to have access to .NET's massive standard library. The documentation is amazing, too. If the costs to run in Azure and to be tightly-coupled with Microsoft rthat way are reasonable for a lean/small startup or self-funded startup, then I may give it a shot again.
VS has a lot of useful stuff around profiling, performance analysis etc, but as a code editor it's pretty bad.
(Also, Roslyn built-in refactorings have gotten so good, I increasingly feel like I should just develop more in VS Code because the language server is the same.)
No need for MSDN subscriptions these days, although they do come with some perks.
.NET has been open source since .NET Core 1 in 2017. The source code is on GitHub: https://github.com/dotnet/
Using Asp.Net 7 for the server component with React and Vite as the FE tooling. Been a HUGE learning curve the first couple months particularly around the DI, options pattern, Asp.Net auth and Identity and etc but everything is falling into place now. Also SignalR is much more low-level than it pretends to be lol.
It would have been easier to go straight NodeJS as I'm quite proficient in writing even framework level code in it(down to the sockets), but I believe .Net is the better option for this long term.
It feels like the pain everyone went through upgrading from Python 2 to Python 3.
It also doesn’t help that .NET Framework has its support cycle tied to the OS, and hence is 10+ years. This means that businesses can be lazy and just leave these old apps to fester and still be technically “supported”.
As web standards evolve, I’m seeing these web apps slowly break.
It’s actually a big problem and Microsoft doesn’t seem to care much. It’s only in .NET 7 that they’ve finally introduced some incremental migration features, but they’re buggy and incomplete.
As a random example of the issues: .NET Core 1.0 broke DataContract deserialisation because of a missing thing in the BCL. A decade later this is present now but they still haven’t fixed the service client generator tool!
Every time I’ve tried to migrate an app, it’s one breaking issue after another with 3-year old GitHub issues that have no responses from Microsoft.
Talking about the awesome future of .NET is great and all, but you've got to give people a path to get there...
Their complaint was that people invested a lot of resources into a technology that Microsoft promoted, and then that technology hit a dead-end with no good upgrade path aside from "rewrite it all!" What year that occurred is irrelevant.
Ironically you brought up MVC 1.0, which Microsoft did EXACTLY THE SAME THING TO, when they released Asp.Net Core MVC which also has no direct upgrade path. In fact, it wasn't until the last twelve months that Microsoft even tried offering anything when they realized it has been ten years and a lot of companies remain stuck to this day on .Net Framework.
Yes, Web Forms was poorly designed. But this is about Microsoft's poor upgrade offerings more than any specific technology, and an import lesson for people investing time/resources into Blazor today (given that it uses a proprietary WebAssembly compilation system and a proprietary back-end not dissimilar from WebForms in terms of lock-in).
These companies had 15 years to do it. And it was fairly easy to get webforms + MVC running side-by-side on the same site so you could gradually migrate.
Too late for that now though, you can't run webforms + MVC Core side-by-side.
You could put a load balancer in front of the old app and start moving end points to a new code base.
It's akin to moaning that MS haven't got an upgrade path from IE7. Or your Adobe Air app has stopped working.
On the plus side, doesn't 4.7 still have a huge support window because it's tied to one of the windows server versions?
I pointed this out in 2013 only to be literally shouted down by my manager. I did so again in 2017 under a different manager on the same team with the suggestion that we investigate using SPAs and RESTful frameworks only to be told that Razor Pages was the way forward which, while an improvement, didn't do any wonders for our ability to attract/build technical talent or create rich user experiences.
As a sibling comment points out (in a sentiment that I've echoed on HN before):
> ASP.NET Web Forms are a complete trainwreck and an abuse of HTTP and other basic web development standards (e.g. by using javascript: URLs and POSTing forms for every single interaction with the page). It is broken by design.
You can probably see why developers that chose this framework in the first place aren't interested in upgrading... learning actual web standards and state management isn't something they wanted to do in the first place.
Done :-).
> Some products/companies just die, because of bad management that doesn’t react to change
Some don't have to because they get paid no matter what.
This is why I'm personally extremely skeptical of both Razor Pages and all of Blazor (Client, Server, Unified, whatever). All of that feels a lot like "Web Forms Again, this time with more C#" to me. Parts of Blazor especially may as well be ASP Classic `runat="server"` and look just like it to me. It kind of feels like a lot of ASP developers have already forgotten the hard problems of ASP Classic and ASP.NET Web Forms and have been doomed to recreate them cyclically.
The vast majority of ASP.NET Web Apps could not have migrated to .NET Core 1.0, it was missing too many features. It was missing types like 'securestring', and had zero support for Workflow Foundation, Windows Communication Foundation, etc...
At the time, .NET Core was also advertised as "an alternative platform for Linux apps", not as a direct replacement for .NET Framework.
It was only in .NET 5 that Microsoft changes their tune and started calling Core the "replacement". At the time, something like half of complex enterprise apps might be able to migrate across, but they would have encountered a long list of breaking issues with a note saying "we might fix that in an upcoming major release". That's after a partial rewrite.
You can't go to a business that has a web app that's "not broken" and suggest migrating it to a definitely broken platform that's very much still playing "catch up".
Support is improving in .NET 7, and the upcoming .NET 8, but it's definitely not 100% and pretending that it's the "end users' fault" for not jumping onto an incomplete and buggy platform is not helpful.
I'll list some random GitHub issues for you to perouse. For large enterprise apps, many of these are showstoppers for incremental or seamless migrations. The workaround is always "rewrite everything from scratch using wildly different technologies that aren't direct replacements."
IIS app pool recycle throws 503 errors
https://github.com/dotnet/aspnetcore/issues/41340
OData core libraries now support OData v4
https://devblogs.microsoft.com/odata/announcement-odata-core-libraries-now-support-odata-v4/
(.NET Framework is v1-v3, and Core is 4.0+, a breaking change!)
Workflow Foundation didn't start getting migrated until .NET 6, and not by Microsoft!
https://github.com/UiPath/corewf
dotnet-svcutil ignores most of the settings
https://github.com/dotnet/wcf/issues/4887
dotnet-svcutil silently failing to deserialize responses
https://github.com/dotnet/wcf/issues/4163
Reuse of Types not working with WCF dotnet-svcutil tool for .NET Core
https://github.com/dotnet/wcf/issues/4277
Visual Studio 16.8 breaks SvcUtil build targets
https://github.com/dotnet/wcf/issues/4431By MVC 3, which was 2011, it was bloody obvious webforms were not the future of .Net web development.
And you could run both in the same project with a small bit of effort.
Here's a Scott Gu post from 2009 saying so:
ASP.NET 4.0 makes it easy to implement clean, SEO friendly, URLs using both ASP.NET MVC and now ASP.NET Web Forms (you can also have applications that mix the two).
https://weblogs.asp.net/scottgu/url-routing-with-asp-net-4-w...
Besides the obvious IFRAME approach, one could have a single solution with both a ASP.NET Web Application project and another MVC (or Blazor) project. Both of them would reference shared models/services via some .NET Standard 2.0 project. IIS can host ASP.NET Core.
Then start refactoring and rewrite the old HTML4/CSS2 into HTML5/CSS4, while the Server Controls and Master Pages become Razor Components.
There is definitely an upgrade path.
Microsoft is recommending YARP for this: https://learn.microsoft.com/en-us/events/dotnetconf-2022/mig...
That sounds okay, but you'll hit all sorts of fun technical challenges. Good look making WCF client certificate authentication work through this! There are also performance gotchas with buffering, etc...
There are not-atypical scenarios where during a migration, an app might be behind 4+ reverse proxies, all of which are different products.
E.g.: CDN -> App Gateway WAF v2 -> Kubernetes Ingress -> YARP -> Legacy App.
You'll quickly discover all sorts of fun interactions between these and 15-year-old ASP.NET code!
.NET 4.8 is still fully supported, and there is no end-of-life communicated yet. It is a part of windows server 2022, which will be supported until 2031, that’s probably the earliest possible end-of-life date for .NET 4.8.
I don't understand why people are coming out of the woodwork to tell everyone that full rewrites are a perfectly normal and expected regular occurrence that you should expect from your tech choice. Python 3 is famous for how much they hurt themselves by ignoring the problem. Many companies never did migrate from 2 to 3, and instead picked tech with better guarantees. I expect the same to occur with .Net Framework.
With .NET you can still, use most of your code from 2001, just the UI frameworks (and WCF) are not supported anymore on .NET 5+. They are supported at least until 2031 on .NET 4.8. So you can easily move your business logic to a newer version and leave the UI on an older version. But seriously, who is still happy with a web application built in 2005?
Or maybe it will not be supported as in "we won't provide support for any applications needing .NET Framework, but they may or may not still work"?
PowerShell Core has been available for years but still is not included with Windows.
Similarly, even new web UI components such as Admin Center are written using .NET Framework.
I'd wager it is more a question of priorities. I bet Microsoft itself sees Windows Server as legacy tech as they want everyone to move to the(ir) cloud.
Partially it is also a organisational problem. Everything you ship as a Windows component incurs a debt, which is exactly why .NET Framework wasn't able to evolve further.
Old .NET Framework was a main, Windows only, branch of .NET.
At tag "v4.x", a new branch named 'Core' was created. Core was a multiplatform version of .NET.
There were three tags in the Core branch: v1, v2 & v3.
Then Core jumped from v3 to v5 and was merged back into the main branch. ".NET Core" has replaced ".NET Framework". Also, v5 dropped 'Core' from the name and was named just ".NET 5".
Development now happens on the main branch and has already reached tag v7. A new tag is created each year.
I use Rider from Jetbrains, develop on a mac, deploy to Linux on AWS, it would blow the mind of 2003 me to know this was the case.
- Unit testing with mocking is kludgy compared to Java and Kotlin (Moq vs Mockito). There's no mocking of concrete implementations for technical reasons, perhaps unless you shell out money for a paid product. This leads to folks putting interfaces everywhere so that code is testable.
- Along the lines of the above, everything feels a lot more corporatized or tied to Microsoft. The open source ecosystem and tooling around C# isn't to the same level of the JVM langs.
- The project structure feels odd. Solutions hide a lot of files from you, whereas JVM langs generally show what's actually there.
When it comes to actually writing implementations, I don't mind it, and it's far ahead of, say, Java 8. ASP.net might even be ahead of Spring in a few ways. But with Java 20 and Kotlin around, I don't feel compelled to move to it as a new default.
With Moq and most mocking libraries, you generally just need to make a method virtual for mocking concrete classes.
Most mocking libraries use Castle Project's Dynamic Proxy, so they should be able to inject things without the need of interfaces. Interfaces for everything comes from the .net community's obsession with patterns, abstraction, clean architecture, and DDD.
You can use vscode with file based projects. With Rider/Visual Studio you can show hidden folder so long as they are in a folder or subfolder of a project.
Otherwise, you'll need to add a solution folder and items to the solution folder.
VSCode has been my way forward for file-based editing so far. It's not so bad - just feels like I'm doing things that I ought not to be when I'm searching for DLLs or a random "scripts" folder in the root folder of our repo in Rider. In IntelliJ IDEA, all of the folders are just sitting there, ready for quick edits.
I think C# overcommitted on interface-driven design. It's good in some instances, but the more I work in code the more I think that the majority of it is needless and oftentimes harmful to maintaining a healthy code base.
Thanks for the pointers! Nsubstitute looks cleaner than Moq at first glance.
I personally use stubs for higher reuse, favor integration/functional tests over units, and InternalsVisibleToAttribute to allow the use of internal classes / methods from test assemblies. Its been a long while since I reached for a mocking library. I probably use structs, statics, and extension methods more than the typical dotnet dev. And for DI, I have no qualms just using concrete classes or base classes.
I also built my own extensions to xunit to enable DI in test methods, since I do more integration tests.
Extension methods for interfaces can be very powerful along with the newer default interface feature. So using that where it makes sense, I get.
In dotnet itself, interfaces are only heavily used for key extension points, so you see them more enforcing specific idioms like IComparable<T>, IReadOnlyList<T> or adapter/extensions for System.Data.x, Microsoft.Extensions.x, and Asp.net core MVC. For the adapter types of things there is generally a base class that implements key interfaces and you can create stubs or mocks from those.
For the files... for stuff in the root folder like .editorconfig, gitignore, etc. I generally create a virtual Solution folder and add those files to it.
For folders with larger amounts of files, I sometimes create an empty project or use specialized project for it like node or powershell project which can be installed as VS extensions, especially if its for a project where the team lives in Visual Studio.
That said, I do most of my coding in JetBrains rider or vscode these days and keep a terminal open. When I'm on a windows box, I have nano and neovim installed, so I can just quickly edit scripts that way or just do code /path/to/file as needed.
Mocking leads to “interfaces everywhere”, I agree. The latter also results from the attitude of putting interfaces all over, even if there’s just a single concrete implementation that makes sense at any point in time, like a file hashing routine (which of course should just be a function but that’s not a thing in C#). Dependency inversion gone overboard?
My biggest qualm with mocking is setting up a detailed test, where every method called is specified, alongside their order etc. You’re reimplementing the entire method essentially. Those tests I despise.
There are other practical reasons for using interfaces other than just "patterns" and "testing" though. Perf is one; you can avoid a lot of unnecessary mapping by being able to pass concrete types between modules when the receiver is accepting a shape instead of a concrete type...
That's an obvious anti-pattern in the first place, especially when you know the optimizer could inline hot code and optimize the function out.
> Everything feels a lot more corporartized or tied to Microsoft
Remember J2EE and the confusing javax stuff? They're the same and fortunately both are waning out.
> The open source ecosystem and tooling around C# isn't to the same level of the JVM
Isn't that because of the open source community's refusal, rejection and resistance against Microsoft? Not only open sourcing is a first mover takes all, but specific to Microsoft they have been known to be hostile to open source in the past, most prominently one of the former CEOs of Microsoft blatantly called Linux cancer and what do we have today (https://www.theregister.com/2001/06/02/ballmer_linux_is_a_ca...)
Also, it is very well known Microsoft tried to spread FUD in the past in order to kill Netscape Navigator and tried to kill Java until it is almost being antitrusted to the extent of Baby Bells (https://en.wikipedia.org/wiki/Breakup_of_the_Bell_System?wpr...). Still Microsoft is not friendly nor hostile towards open source given that they let Mono lived, despite using Microsoft's trademarks and patents (you heard me right, dotnet has several patents and ECMA standards before!)
It's not like we dotnet people don't know the past shady shit of Microsoft, but I do believe in a convicted criminal could learn from the past, correct itself, move on from the past and be a better person off the record in the long run.
It's like a big bully tried to beat off a skinny nerd but now the nerd is as big as the bully now, and all of a sudden the bully found its conscience and begged for apology. It is natural for the nerd to not accept it in the first place. Except when both are nerds and the analogy may sound kinda odd.
But I also do understand not all people thinks like that especially for the open source community who is wary of Microsoft may attempt to commit a FUD to the open source community and they do have their memory and thus reactions, I don't have any means to control it. In fact most ordinary people would still choose to reject a convicted criminal in their community instead of accepting even if they have stopped the behavior, because it is a social stigma that indicates this person is of high risk
And contrary to Java world, the Oracle situation is getting more and more heated due to its predatory licensing agreement and people are flocking to other Java distributions. This could tear the Java ecosystem apart because it is the .NET Standard situation again and I'm pretty sure histories repeat. By the way, fragmentation is what ultimately killed MIPS and I think RISCV is on the watch.
Alas, time as always will tell, just like it's either hit or miss. We should look for a longer vision and see how it shaped out in the futures.
> The project structure feels odd
No it isn't. It's the multiprojects structure of Gradle, although I'm not sure if you ever heard of it in the first place given your reaction. In JS ecosystem this is aka monorepo but this means .NET Ecosystem actually have monorepos for almost the last two decades!
I don't really remember J2EE, but I do run into weird Javax imports from time to time. +1 on them going away being a good thing.
For the open source stuff, I don't really care that much about the politics or history of it. Maybe I should, but at the end of the day Java just seems to have the libraries I need.
And for the project structure, maybe I should have worded that differently. The structure of nested projects makes sense, but the dependency management through Nuget and the hidden project files in IDEs just seems needlessly indirect - just show me files and let me add text for a dependency like in Maven or Gradle. To be fair, I think this one is partially just me being new to the .NET world.
It is pretty much what Red Hat Linux does to linux.
Here I expanded a bit more on the topic: https://news.ycombinator.com/item?id=35249701
That was always halfway between oxymoron and forbidden magic.
Obviously you’ll be able to run older versions of the framework so long as the underlying OS supports it but depending what type of environment you’re in this means you’re likely going to have to update perfectly working applications every two years.
Do I think that it will take a massive amount of work to update from dotnet 6 to 8, or 8 to 10 in a few years? Probably not, but that could change at any time depending on the whims of Microsoft on that particular day.
I get that the current technical zeitgeist is that if you’re not releasing a new version every other day you’re falling behind but five years seems a bit more reasonable to me.
No, I feel the same. It really is a completely different mindset compared to .net framework. And I believe it is recommended to keep upgrading, even in maintenance mode. I noticed that Microsoft removes target frameworks from their nuget packages as soon as they are no longer supported. That could be problematic if you need a security update and are on a no longer supported target framework.
An “enterprise” environment that is used to Microsoft products is not used to that.
There was a massive amount of churn between the introduction of .NET Core and Core 3, then 3 to 5 was quite a bit less so, and since then it’s been a non-issue. Pretty much just flip a version flag and get new features.
Sure, but in many cases for an application that is "done" we're not looking for new features, per se - we're looking for security patches. Moving from one major version to the next incurs a whole bunch of testing and validation that otherwise might not be needed (depending on your environment and industry).
Frankly, places that put off updates have always been a bit of a mess from personal experience and I'd rather have smaller more frequent updates that take a day or two every few years than massive irregular ones that aren't feasible to apply.
If you are using BinaryFormatter you certainly will have some work ahead of you.
Is anyone here having success with Linux and Neovim for C#? Even trying to learn more about the language is awkward as so many resources go straight into VS.
I find Rider a much more smooth experience than VS. It's faster in almost every way (loading solutions, searching, etc).
As far as I can tell GPU debugging is C++ only, where I thought we were discussing C#, same with the C++ hot reload. However CLion does both.
We where discussing what a Visual Studio full install offers, with a single license.
Also, again why pay twice.
Maybe when I start seeing Rider demos done at BUILD.
You can always use vs code however.
There is this alternative LSP, which I plan to try out still: https://github.com/razzmatazz/csharp-language-server
At the moment I'm stuck with Rider, which is terrible (there is an official vim mode plugin), but I still have to port my bindings over to their vim config. Telescope etc. isn't available, which is a sad day.
https://github.com/dotnet/runtime/issues/12409
So the most efficient C# development solution for me is going back to Windows and Visual Studio. As they want it to be.
Hot reload works mostly fine in Rider on Windows.
I found that the upgrade process is not seamless. Asp.net core has little to do with asp.net MVC. Winform introduced all sorts of contraints. The BCL is full of small changes or features missing.
People are less vocal than for the python schism, but I'd expect a lot of projects to be stuck on .net framework, not the least because of incompatible dependencies.
Nullable reference type is another massive breaking change coming if they make it more than a compiler warning, which I suspect is the long term intention.
Not breaking your users used to be a laudable goal of the .net team. I miss it.
Is it that bad once you are on .NET Core? Asking as someone still on Framework.
Framework -> Core ofcourse is breaking, what did you expect when going cross platform and totally rearchitected?
I am not saying .net core is bad, just that the migration is a lot of work. And you need to disable all sorts of compiler warnings unless you are ready to rewrite pretty much all your code to make it nullable ref type friendly. And some day those warnings will be errors.
Not really. Just disable nullability checking — you can do it project-wide by removing the Nullable tag from your .csproj, and you can continue ignoring nullability.
> And some day those warnings will be errors.
[citation needed]. A lot of code is written without nullability in mind. That includes the .NET Core BCL, AFAIK.
The BCL was completely annotated with NRTs for the .NET 7 release, minus some #nullable disable spots.
There are lots of gotchas here and there and it does require a pretty reasonable understanding of both legacy and new frameworks.
I have a bit app: Razor, MVC, EF, custom nuget packages, reflection, very advanced expressions ( linq) and my IoC is Autofac ( .net 4.7.2).
101 projects in one solution.
Have you got any pointers on gotchas? Did a quick attempt ( 1 evening) and got blocked on my nugets+ Autofac.
Also have a look at the MS upgrade/migration guides for .NET. They’re exhaustive and extremely helpful.
I'll have another try later this year probably :p
Yikes. This is in C# I assume? If in VB, I'd run one project through Instant C# and see how many problems you encounter. There are lots of VBisms that are simply not in C# (like statement for one, xml literals, etc...).
Run the simplest project through the `upgrade-assistant` tool. It'll convert it to .NET Core. I saw the sibling post recommend .NET Standard, but if you don't plan on using anything legacy this is more headache than its worth.
But yeah, I hear you on the nuget dependencies. Some of them were never ported to .NET Standard or Core so you are left trying to recompile them manually yourself.
It's c# yeah. But I believe the project is much cleaner and frankly better to understand than all other projects i've encountered for this size. I'm using DDD, so DDD knowledge is a requirement to navigate this in a breeze :) :
- https://snipboard.io/D03VWg.jpg - General overview of the architecture. Small fyi: Connectors => Autogenerated nugets to call the api's
- https://snipboard.io/9M24hB.jpg - Sample of Modules + Presentation layer
- https://snipboard.io/ybp6EH.jpg - Example of Specifications related to catalog ( = products )
- https://snipboard.io/lE9vcK.jpg - How specifications are translated to the infrastructure ( here I'm using EF, so I'm using Expressions a lot), but plain old SQL is also supported. A query is basically a list of AND/OR Specifications where a hierarchy is possible. This would translate to "(QUERY 1 ) AND ((QUERY 2) AND (QUERY 3))" in the Infrastructure layer.
- https://snipboard.io/7rVBpk.jpg - . In general, i have 2 List methods ( one for Paged queries and one not for Paged queries)
Additional fyi: Is V2, so has some legacy code. Uses my own STS. Has 2 gateways ( the ShopGateway that is used to develop new sites and the BackendGateway for the Backend). Enduser frontend is in MVC for SEO purpose, Customer backend is in Angular ( SPA). The basket is a NoSql implementation on top of SQL server.
The enduser frontend supports a hierarchy of themes ( so it's insanely flexible to create variations of pages for other clients).
There are more projects involved outside of this solution, eg. nuget repo's usable accross solutions (JWT, Specifications, ...) and "plugins" for a standalone project that is installed for end-users for syncing local data. So it's +101 projects :)
It's used for eg. https://belgianbrewed.com/
I even hate to suggest it because it's double the work, but I'd convert all your libraries first to .NET Standard 2. And then make sure they will work fine with your .NET Framework 4.x UI projects.
The reason this sucks (I didn't realize it at the time) is that .NET Standard 2 is stuck with C# 7.3. Which means you are missing out on all the C# cool toys. You can't even use .NET Standard 2.1 (and C# 8) because it's not supported by the .NET Framework 4.x.
Then go on to the UI projects. Automated conversion may or may not work. Probably not if it's complex. The middleware won't simply convert, particularly if you had routing, filtering, etc... You may have to create new projects. I was able to copy/paste lots of code (routing for instance) but how it was wired was different.
If you are successful, you can go back and upgrade the library projects from .NET Standard to .NET Core.
Good luck.
Good news is that the nugets are already on it!
Automated conversion didn't go okay enough in a short timeframe ( even with the Microsoft docs).
Nullable reference types are completely optional. You can turn them off at the project level. I find they make more sense for some projects (domain logic, etc.) than others (EF and API projects for example).
There's still a lot, but it's now more or less comparable to what you'd expect you'd end up with on a similar app built on, say, FastAPI.
You used to have a class, and there would typically be a separate Startup.cs file. Today, this is a complete .cs file for creating an API:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();I personally hated working with Visual Studio 2017. 2019 was an improvement, but one so small it was still awful. I recently used it again (2021?) and was cynically surprised that they FINALLY got scrolling that isn't forcibly line-based. Why did they even bother now?
This and some other small things that used to make the experience painful are now solved, but they still haven't really gotten on top of the freezes and crashes.
Visual Studio being bad is one thing, it being the only officially supported IDE is the other. They should not only officially support third party efforts like Jetbrains Rider, in the past I've gone as far as saying they should discontinue Visual Studio in favor of it.
Now for the nice part: Almost all "new .NET" announcements/tutorials focus on VS Code, an IDE that sucks a whole lot less.
On another note: Transforming a 20 line T4 template took 4-5x as long as the actual compilation on the last .Net project I've seen.
Yeah it's big and takes some big chunk of resources, but that has never been a problem in my daily work.
What I mean is that I found it very hard to find "interesting" stuff built with it. You get the official documentation and that's it - for .Net Framework, anyway. Few blog posts about issues and their solutions, cool demonstrations or just general musings. It's like people don't care. And maybe they don't - it's just their job, after all, and they can't wait to go home to their wives and kids rather than think about some technology more than they have to.
As a newcomer (forced through an apprenticeship) who does think about technology more than he has to I found this bewildering and demotivating.
Microsoft seems to have realized this and seems to have put a reasonable amount of effort into building a community around the new .Net and making it seem exciting/interesting rather than something that's just in a really slow death spiral. Oh, and building things that one can easily get excited about. Non-nullable types come to mind.
Not sure about Xaml based Maui...that may well go the way of Silverlight.
"In a Blazor Hybrid app, Razor components run natively on the device. Components render to an embedded Web View control through a local interop channel. Components don't run in the browser, and WebAssembly isn't involved. Razor components load and execute code quickly, and components have full access to the native capabilities of the device through the .NET platform."
I'm not familiar with Tauri but skimming it seems similar. Except that Razor syntax allows C# inline with HTML so you can avoid JavaScript altogether. Maui Blazor also uses Xamarin to support mobile, and can compile to WASM to run in a browser.
(What i skimmed) https://tauri.app/v1/references/architecture/
mkdir MyApp && cd MyApp
dotnet new sln -n MyApp && dotnet new web -n Api
dotnet sln add ./Api/Api.csproj && dotnet run --project Api
Many devs would want more features out of the box so they'd replace "web" in the "dotnet new" command accordingly (use "dotnet new list" to see the options, eg mvc, grpc, or react). But this is the minimalist option just to 'get something running'.
Edit: For background, new sln creates a new DotNet Solution and new web creates a new (minimal) C# web Project. Then sln add tells the new solution that the new project is part of it.
C# is still one of my favorite programming languages (including syntax, semantics, and standard libraries). Although it is a bit complex now, it has also created many excellent designs such as async, await, and so on.
VS and VS Code are also IDEs that I still use today, although I rarely write C# code.
...and is it used much compared to C#, or is its use at least growing, or is it stagnating/dying?
It is, hands down, my best experience working in a functional language. I do hobby work in it, and it seems super nice, but I'm not sure where everyone is.
> still very few people use it.
Looking at a chart of GitHub and StackOverflow usage[1], OCaml/F# seem almost steady compared to the other functional languages, my suspicion is that Rust absorbed a lot of programmers looking for functional concepts in programming languages.
[1]: https://tjpalmer.github.io/languish/#y=mean&weights=issues%3...
It’s a great platform, in some ways it’s like a secret weapon…
I joke that F# is kinda “easy-button Rust” lol
The productivity benefits are small and sprinkled - I don't personally think there isn't one "killer feature/app". It isn't just one thing, its little things that add up. Given the team jumped from other ecosystems (e.g. Go/Node/etc) they found F# easier to approach than C#. This is the perspective of the team I run and it comes up in PR comments (I do less code writing these days). Comments like "don't have to do this in F#", or "we need a framework for this because C#" are known to occur. Easier unit test writing, less dep injection headaches, concise function passing, easier inlining of math for perf, easy mocking/stubbing, unions, etc etc.
The big weakness to me is that the people that use it typically don't want to flaunt it, and that means good mentoring, the best/simple patterns to use, etc and management buy-in are not really public. Communities that you can join are not into large scale apps, meaning good scalable patterns and lessons learnt are hard to find.
As .NET 6 and MAUI started to come on the scene, stuff went haywire pretty badly, tooling issues like breakpoints in Visual Studio no longer working in my projects, obscure build errors, confusing build warnings, dependency hell particularly with Xamarin.Android NuGet packages and Xamarin.Essentials. I'm still not up to date with all NuGet packages because doing so breaks my app at runtime. I'm in this halfway point in regards to use of the PackageReference project type. Things haven't been smooth lately for F# and Fabulous projects.
Things are slowly getting better though, and I would say that my experience is probably not entirely typical due to the inclusion of Xamarin, which introduces a whole additional layer of crazy. I think if you were to use F# for backend web services for example, then your experience would probably be a great deal more palatable than mine. I don't think F# is stagnating or dying by any means, but I do feel that it is still a second class citizen to C#. I hope MS continues to work towards this not being the case, because with all the "batteries included" of .NET behind it, I think F# is a great functional-first language.
It wasn't too bad, really. Mostly it was updating any new controller methods to use the new attributes.
We originally planned to upgrade incrementally (one project at a time) but to be honest, it looked that was going to be far more fiddly and annoying that just doing the full thing.
Were running .NET 6 + F# in production with ~4 developers and its been a breeze.
Which is sad because there’s so much to like about .NET
And yet, here you are, right on the dot. First comment on any .NET thread.
If you're before that, well, good luck.
Except the docs, which still say core all over the place, including the url's. ;-)
The Java stuff is easy. From the perspective of a Java developer, most of the things you listed (except for ME, EE and JavaBeans) are just “Java”. And they always have been.
[0] https://michaelscodingspot.com/assemblies-load-in-dotnet/
2. .NET "Framework" ... I tell myself "F is for Former." This is the older, Windows specific version.
3. .NET "Standard" ... I tell myself "S is for Specification." This is just the spec which defines what Core and Framework must implement.
Where it still gets a bit murky is:
> “ .NET has many different implementations, including the .NET Framework, Mono, and Unity. Each of these is a separate platform with separate Base Class Libraries (BCLs) and app models. .NET Core is another separate platform.”
Unity looks like it uses its own fork of Mono, or IL2CPP, which is a fancy thing that converts the intermediate language produced by C# into C++ code, which it compiles. [2][3][4]
[1]: https://www.mono-project.com
[2]: https://docs.unity3d.com/2023.2/Documentation/Manual/overvie...
Though reading through it, I am still confused.
.NET Standard solved a problem at the time but as elucidated in the article, it turned out not to be the right solution in the long term. .NET Framework is essentially in maintenance mode and won't be receiving new language features, so new .NET Standard versions don't make much sense given the overhead they introduce. Newer versions of .NET will still be able to build and run libraries targeting .NET Standard but no new .NET Standard versions will be released.
Greenfield projects now typically target what was previously called .NET Core. With the removal of .NET Standard, .NET Core was renamed to .NET to reflect the fact that it's the whole ecosystem as far as any future development is concerned.
I’m skeptical, ChatGPT is not magic, and it was trained on ~15 years’ worth of “there is only one .NET Framework, so we might as well call it .NET” content, ~5 years of “.NET Framework is for serious business, and there’s the cross-platform (mostly-)web .NET Core” content, and 1 year of “.NET 5 is the future of .NET”.
Because I imagine you end up with what MS did - define a standard that can be used for building common code between new/old, continue support for both, quickly iterate the new while it's still new, be very open about support timelines and document everything pretty thoroughly. You'd just end up with a different set of names for that standard (.NET Standard), and new implementation (.NET Core) that make sense to you, but probably still confuse some people.
I don't think it's perfect, but .NET developers will (or should) all grasp the relationship between .NET Standard/Core/Framework (and now plain ".NET") pretty easily.
Trying to find solutions to ASP.NET Framework problems usually requires discarding a whole bunch of irrelevant ASP.NET Core/.NET ones.
It's the same with Visual Studio/Visual Studio Code.
Whether it spits out the correct answer is still up to chance but the narrowing does seem to work quite well.
I’m not sure what would help in that case other than using something else than “.NET” to identify it.
Griping about a minor inconvenience isn't to say that I don't think it was the correct decision. The new .NET is already far better than Framework, in how the platform is progressing and the development experience.
My experience was this was always a problem even before later versions of ASP.NET. There was so much backwards incompatible changes between ASP.NET 3/4/5 and ASP.NET MVC 3/4/5 and even things that searching for ASP.NET MVC 3/4/5/6 would disagree with ASP.NET 3/4/5 non-MVC recommendations.
If there was a point where googling ASP.NET problems was clean and unpolluted it probably only existed briefly in 1.0.
I'm young enough to have only caught the tail end of Framework in my professional career but I do recall a lot of Razor Pages content showing up when looking for tutorials on MVC.
It's certainly not unique to .NET. Being stuck on an old version of Elasticsearch can turn into a nightmare when trying to find a quick a solution to a query problem.
No thanks...
.NET 6 LTS
.NET 7
.NET 8 LTS
FreeBSD support has been in the works for a while:
From my perspective, this is not "long term" ; but maybe others view it differently.
If it already runs on two Unix-likes I don't see the absence of Solaris support showing lack of portability. I actually think Solaris usage is the _more_ important piece: software is made for users and if those users don't exist (or there's such a small number as to make the opportunity cost of development not worth it) then what is the point?