Hacker News new | ask | show | jobs
by jasonkester 3059 days ago
One of the cool things about ASP.NET is how many different ways there are to get things done. There is a ton of stuff in there, and you can pick and choose the bits that make sense for your project.

In the old days, that meant you could toss out all silly drag/drop, faux-stateful garbage they put in to make it so you could build webapps like they were VB6. Thus, leaving behind a really good compact MVC framework for building webapps (long before they came out with something actually called MVC that wasn't as good at it). Now it means that I can read this list and not see a single technology I've touched at all in my 18 years of build stuff on this stack.

Personally, I tend to disagree with the author's approach of pushing routine stuff out to the realm of "magically happens behind the scenes", in favor of having common base classes for my page handlers that can be explicitly told to do those things with a little Initialize() method at the top of the handler. That way, when something goes wrong I get a breakpoint looking at my own code.

I like control, so the idea of voluntarily putting something in place that could magically shove its way in front of my code and whisk the user off to an error page just smells wrong.

But again, it's cool that the stack gives us the option to do so if it fits our style.

5 comments

> I like control, so the idea of voluntarily putting something in place that could magically shove its way in front of my code and whisk the user off to an error page just smells wrong.

I feel the same way as you however I've found that when working on a large code base with a lot of junior developers or contractors who lack qualities like discipline, attention to detail, consistency, and convention; it's often easier to just wire up all of the important parts behind the scenes.

It's also useful when you're managing a number of projects that are primarily maintained by junior developers and you only drop in occasionally to do the heavy lifting.

> I tend to disagree with the author's approach of pushing routine stuff out to the realm of "magically happens behind the scenes"

Me too, and it's easy to take this approach way too far to where you're basically recreating your own MVC architecture in some pseudo-service layer that you're not 100% sure how it even gets executed in the first place.

But I'll admit for things you should be doing on 99.9% of calls (e.g. validating the model) it's nice to have it done one place and one place only.

The drawback to validating the model in one place only, as shown, is that there is only one response for validation errors. Sure, it's got the details of the error that will vary, but it's ALWAYS sending a 400 error. Even the example of the 'usual' way includes a comment showing multiple possible responses:

    // return bad request, add errors, or redirect somewhere
An ActionFilterAttribute might be a good approach, but in a real application I'd want two things: to apply it explicitly at the controller or method level rather than globally, and to pass it arguments to tell it what to do when there is an error. One of the arguments should allow me to call a response handler method, even if the only way to do that is to pass a string with the method name and use reflection to find and invoke it.
I do feel the recent changes have moved to prescription rather than flexibiity though.

For example, last I looked, the new configuration manager has to be injected.

Injected!

To me, who's not a particular fan of injection, that's insane. Especially given that the previous iteration was a static class that most of would just wrap in a static class called something like "Constants" or something.

These are global, static, variables. They shouldn't be changing during the application's run-time It should be super simple to use and fire-and-forget. Having to pass it into every constructor and then manage it is crazy to me.

Yeah, you can wrap the whole thing yourself, but it's just been done because DI is trendy at the moment. In my opinion, for a statically typed language, DI is just a pretty nasty language hack that's only there to facilitate test methods. You shouldn't be designing things around it.

Configuration can come from different sources, it can be reloaded, it can be lazy-loaded, there are so many different reasons why config is more like service and not a static variable. It has to be injected. Maybe you're using it in a way static access makes sense, but when you're designing generic Configuration solution, its whole another set of problems, requirements and constraints.
No it's not. We've had app config for the last 20 years. When you need mutable config put it in the db.

There is a case for a mutable config, but it should be a separate class, and still wouldn't need to be a service.

I sort of agree that config is more of a service.. but I had the same reaction as the parent here that logging had to be injected!

That just felt insane!

I personally like DI because you can take one glance at the constructor and know what the class depends on, as well as the testability benefits. Can you expand more on what you don’t like about it?
Fundamentally DI is magic code. Where did that thing come from? How did it get initialised? How do I fix it when it breaks?

But especially for config, it's that if I'm making a tiny app, or just bashing something out, or prototyping, I don't want to spend a bunch of time setting up and configuring something as fundamental as config.

At the moment you add the System.Configuration library as soon as you start a project and out of the box you can just do:

    var key = ConfigurationManager.AppSettings["SendGridAPIKey"]; //will be a string
With the newer features of C# you can put that on a class as simply as:

    public static class GlobalConfig
    {
        public static string SendGridAPIKey => ConfigurationManager.AppSettings["SendGridAPIKey"];
        public static bool EmailsEnabled => bool.parse(ConfigurationManager.AppSettings["EmailsEnabled"]);
        //etc.
    }
Then access that anywhere in your code with the below. At worst you might have to add a namespace, which is literally ctrl+. then press space, if it happens to be in a different namespace:

    GlobalConfig.EmailEnabled
What I wanted from a new config manager is for it to be even simpler, to handle empty strings, etc. automatically. I don't see anything wrong with accessing those sort of config setting with the below. It's config, it's static, it's supposed to be globally accessed. It's a special sort of const that you're allowed to change without re-compiling.

    public static decimal MinimumOrderValue => Config.GetDecimalOrDefault("MinimumOrderValue");
(The OrDefaultValue there would mean 0 by .Net conventions for non-C#ers, if the string was null)

Instead virtually every business class you make has to accept the config object, store it on a local variable and then access it through a local variable. Or you can spend a bunch of time wrapping the god-awful thing in your own static class or singleton (ugh) or whatever.

With the new way you have to do a bunch of extra work, write extra code. And what have I gained? Sod all. I can already change the config for unit tests by changing the config file in the unit test project. The functionality already exists. You still have to do that with the new method.

I want simple, easy and usable without having to spend a load of time configuring. Instead we got a steaming turd because it feels like their ASP.Net Core developers don't seem to get what DI is for. I feel like the ASP.Net team has been getting far too opinionated and often shows a lack of understanding about how swathes of their developers use their product.

This in particular reminds me a lot of the WCF and WWF over-engineering and over-reliance on lots of config and voodoo magic and rain-dances when it went wrong.

If you don't like DI then I feel like C# in general won't be your cup of tea. As for the "only for testing" bit, I disagree.

I'm currently in a situation where I'm on the fence about two different ways to access my database (both are terrible options). Both db drivers are very different, and require very different code. However, they both return the same models. By defining an interface and injecting one service or the other in to my controllers, I can quite effectively and painlessly switch between the two. Why not a singleton? Because with one of the drivers it's better to build a new one on every request.

Back to injecting configuration... I actually use this database access service across two different ASP.NET Core projects. The configuration manager has a nice way to extract only parts of the configuration in to a model. Combine with DI and I can easily split my configuration models between these projects without a shared config model.

Sure, there's other ways to skin the cat. I'm not saying DI is the only way, but I do find it an effective method and certainly not a "nasty language hack".

That's utter nonsense.

There's absolutely no reason you have to do DI in C#. C# only recently had native DI introduced.

The only reason to use DI is to be able to inject mocks. It's the only reason. Ruby, for example, can do this without DI. It's literally making bad code just to be able to test it.

It's a language bug that DI is needed.

There are some who believe DI somehow magically makes your code less coupled, but go take a look at any client project in the wild and that's immediately a laughable and indefensible position. If anything, DI makes it worse. I've seen classes with 20 odd service injected, which each all have their own 10 or 15 classes injected, making that class coupled to something like 70-100 other classes.

And DI makes it even harder to pick them apart.

I just gave you my reasons for using DI and you just ignored them, repeating that the only reason for DI is testing, even though my reasons are not for testing. That's utter nonsense.

If you don't like DI and you're using C#, you're gonna be swimming upstream. It's been the C# dogma for many years. You sound like you'd be much happier away from C#.

Serious question: how do you test something without injection?

But yes I agree injecting configuration seems sub-optimal at best.

Read about Ruby and DHH's opinion on DI. That's how.

Also, remember there's a huge swathe of apps where tests are wholly inappropriate and designing everything around DI is annoying for all those cases.

Prototypes, one time apps, internal tasks, the significant proportion of programmers who think testing's a waste of time and/or money, etc. or that only small, key parts should be tested.

In the case of the configuration manager, the test project would have its own underlying configuration file.
I don't see how injecting is crazy? You mean you find it crazy that you have to add an additional config parameter to your controller constructors? If that is what bothers you then you can create one base constructor where you inject the config and then have other classes derive from it...
It's simply not an appropriate thing to be injecting. App settings are that, settings for the app. They don't change on the fly, if they do change the app should restart, and it's definitely not something that should be injected.
I'm not quite sure what you mean. There is no ConfigurationManager used in Asp.NET Core. Are you talking about IConfiguration?

The reason why you want to have it injected as IOption<> is cause you might want to change configuration on the fly. If you do it right, there is no need for the app to restart if you change the appsettings.json at all. Also it makes things much more flexible and loosely coupled, for example third party middlewares can bring their own Settings and you can define which part of the whole appsettings.json is meant to be for that Middleware.

There is also no need to pass it to the constructor and manage it yourself via a field inside your class, at least in your controllers. You can just use the [FromServices] attribute before an action argument to inject it into just that method (and that works for properties deeper down the "callstack" aswell).

Sigh. Of course I want the app to restart, it's configuration. Changing it on the fly and hoping the Devs coded defensively enough to handle mutating config is a recipe for disaster.

I can see a case for mutable config, I use it, but the majority of config is not app time mutable and you shouldn't be designing a system for an edge case. Have a mutable config.

Experienced devs use the db for that by the way as mutable config generally needs an in app interface.

I remember fondly "back in the old days" spending a couple of years doing Java servlets (for reference, this was right when JSPs were becoming a thing) where we implemented a nice compact MVC approach. The performance was very good at the time. When I moved over to ASP.NET I felt baffled by all of the heavy WebForms templating, but soon discovered that you could set up an almost identical framework as we had with servlets by implementing HttpHandlers and some light-weight views with all the ViewState stuff turned off (it was probably most similar to something like NancyFx now). I feel like many of the frameworks now are doing the same stuff that worked well 15 years ago, but it is fleshed out better. And of course there is a ton of open-source work, which was missing back then.
interestingly the plus points from your first sentence (which I agree are plus points) are the exact things that Scala is often criticised for.