10

I am trying to get a .NET Framework class library in line with an ASP.NET Core 2.1 application while using builtin DI mechanism. Now, I created a config class and added appropriate section to appsettings.json:

services.Configure<MyConfig>(Configuration.GetSection("MyConfiguration"));
services.AddScoped<MyService>();

In class lib:

public class MyService 
{
    private readonly MyConfig _config;

    public MyService(IOptions<MyConfig> config)
    {
        _config = config.Value;
    }
}

However, in order to build this classlib I have to add Microsoft.Extensions.Options NuGet package. The problem is that package carries a hell of a lot of dependencies which seem rather excessive to add just for the sake of one interface.

enter image description here

So, the question ultimately is, "is there another approach I can take to configure a DI service located in .NET Framework class library which is not dependency heavy?

mmix
  • 6,057
  • 3
  • 39
  • 65
  • 1
    Without losing the scoped/reloadable features of the MS Configuration? No. If you are happy with static singleton options, you can register the options class with the IoC container. But you won't get new configuration when it changes and it won't be scoped (it could be changed in mid request if another requests modifies it) – Tseng Aug 17 '18 at 11:40
  • Yes. At this point I would even consider a hack. – mmix Aug 17 '18 at 11:41
  • 6
    The dependencies by itself aren't anything bad btw. Its the way .NET Core and .NET Standard were designed upon. Before you would have the big fat framework with all the assemblies shipped with it. In .NET Core/.NET Standard, its split among packages, that's the reason you see it. Also System.Memory contains important optimizations to reduce memory allocations and memory fragmentation, so its a good thing :P – Tseng Aug 17 '18 at 11:46
  • it's silly approach imho, but the bigger problem we have is that our world does not revolve around .net core and we are big fans of fat stable frameworks, they make our lives tolerable and our applications uniform and safe from DLL (or in this case dependency) hell. The core seems to push us back into instability for the sake of reduced upgrade cycles. We have a full set of LoB apps on forms and WPF and already DI enabled service libraries used in those projects. I can live with some interoperability package added to them, but sink them deep into core dependencies? imho, this is poor design. – mmix Aug 17 '18 at 11:53
  • I wonder whatever happened to people who made Unity and MEF... – mmix Aug 17 '18 at 11:54
  • Think you got it wrong. Shipping the framework increases the update cycles, since now you don't have to wait until the monolithic framework is upgraded and field tested enough to install that on your application serer machine, since the old monolithic framework upgrade may affect all installed applications. .NET Core and .NET Standard run side-by-side. You can use a newer version of a specific library in one project without any side effects on the other applications that are running on the same machine. – Tseng Aug 17 '18 at 12:01
  • UnityContainer (and Prism) was handed over and are now maintained by the community. https://github.com/unitycontainer/container – Tseng Aug 17 '18 at 12:01
  • We are not getting it wrong. We like waiting on field tested and stable things. This is the enterprise. We don't want to have 300 applications each running on their own versions of libraries using features that should be "common". That's by definition a DLL hell. This system has been working for 17 years since .NET came to be and is the principal reason why others died off, the enterprise fell in love with it at first sight. This new MS .NET crew is making us all jittery. – mmix Aug 17 '18 at 12:14
  • 1
    Well this tone is not going to bring you any further. Like I said, if you don't care for the scoped options guarantees and the reloading mechanism, you can just inject `MyConfig` into your services instead of `IOptions` and have an additional registration in ConfigureServices: `services.AddScope(provider => provider.GetRequiredService>().Value);` instead. But .NET Core and .NET Standard is pretty mature by now, platform independent and can be used in a lot of enterprise scenarios (unless you need WCF or System.Web based stuff) – Tseng Aug 17 '18 at 12:46
  • 1
    We do use f/w, but asp.net has moved completely to core. So no choice there, we either forsake web or we use core. No more TLC from MS for .NET :). And core is not mature, it has breaking changes between minor versions. Its far from being a rounded, well designed product. We hope that's why they took the 3.0 to go out next year, maybe someone finally realized this chaos is counter productive and decided to stabilize things. The mere fact we now have two competing .net products from the same company is disconcerting enough. The fact its done by the same people is, well, slightly schizophrenic. – mmix Aug 17 '18 at 14:21
  • I did resolve this problem with singleton configs though, so at least we can try and use it. If you add that as an answer I can accept it. – mmix Aug 17 '18 at 14:21
  • 1
    I have to ask because I find this fascinating, which dependencies are the problem? – davidfowl Aug 20 '18 at 06:15
  • 1
    All these dependencies you take anyways if you use the .NET Framework. Here, it just happens to be in its own package for the sake of clarity. But the framework - be it .NET Core or .NET Framework - ships with that stuff anyways... – Structed Aug 20 '18 at 07:31
  • 1
    You don't have to depend on `IOptions` and IMO you *shouldn't* depend on `IOptions`. It's a useless abstraction. See here [my reasoning](https://simpleinjector.readthedocs.io/en/latest/aspnetintegration.html#working-with-ioptions-t). – Steven Aug 21 '18 at 12:36
  • We run a tight ship, so all stations and servers are standardized. When we upgrade .net framework we retest the entire code base and we upgrade 10% then everyone at once. The process takes almost a month. The prospect of users running countless versions of libraries on different projects is just a big no-no, having them depend on standardized framework version is OK (because everyone has the same and its tested). As for davidfowl, the answer is "all unneeded". I maybe old school but I prefer simplicity. You do not create dependencies to single-liner modules, thats what front end people do. – mmix Aug 23 '18 at 08:00
  • FYI, ASP.NET Core also runs on .NET Framework, not just .NET Core. – user247702 Jan 18 '19 at 10:35

3 Answers3

7

Check this article written by Filip Wojcieszyn.

https://www.strathweb.com/2016/09/strongly-typed-configuration-in-asp-net-core-without-ioptionst/

You add extension method:

public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration) where TConfig : class, new()
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));

        var config = new TConfig();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
}

Apply it in configuration:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.ConfigurePOCO<MySettings>(Configuration.GetSection("MySettings"));
}

And then use it:

public class DummyService
{
    public DummyService(MySettings settings)
    {
        //do stuff
    }
}
Slawomir Brys
  • 341
  • 3
  • 4
  • 1
    Actually, I solved the problem by appplying `Bind` in `ConfigureServices`, but I see the appeal and clarity in making that an extension on `IServiceCollection`, I am actually going to refactor it using this approach, so I'll also accept your answer. – mmix Aug 23 '18 at 07:52
  • There is literally no need for any of this code. The IServiceCollection will handle the casting of the configuration section, exactly as outlined in my answer below. Traditionally the null check for services is done at the consumer level (i.e. the constructor) so I don't see the value added here. – pim Aug 24 '18 at 09:26
1

I bumped into this problem a little while ago, if you can even call it a problem really. I think we all tend to get a little shell-shocked when we see a dependency list like that. But as @Tseng mentioned, it's really not a big deal to include a bunch of extra tiny assemblies (they'll be included in the bin already anyways by virtue of a reference in another project). But I will admit it's annoying to have to include them just for the options interface.

How I solved it was by resolving the service dependency in startup.cs and adjust the service's constructor accordingly:

services.AddTransient<MyService>(Configuration.GetConfiguration("MyConfiguration"));
pim
  • 12,019
  • 6
  • 66
  • 69
  • The problem I have is not so much being shell shocked, its that these libraries are not solely asp.net libraries, they are also shared by WPF projects and a lot by legacy WinForms. Putting these dependencies in business classes makes no sense to us as they will then become dependencies of those other projects. – mmix Aug 23 '18 at 07:47
  • I feel you and totally agree. I like to build my services with dependencies that can be considered the lowest possible denominator. Hence, why I like the injection of `MyConfiguration` directly over `IOptions`. – pim Aug 23 '18 at 09:39
0

If you don't care about whatever IOptions provides you, why not just inject IConfiguration into your service?

public class MyService
{
    private readonly IConfiguration _config;

    public MyService(IConfiguration config)
    {
        _config = config;
    }

    public void DoSomething()
    {
        var value = _config["SomeKey"];

        // doing something
    }
}
TheBlueSky
  • 5,526
  • 7
  • 35
  • 65
  • 6
    I would say that injecting `IConfiguration` is even worse than injecting `IOption`. It's the [Service Locator](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) equivalent for configuration values, since it completely hides the used configuration values instead of explicitly stating them in te constructor. It makes the class responsible of reading and converting those values instead of the application's start-up path. I would even go as far as calling the injection of `IConfiguration` (any any class that lives outside the Composition Root) an anti-pattern. – Steven Aug 21 '18 at 12:48
  • I would agree with the comment. Also, the `IConfiguration` is also an unneeded dependency that carries other stuff with it. – mmix Aug 23 '18 at 07:45
  • Besides injecting `IConfiguration` and additional logic behind you could put into a class, the problem is configuration keys. In case of simple structure you could have `var value = _config["SomeKey"];`, but in more complicated configuration you might end up with `var value = _config["ParentKey:ChildKey:ChildKey"];` – Slawomir Brys Aug 25 '18 at 10:14