I'm not sure if maybe you're referring to the Options<T> stuff?
The way I do it is pretty simple.
1. Create a POCO that represents your config. You can have properties for bested configs if you want, e.g. SecurityConfig, MessageBusConfig 2. When the app starts, use a ConfigurationBuilder to build an IConfiguration from whatever Co fig sources you want - typically JSON files and env vars 3. Bind the IConfiguration to your POCO 4. Configure both IConfiguration and the POCO as singletons
Now you can inject the POCO config object wherever you want it, or the IConfiguration should you need it. I feel like this is simple, and works well for dev, test and prod.
The options stuff is a car crash.
On my last project, I just assigned the configs to a static class that's available everywhere and the code was just simply much cleaner.
You really don't - just inject your populated config object where you need it.
So it sounds like basically he's not using DI (is just using it to pass stuff to controllers) and is surprised that using a library designed around DI is awkward. The correct solution is use DI to construct that hierarchy and then you can just request IOptions at the bottom/where you need it.
This is precisely why I jumped ship 5 years ago to Go.
Plenty of other people piping up saying they also find this bad abstraction obnoxious, sorry you can't empathise and understand we all have difference preferences.
Bonus Points, by loading all settings at once into strongly typed properties at startup, it will fail fast if a setting is missing, which to me is a benefit.
But I still pass this class via DI to the controllers for testability reasons. If you are using ASP Net, just do things the asp-net way for the sake of other people who have to inherit the code.
Outside of AspNet I never use any automatic DI.
This feature is added in the `config.AddEnvironmentVariables()` line, which is already included in the default host builder. The latest releases use very good conventions and include a lot of the typical functionality. I recommend reading the docs to avoid redoing what's already there: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/co...
The .net core configuration mechanism has a hierarchy of providers it reads from, one of which is environment variables, and strongly typed configuration is supported out of the box, so you're exact use case is supported natively by it.
This blog has similar thoughts: https://rimdev.io/strongly-typed-configuration-settings-in-a...
or this blog: https://adamstorr.azurewebsites.net/blog/beyond-basics-aspne...
Or simply google 'asp net strongly typed configuration' and notice everyone seems to be reaching similar conclusions and building their own things.
Just load the setting yourself and register it as a singleton.
Yes, indeed you have. I've tried to both skim and read the "introductory" documentation for `Options<T>` and the only gain I could figure out is monitoring + refresh. I still have no idea how to use it though, so I don't.
Meh, seems like just an interface to me.
Why would you prefer that, to the the built-in classes to load settings via good old environment variables, among other sources? Which do you think new people coming to your team would prefer to see?
I seriously don't understand how MS managed to make loading some configs so outrageously confusing.
Every time I have to write in .net and I'm forced to go down this path, I've got to go back to the one project where it worked, and copy code across and install dependencies until the whole thing decides it's finally happy.
appsettings.Test.json
Voila, different settings!
I've said elsewhere, the DEFAULT should be super simple, really, really, really easy to use. No thought, no effort, just use it.
If you want to go all crazy and start injecting values into your config in your unit tests, great to have that option, you should have that option. But you're the one who should be scrabbling around writing tons of extra boilerplate code, not me.
But I just want to set a filepath, that's probably never going to change, but might one day. Or an email address to send a weekly summary email to. Or some settings on paging that the client might change their mind about once and I don't want to have to rebuild the project.
The vast majority of config settings are just cover your ass in case you need to one day change this value. They don't need to be tested.
So it not the "normal" path to need to test config values. It's not the path the vast majority of programmers need.
The .json is for running the application locally. A unit test should be able to cover if a setting has value 'a, b or c', which is much easier with a regular object.
Yes, one solution is to make the software more complicated in order to make the tests less complicated, but the other solution is to just use fewer unit tests. Note that I'm not advocating not testing (my motto with software is "If nobody tested it, then you can be certain it doesn't work."), but One good e2e test can often replace dozens of unit tests.
I find that E2E -> Integration -> Unit form a scale from "easy to write, hard to run" on the e2e side and "easy to run, hard to write" on the unit side.
There are exceptions; testing that you can sort N objects in M microseconds is a trivial test to write, and potentially a challenging requirement to implement, and a unit test might be completely appropriate there. However, for the general case, I find unit tests to be too often trying to make a square-peg fit a round-hole, and then saying "okay we'll shave the corners off a bit so that it's easier to fit in this hole" rather than saying "maybe we should use a square hole"
I personally believe that tests are how you prove you understand the code you’re writing, and the changes you’re making. If they’re harder to write than the software, you probably need to further clarify the behavior being tested, but they’re going to be hard sometimes, just as some software is hard.
> if I can only write software for which I can also write quality unit tests, I am greatly reducing the space of quality software I can write.
I follow TDD, so indeed I only ever write code that I know how to write unit tests for. I haven’t found that TDD restricts the kind of software that I can write, but sometimes it does mean I’m slower to get started in a particular domain. I do find that once over the initial hump my unit tests are a constant companion that make me more comfortable and productive in developing new software.
Realistically we both agree that testing software is important, we just disagree in the details. The problems I see with end to end testing are that good patterns for building extensible and loosely coupled end to end testing suites are not as widespread in the industry as they are for unit and even integration tests.
My theory as to why is that we assume that end to end tests are going to be slow to run and “heavy” in some sense, and that as such we aren’t running them often enough to force the improvements we need in those suites.
I always find it weird when discussions about unit tests become discussions about other kinds of testing, though. I think high (though not necessarily 100%, as that’s arbitrary) and increasing unit test coverage are an empirical sign of code that is being properly maintained. They are not the last word in testing and software QA, they’re closer to being an indicator like using CI and a proper build system instead of a thousand line shell script, and having quality well-maintained documentation. As I said in another comment, I am not going to say that unit tests are the only way to write good software. They’re a powerful tool that I have found helps me and my teams a lot. That’s as far as I will go.
I find it humorous that so many programmers still can't see past the end of their nose when it comes to designing code for application testing.
An application is deliverable. That means testable and deployable with a consistent, repeatable process. Anything short of that is not an application, it's a prototype.
Yes, it takes longer to deliver up front, but it removes the long tail of maintenance. Getting a low quality product out of the door makes for a bad user experience and garners you a bad reputation.
The test suite _should_ be a first class citizen.
And of course you can always just access environment variables directly if you want to.
Env.Config("Settings:MyEasyValue");
Or: Env.Config<Settings>().MySuperEasyTypedValue;
And the Dependency Injection? Sure, add some version you can DI with. But not the default.I now know how to navigate the mess they've made, but it's not time that I feel where I gained anything in my life, it was just frustrating.
Here's one (of many) questions asking simple questions on how to access config values in .Net core:
https://stackoverflow.com/questions/46940710/getting-value-f...
280k views!
And look at the sheer length of that answer.
If you think they succeeded in making a good configuration library, we have very different definitions of success.
Is this so hard?
public class AccountController : Controller
{
private readonly IConfiguration _config;
public AccountController(IConfiguration config)
{
_config = config;
}
public IActionResult ResetPassword(int userId, string code)
{
var vm = new ResetPasswordViewModel
{
PasswordRequiredLength = _config.GetValue<int>(
"AppIdentitySettings:Password:RequiredLength"),
RequireUppercase = _config.GetValue<bool>(
"AppIdentitySettings:Password:RequireUppercase")
};
return View(vm);
}
}"What's hard about [mass of code] compared to [one liner]?"
Sure, it could be done in one line. My point wasn't to say that this is the most terse environmental variable code possible. My point was to say that it's disingenuous to say "look at the sheer length of that answer" as a way to state that the way to get env variables is hugely bloated. It's literally one ctor param and one private variable. If you don't like that for stylistic reasons, that's fine. I understand the argument saying "there should just be a static class with a readonly prop per variable", I just don't think that this particular code is in real terms actually any worse.
A 'new' C# programmer doesn't. You have to be advanced enough at C# to know you can do that (i.e. not a junior or outsider)
It's also not clear how the config works at first glance or the implications of using a POCO on the app lifecycle.
An experienced coder will have to ask themselves questions like, if I store the IOptions object, will it automatically update? If I assign the POCO in startup, will it be a single object, or does startup get called for every new request? Or maybe it will get called for every thread? Or maybe if there are no requests for 10 minutes it automatically shuts down?
"But wait," you say. "I'll just pass in the thing that provides that data"--and you just reinvented DI, albeit likely poorly.
DI is the removal of complication when it is done correctly. (I have no opinion on whether ASP.NET Core does it correctly.)
I do, it was done in a really weird way and I don't care for the provided DI Abstractions nor the 'Microsoft.Extensions.Configuration' namespace.
To take the 'common' object used for configuration, the nuget package for IOptions<T> requires pulling in Microsoft's DI Abstraction..
That's the first sign of a smell. Config and DI can go hand in hand, but they should still be orthogonal.
The further you go down the DI stack, the more you can see that it's an abstraction has a lot of tradeoffs for front-line devs in the name of using the same abstraction for the underlying framework.
The gist:
Relationship Adapter Type Meaning
A needs a B None Dependency
A needs a B at some point in the future Lazy<B> Delayed instantiation
A needs to create instances of B Func<B> Dynamic instantiation
A provides parameters of types X and Y to B Func<X,Y,B> Parameterisation
A needs all the kinds of B IEnumerable<B> EnumerationRight. It's primarily interfaces, but they are tangled.
This was especially painful between Net Core 2.0 and 3.1, because moving fast and breaking things is ugly when you have tangled dependencies and everyone is trying to catch up to the breaking API changes and related nuget versioning dance.
Personally, I would rephrase that as, "DI is a pattern that is designed to mitigate certain kinds of complexity when done correctly."
That leaves room for two ways in which it can backfire. Doing it wrong, like you say, but also doing it in situations where you don't actually have one of the problems it's trying to solve. Cost/benefit ratios always get out of whack when there's no benefit to offset the cost.
There are certain benefits to DI, but this one seems more like a lack of tooling.
I’ve been experiencing the same issue with some Azure SDKs that only expose async methods rather than both async and sync. Frustrating to have to redo 8 caller functions to make a single sdk call.
Though you'll probably hit the `async void return` runtime bug at some point where you accidentally return void from a method you tried to make async but then got bored of rewriting everything, so just whack in a .Wait or .Result() but haven't returned Task and the damn thing fails at runtime with a mysterious error making you curse async even more.
You call .GetAwaiter().GetResult();
and avoid mixing sync and async where at all possible.
This pattern, 'sync-over-async', is to be avoided at all costs, at all times.
You're not going to like it, but the MS recommended way of dealing with this is to make your callstack async.
In all honest - if you're making an ASP net core web app, go all in on async from the start. If you have to mix and match, never go sync-over-async inside an action.
Authentication is blocked by accessing storage. You cannot proceed until the storage read operation is completed. There is no parallel work to be accomplished here. This isn't UI code. There's no event loop. Why would you want this to be asynchronous? This seems like async as a dogma, rather than as a tool to accomplish something specific.
.NET is used by millions of developers. Strong and scalable foundations matter, and performance is an explicit goal with asp.net being one of the fastest web frameworks. Async is a core part of this.
There's very little - if any - overhead that you need to worry about with async code from the language to Visual Studio.
it actually uses more resources. async has a cost, but it is really low.
Threads have their own resource cost.
Exactly. So when you await it, that thread that would have but blocked waiting for the I/O instead can be used to service other requests until the I/O has finished.
Concurrency is not parallelism [1].
Both solve the same problem with the same technique (under the hood it is all the same). One language had the benefit of late birth (go), the other suffers with its friends through a library/language migration (C#, C++, Java, JavaScript, Python, ...). async/await is effectively the modernization of structural programming to benefit from modern concurrency.
While async/await is nowhere structurally as capable/composable as for instance Haskell's do notation, it's still more composable than a lot of alternatives and a lot of manual thread management techniques.
The biggest thing though is that these concerns are orthogonal. You can have green threads backing an async/await monad (with some caveats), but you can't as easily swap in anything that follows monad rules into code written specifically just for green threads. (Python makes you explicitly define your threading model before using async/await; the others provide methods to configure it.)
Which is to say "late birth" isn't really a consideration in async/await, it's as much a consideration of flexibility/composition of abstractions. JS, for example, needed that flexibility/composition in the wild west of multiple disparate Promise implementations early on, and may need it again if/when Browsers ever decide to support proper multi-threading whether it is green threads or something else. In such a future you should still be able to compose existing async/await code without modifying it, even as you take advantage of newer threading options.
I'm a huge fan of Go, but pretending the two systems are equivalent either is either disingenuous or belies misunderstanding.
Some articles:
https://docs.microsoft.com/en-us/archive/blogs/vancem/diagno...
https://github.com/Microsoft/vs-threading/blob/master/doc/th...
Synchronous code locks you into the model of handling one request per thread.
Given the minor overhead (both cognitive and runtime) of a sync/await, I can’t see why you wouldn’t want to do it here.
Whats a way to get the utility of normal debugging while using async?
Using a good IDE (Rider or Visual Studio), debugging async/await code works as expected.
Are you doing interop or calling unmanaged code?
https://www.ageofascent.com/2018/01/26/stack-trace-for-excep...
Async makes your code do significantly more complex thnigs, and while the compiler makes the easy stuff easy, the underlaying complexity hasn't gone away it's just been abstracted, and somewhat leakily.
You can't do 'just' what you said, you also have to deal with of boundary conditions that don't exist in non-async code, plus if you want to understand what your code is doing you now need to keep two mental models of program execution in your mind.
For an introduction to the complexity of async, and practical considerations you need to manage to use it well this official documentation is a place to start - and note the article is pretty long, async does have a lot of additional complexity.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2011/...
Btw I'm not saying don't use Async. Async is an incredibly powerful and valuable tool. But it has costs and those are weighted toward downstream in the software lifecycle and you should be aware of them when deciding which situations that tool is appropriate in.
Yes async is a first class citizen in .NET and thats emphasised by the framework libraries using it everywhere. But they are writing a framework to be consumed widely by users of differing requirements so they have real reasons to support the widest possible use with highest possible performance. Most application code doesn't really have those needs.
Using async should be considered as a tool to reach for (when appropriate) not a default for every situation because it has significant costs through the lifecycle of the system.
Side note: this gave me a good laugh. I just pictured it being used as a sign off on PR reviews.
Is it a technical term? If it's not I think it should be, and if it is I want to know what the exact nature of potato code is, so I can call it out when I see it in the wild.
Scaling machines (VMs, containers) is a solved problem and very easy to do today.
Even if you don't need the performance, async code helps keep your app responsive and use less resources on your server. It's far better to have async and not need it then try to refactor your entire ap when it's necessary.
Also, for high-traffic websites, that's really not a great idea. You might say "but Facebook, PHP" - I'm really certain that their current codebase doesn't have a thread that blocks on DB for each user request.
What does this mean? .NET has been around before SOA was a term and microservices is an evolution of that. The concepts are orthogonal and you could write microservices in PHP, if you really wanted. Like Java, the .NET runtime and platform is highly efficient. That's what .NET has historically had over PHP/Python/Ruby, but I don't keep up with the PHP and Ruby platforms these days.
I feel like this is another one of those discussions like the "Python GIL problem." Whether it's a really a problem depends on the circumstances, and the circumstances were it is a problem affects fewer people than the HN threads suggest.
I'd be willing to bet more projects have been killed by trying to architecture for performance up front than have been killed by being too successful for their own good and keeling over.
If Twitter could make it past the Fail Whale days, I think most of us can too.
As regards configuration, all the means you could want for configuration are there. You can access items directly [1], or via strongly typed objects. These all support multiple sources of configuration data, and many means of overriding them.
I generally really don't like environment variables as they expose configured secrets to everything in the same machine / container, but we make use of the standard configuration system's environment variable overrides, but we use akv2k8s.io and thus only expose secrets to the container's entry point application.
[1] public Startup(IConfiguration config) { // injects config store var setting = config["yourConfigKey"] // ... but you now have to convert this from a string for any other datatype }
One curious thing is that Java has elected to use lightweight threads instead of async methods: https://www.javacodegeeks.com/2019/12/project-loom.html
Their arguments are compelling:
- Works with existing debuggers and debugging techniques.
- Stack traces aren't polluted with irrelevant garbage.
- Doesn't create method apartheid, where async and non-async can't "mix".
- Doesn't force library authors to "buy into" the async model.
The nice thing about promise-based async is that it's very easy to map to straight C ABI callbacks, which means that it can be fully cross-language. Green threads are runtime-specific.
With green threads being "free" (you can have millions), it might be reasonable to just block your green thread until the callback arrives.
Should be able to likewise give you a much simpler and understandable config model.
I do this with a lot (most?) web apps, and don't have any issues with it at all - what kind of problems are you seeing?
> I've given up with the config DI and just assign them all to static variables.
Not to be a fanboy, but it's designed in a way where you CAN use all the fancy features. If you want something simpler, you can just do exactly what you did.
This syntax is probably wrong because I'm doing this from memory, you should probably also use the Lazy class or something, and if I were to actually write this code I'd think twice about thread safety -- but if you want simple static access to config values, you can just do this:
public static MyConfigPoco MyConfig {get;set;} = JsonConvert.DeserializeObject<MyConfigPoco>(File.ReadAllLines("appsettings.json")) ;
I'd recommend both node with TypeScript and Rust for backend web development in a statically typed langauge.
But C# lacks an ecosystem compared to Rust? I don't understand that.
I'm not sure whether we're talking about the same thing, but you can publish Self-Contained App (basically your app and framework together) and you don't have to install anything.
Don't get me wrong, I totally love 4k and 64k demos and am always fascinated what could be packed into such small binaries, but in my professional life I think developer productivity, code quality and tooling is way more important than filesize. This is of course different if you are a webdev that ships .js, or an App developer publishing to Appstores.
Running this command:
dotnet publish `
-p:Configuration=Release `
-p:PublishSingleFile=true `
-p:PublishTrimmed=true `
-p:RuntimeIdentifier=win-x64
gives an 11 MB file, that still requires 4 DLL as well.Things like
var filtered = purchases.Where(x => x.Name.Contains("Sean"));
and var grouped = purchases.GroupBy(x => x.Buyer).Where(x => x.ToList().Count > 10).ToDictionary(x => x.Buyer, x => x.ToList());
etc. come up a lot in day to day programming and always feel tedious to me in go. The first example in go looks something like filtered := []purchase{}
for i := range purchases {
if purchases[i].buyer == "Sean" {
filtered = append(filtered, purchases[i])
}
}
and it only gets worse as the domain gets more complicated. I don't really know what your background is where you feel that static typing and generics are complicated. I write C# every day and spend 0% of my time fighting the language.I like Go for services that just need to push bytes around to various places but otherwise it does get quite tedious for complex logic.
It's not that those aren't also complex, but there's less "business" in the logic, if that makes sense. Something that doesn't handle arbitrary user input or deal with the messiness of humans, like first/last names, dates and timezones, etc.
As an example, I had once written a WebSocket server in go which took a program as input, ran it in a heavily stripped down docker container, and streamed the stdout back to the client. Go was perfect for that use case. (though to be honest I'd pick Rust now because I'm past the learning curve on it)
In what capacity are you using Go that made it a good replacement for C#? Microservices? If so, which framework are you using?
And also I don't have to fight against the language to do what I want to do ... No classes, no types, no constraints.
Well, do you have all of the bells an whistles that you had with .NET? I mean, you can shrink a .NET API down to pretty much just 1 file if you really want to. The question is if you should.
WebHost.CreateDefaultBuilder().Configure(app => app.Run(c => c.Response.WriteAsync("Hello world!"))).Build().Run();
that's giant, but I don't think that "just" language / environment difference makes this huge difference
The one issue I can see with DI in ASP.NET Core is that it doesn't really do DI correctly. The point of DI is that ultimately, you have complete control over how you build your dependency graph and you shouldn't be forced to use a container at all if you don't want to. With a DI abstraction the developers are basically saying "you can do what you want, as long as it's pretty much exactly what we expect you to do".
This explains this pretty well: https://blog.simpleinjector.org/2016/06/whats-wrong-with-the...
So you have static variable in web project and then add reference to web project to obtain that config? Do I get that right?
I never felt like config DI was a problem once you did all the stuff that you do at the beginning of the project
using Microsoft.Extensions.Configuration; //extra line
using Microsoft.Extensions.Options; //extra line
public class AdminController : Controller
{
IOptions<Settings> settings; //extra line
public AdminController(IOptions<Settings> settings) //extra code
{
this.settings = settings; //extra line
}
public void RandomMethod()
{
var a = settings.Value.FinallyMySetting; // 5 extra lines to access your config, genius...
}
}
A complete and utter waste of my time to put all those lines in, it's just pointless boilerplate trash, which C# has been getting rid of excellently, all to be undone by whoever wrote Microsoft.Extensions.Configuration. And you have to do it any where you want to get a config value.On top of that, if you want to use it in a console app, you have to add like 5 packages. Yes, FIVE.
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
Then add this delightful mess just to initialize it: var environmentName = Environment.GetEnvironmentVariable("ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{environmentName}.json", true, true)
.AddEnvironmentVariables();
var configuration = builder.Build();
When you compare that to what any other framework does, it just beggars belief that they though this was a good way of handling config.When I was setting this all up, I obviously hit up SO, and it's clear from many of the (incorrect) answers on SO that people just don't get it. It's too complicated for something that should be super simple.
I get that it's handy to be able to configure all that yourself, but all of that should be default and a single line/package.
ASP.NET Core does push you strongly towards dependency injection, but it does not force you at all to use it.
Their own documentation for making queries against SQL Server doesn't provide examples of how to do it that aren't using DI. "Just inject it" it just says. Thanks .net, super helpful. Maybe the docs are there, but I couldn't find them at all when I went looking.
using(var conn = new SqlConnection("<conn-string>"))
using(var cmd = new SqlCommand(@"Query here", conn))
{
conn.Open();
cmd.executeNonQuery();
}It is something that annoys me about .NET in general there is just decades of over-engineering cruft and once these practices emerge its difficult to get people to stop doing them. The problem only gets worse as time passes.
It's such convoluted nonsense.
I like C# but sometimes it feels like MS main aim is to make developers miserable.
I don't regard the "ConfigurationBuilder" and following lines as "boilerplate" exactly, since by not using the default HostBuilder and instead including that code, you are making choices.
Specifically, you are choosing to pull in settings from "appsettings.json", "appsettings.{env}.json" and then environment vars, in that order, and specifying that both files are optional and should reload on change.
You can make other choices, with other code.The code is there to "configure the config", i.e. to make those choices.
> it's just pointless boilerplate trash, which C# has been getting rid of excellently
Could you elaborate on how you feel C# avoided this?
From my point of view the only alternative (to DI with constructor injection) is static members somewhere, which does not scale. Maybe property injection, but ugh.
For example:
test.appsettings.json
{"db-url":"test.url.here"}
prod.appsettings.json
{"db-url":"prod.url.here"}
Now in the code:
Settings.fetch("db-url");
// Will return the test one if environment variable says we are running under test environment, else will return the prod one.
A lot of the complexity in the configuration system exists to enable a lot of configuration options. I have some ASP.NET applications that get some configuration from Environment Variables on the host machine, some from environment-specific config files, some from a configuration service (specifically Azure Key Vault), all without any of my DI-injected downstream components specifically needing to know which source provided specifically which configuration key. For the DI injected things it is just `config["db-url"]` or maybe type-safe class if I DI-inject an IOptions<T> (or add my type-safe classes directly to the DI without the IOptions<T> wrapper, which is another configurable option).
It's absolutely a very complex system, but with great complexity comes great flexibility (to badly paraphrase the Spider-Man mantra).
I just type IOptions<T> config in ctor, use ctrl+. and private readonly property and it's initialization is generated
I just tried it.
https://i.imgur.com/cWs1M77.png
and I have some naming rule
https://i.imgur.com/iPwNj5b.png
So I basically have to type IOptions<T> Name and use ctrl+. twice in order to generate private readonly property and initialize it and add using to IOptions
also as far as I heard, is that microsoft is working on a micro web framework, that does not use di. (something like https://github.com/featherhttp/framework, just more official i guess)
Can you show me what that looks like?
No part of aspnet core is built to run synchronous. Its all async all the way down.
In usual HN fashion, even the most seemingly obviously true points to me are being argued and refuted.
In my experience, the complexity is vastly reduced by not having to implement tricky asynchronous APIs (Task Paralell, thread pools, BackgroundWorker, etc).
Go: Goroutines make you wire the "stop" scenario on your own (channel close). Rust: Hold on to your future and drop() it. C#: Pass a cancellation token and call Cancel()
All seem reasonable and stay within what they think their developers will be able to pick up and run with.
I just finished an app that had to shutdown while some async operations were still not finished. Really hard to manage compared to using threads.
It's been said that C# is "Microsoft's Java". That applies not only to the language, but the culture of "enterprise-ism" (insanely excessive abstraction and complexity) that seems to permeate throughout the code.
I worked briefly with Enterprise Java a long time ago, and it brings back much of those memories whenever I have to look for something in a C# codebase.
I've left C# ecosystem 3 years ago because I was done with that as well, but then I landed in a mature Ruby on Rails project and I was crying for .NET "enterprisey" abstractions - once you see the code duplication that comes from the fat models and controllers, you have to grep the codebase and guess what happens in a function because it's in a mixin that assumes property exists in context, tests take forever to rerun because they bootstrap an entire environment and have no mocking. If I had to chose between a bad C# codebase or a bad RoR codebase if it's beyond something very simple I would chose C# one any day of the week - which is what I think C# approach gets you - guarantee that if you follow the conventions your project will scale reasonably well into maintainability and codebase size. You pay for that in initial development speed and pointless verbosity.
Most DI containers are not just injectors, but they also manage life cycles/scopes of objects: singleton/transient/scoped.
Since in web development, a request hitting a controller endpoint is typically short lived and most objects and services are tied to the main request scope, it makes sense that all your objects should live and die when the request is made and completed. What is the cleanest way to manage this life cycle scopes? Dependency Injection. Most of your object/services will be "scoped", with fewer being transient (aka a pool of reusable objects) and the fewest being singletons that will survive individual requests.
A second side effect of DI, it forces you to code by convention. Conventions make maintaining the code 6 months from now a breeze. Want to add a new service? Just let it inherit from an IService and consume it in 10 places, it will auto find/inject your new code. No need to new() up and dispose your service in 10 place.
Do all of this without DI and use only constructors + Dispose(). Good luck! Mercy on the poor soul who has to maintain all that mess.
If you want to go a step further, instead of having 100's of IService and each constructor declaring they each need 10 of them (ILoggerService, IAccountService, IEmailService, IStockService etc etc)... that also becomes very gross in large projects: so to go a step further, use the Mediator pattern. You can either implement your own (super easy) or use the Mediatr nuget package. Then each service becomes much cleaner. Each service then only has 1 method that does work. It also ties in nicely with the Unit of Work pattern.
Why should my IStockService inject the IEmailService when I just want to check stock levels with GetLatestStock() that return an int and does no other work?
Lets say the IStockService has 15 methods to do with stock and 10 dependencies. So on each request where you depend on IStockService, you would also pull in the 10 dependencies and their inner dependencies which may add 1 second to your response time and uses 10mb RAM. But all you wanted to was check stock which takes 10ms and 1mb RAM.
This is exactly the scenario that mediator pattern can help solve. So your 15 methods become 15 individual classes, each pulling in ONLY the dependencies it needs and nothing more. Your file and git commits stay small too which makes everything more digestible (less than 100 lines of code if lucky),
So my favourite setup is DI + Mediatr. Each file/class has only ONE purpose to exist and only one reason to be pulled into a dependency tree, all while DI will manage their life cycles.
Edit: For those downvoting, please go read "Dependency Injection by Mark Seemann" and also read https://docs.microsoft.com/en-us/aspnet/core/performance/per... . Then try to build your own framework from scratch. Do it. Then come crawl back and upvote this comment. I've have a ton of experience with this stuff and have built 2 or 3 custom frameworks. The core trait that keeps everything sane and fast is to keep things simple, keep dependencies low, keep hot paths lean. The easiest way to get to that place is DI + Mediatr and to break some services into their own api's if they get deployed too often. It's that simple.
That imperative lifecycle stuff is a big problem. Is that idiomatic in .net? Ideally, the program creates per-request data and then the garbage collector disposes of it after the end of the request (or earlier if it doesn't need to be retained until the end of the request). If you need to actually call Dispose, short of `using ...`, you have a problem.
> Lets say the IStockService has 15 methods to do with stock and 10 dependencies
Is this idiomatic in .net? It's obvious that that is a problem and that smaller classes/functions are better. Normally, your programming language and environment would let you create smaller classes/functions and call them as necessary. Why is something more complicated required in this context?
DI just hides the problem because such a class is now only 500 lines long instead of 2000 lines, so for some people that is acceptable (not for me though). You don't see any lifecycle code in your classes anymore since the DI container manages it for them.
Remember it is typical to see a business project with 300 classes/tables if the project is about 5 years old and never seen a major refactor/rewrite, and each of those classes gets manipulated in some form from an orchestration point (xManager class or xService class), which in turn might depend on each other or have 10 inner dependencies. To keep things "simple" most guys would just add another method to an existing service where it seems to make the most sense.
I argue against that cause the services become heavier and heavier over time and can do allll sorts of things after 5 years (aka IStockService can now check stock, send email notification, generate invoice, save pdf to disk etc). So I've come to the conclusion its better to have more folders/files to contain handlers, and each handler only has one Execute/Process method, and it's constructor declaring only the minimum amount of dependencies to do it's work. It also encourages ALL your services to do only one thing: you know and can trust that IPdfDiskPersistor only does one thing and doesn't have other weird side effects (like trying to send an email).
If all this is really not clean enough, then maybe an ActorModel framework like Orleans Framework might be better suited, but I haven't been able to convince anyone at work to use it instead of DDD (they are slightly at odds with each other).
Hope that answers some of your questions. If other are reading this, please think twice before you go down the DDD path - it is expensive both in code and time and can easily spiral into a monster (but also well worth it if a good developer have stewardship of it in the long term - keyword being stewardship). Unfortunately teams with high turnover is basically doomed to have a messy/failed project.
Some feel it can be needlessly complex to troubleshoot such a system or gain understanding of it if you weren't one of the original authors.
I've seen projects where the dependency object graph exceeded anything you could possibly new up yourself, due to circular dependencies and impossibly complex scope rules. That's definitely a sign of a program so abstract no one can follow it.
(Microsoft's very simple DI container that's now standard out-of-the-box in .NET Core has somewhat strict limits on DI scopes and disallows circular dependencies, so it's one of the better ones.)
The async/await syntax is where it's at. Honestly it's a massive improvement over promises, which I hated reasoning about almost as much as callbacks :)
I get that some people prefer async/await (I actually find explicit Promises clear enough that I don't have any strong preference, myself), but why wouldn't you be using explicit callbacks, which in my JS experience (mostly frontend, but also fairly modern) are super common outside of promises, which is the only place where I see modern JS replacing exlicit callbacks with a different structure.
Why not typescript?
But the ASP.NET part feels bloated.
Is there a simpler .NET framework?
Very curious. Why do you think it is poor ? Are you comparing it against other languages or frameworks?
I'm also impressed at just how fast asp.net core is compared to asp.net, the time it takes to open your site in debug mode has dropped dramatically, from what used to be 1/2 minute in asp.net to a few seconds in .net core.
On the bad side? Mainly the asp.net core team and their push for Dependency Injection and the really poor async code has got to be the most frustrating thing about the whole thing.
My biggest bugbear is the way you access your config, which is an absolute and utter kafka-esque mess. Because someone at MS was drinking the DI kool-aid you have to add a minimum of 5 lines of code to any class you want to access config values in, and you're in for an even bigger nightmare if you want to separate business logic and web code into two projects (which is a pretty common design). I've given up with the config DI and just assign them all to static variables. I still don't get why you'd even want your config injected!
The entire authentication uses async code too, which still has all the normal problems of being hard to debug, silently failing if you invoke it wrong and completely overwhelming the callerstack with a bunch of redundant calls.
Huge disadvantages with absolutely zero performance gains for 95% of programmers.
Having been using express again recently, I'm seriously thinking of ditching the asp.net core stack despite my general preference for statically typed languages. C# is great, but the asp.net core are so obsessed with shoving DI and async down your throat and making you write really unpleasant, boilerplate code. Feels like you're back in 1990 with all the FactoryFactoryFactories.