Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Questions about Func<> factories #308

Closed
jods4 opened this issue Mar 17, 2023 · 7 comments
Closed

Questions about Func<> factories #308

jods4 opened this issue Mar 17, 2023 · 7 comments

Comments

@jods4
Copy link
Collaborator

jods4 commented Mar 17, 2023

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!

@Lex45x
Copy link
Contributor

Lex45x commented Aug 10, 2023

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();
        }
    }
}

@jods4
Copy link
Collaborator Author

jods4 commented Aug 10, 2023

@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 ;)

@jods4
Copy link
Collaborator Author

jods4 commented Aug 10, 2023

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).

@jods4 jods4 closed this as completed Aug 10, 2023
@jods4
Copy link
Collaborator Author

jods4 commented Aug 10, 2023

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().

@ipjohnson
Copy link
Owner

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).

@jods4
Copy link
Collaborator Author

jods4 commented Nov 23, 2023

@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!

@jods4
Copy link
Collaborator Author

jods4 commented Nov 23, 2023

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants