ipjohnson / Grace

Grace is a feature rich dependency injection container library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Questions about Func<> factories

jods4 opened this issue · comments

commented

Hi @ipjohnson !

I have a few question regarding factories if you don't mind answering them.
These questions are about use cases where I inject a scope locator and it works absolutely fine.
I was just wondering if there was a way to avoid taking a dependency on the full container.
I know Grace has tons of features but they're sometimes hard to discover! 😉

So I know that you can inject:

  • Func<T> for a factory resolving T in current scope.
  • Func<Scoped<T>> for creating a new (disposable) scope and resolving T inside of it.
  • Func<string, Scoped<T>> same as previous one, but you can give the scope a name.

Question 1: Can you get a factory to resolve a keyed T, the key being dynamically provided when calling the factory? (as opposed to: when importing the factory)

An example use case is having an interface IExporter and many implementation keyed by format (e.g. "xls", "xlsx", "cvs") and then being to dynamically create from code an IExporter based on a specific format.

Question 2: Can you do the same as Q1 combined with Scoped?
I.e. creating a new scope, and resolving a keyed T from code through a Func<>.

Question 3: Is it possible to import factories in one scope (e.g. global scope) and then use them to resolve T in a different scope provided dynamically?

Hypothetically I would import Func<IExportScopeLocator, T> in a global singleton, then use it like factory(scope) to get a T from a local scope.

Thanks!

Hi @jods4,

I hope your question is still relevant 😄

Short answer

Use this and you will be able to resolve Func<IinjectionScope,String,IExporter> to get an exporter with a given key from the provided scope.
registrationBlock.ExportFactory((IInjectionScope scope, string key) => scope.Locate<IExporter>(withKey: key));

Long answer

Func<string, Scoped> same as the previous one, but you can give the scope a name.

This is possible because Grace will try to propagate Delegate's parameters to all constructors of your target and related dependencies. And, coincidentally, Scoped<> constructor has a string parameter that allows you to define the scope name.
The most important thing is that this is not a standalone feature, but rather a part of a bigger feature that supports any custom delegate. See wiki: https://github.com/ipjohnson/Grace/wiki/Special-Types

Let me know if I could help you with anything else!

And here is the complete example:

using Grace.DependencyInjection;

namespace CodingSandbox;

public interface IExporter
{
    Stream Export<T>(T data);
}

public class ExcelExporter : IExporter
{
    public Stream Export<T>(T data)
    {
        throw new NotImplementedException();
    }
}

public class XmlExporter : IExporter
{
    public Stream Export<T>(T data)
    {
        throw new NotImplementedException();
    }
}

public class JsonExporter : IExporter
{
    public Stream Export<T>(T data)
    {
        throw new NotImplementedException();
    }
}

public class Module : IConfigurationModule
{
    public void Configure(IExportRegistrationBlock registrationBlock)
    {
        registrationBlock.Export<JsonExporter>().AsKeyed<IExporter>("json");
        registrationBlock.Export<XmlExporter>().AsKeyed<IExporter>("xml");
        registrationBlock.Export<ExcelExporter>().AsKeyed<IExporter>("excel");
        registrationBlock.ExportFactory((IInjectionScope scope, string key) => scope.Locate<IExporter>(withKey: key));
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var container = new DependencyInjectionContainer();
        container.Add(new Module());
        var factory = container.Locate<Func<IInjectionScope, string, IExporter>>();

        var exporter = factory(container, "json");

        if (exporter.GetType() != typeof(JsonExporter))
        {
            throw new InvalidOperationException();
        }

        var scopedFactory = container.Locate<Func<IInjectionScope, string, Scoped<IExporter>>>();

        var scopedExporter = scopedFactory(container, "json");

        if (scopedExporter.Instance.GetType() != typeof(JsonExporter))
        {
            throw new InvalidOperationException();
        }
    }
}

@Lex45x Thanks for answering.
The question is still relevant as far as my curiosity and knowledge about Grace goes! 😁
As I mentioned in the question, this need was fulfilled in our code by injecting an IInjectionScope directly.

The export factory approach is interesting. Some thoughts:

  • I'm even surprised it doesn't create a circularity issue in Grace (you define a factory for IExporter that itself locate an IExporter)!
  • It's nice because you don't actually fall into the service locator pattern by injecting a full-fledged IInjectionScope.
  • But you need to inject a function that takes injection scope Func<IInjectionScope, ..>.
  • You also need to manually configure the factory for every type.
    It's nice that Grace provides Func<T> automatically; I kind of hoped that for keyed exports there was a Func<string, T> always available.

Given all this, I suppose injecting IInjectionScope and doing scope.Locate<IExporter>(withKey: "xml") is just the most straightforward way to go about it. I'm not a purist ;)

As this is a discussion, not an issue I'm just gonna close it. (I would have opened a discussion if it was enabled for the repo).

BTW, speaking of keyed injections:

@Lex45x @ipjohnson I'm sure you've seen .net 8 introduces built-in support for keyed services?
https://weblogs.asp.net/ricardoperes/net-8-dependency-injection-changes-keyed-services

I suppose you'll have to implement new interfaces to integrate Grace with the new built-in API such as AddKeyedSingleton().

I did see the new APIs, very exciting. My plan is to add the interfaces as we get closer to .net 8 release date (late Nov).

commented

@ipjohnson not putting any pressure, just for my information:
.net 8 has RTM and grace 8.0 is in RC, so I wonder if you have any plan for a release that integrates with built-in AddKeyedSingleton()?
Thanks!

commented

@ipjohnson I've looked at what is required exactly and have partially done it in PR ipjohnson/Grace.DependencyInjection.Extensions#36.

It's incomplete because I think at least one new feature requires core Grace support: keyed registration that satisfy any key.