yairhalberstadt / stronginject Goto Github PK
View Code? Open in Web Editor NEWcompile time dependency injection for .NET
License: MIT License
compile time dependency injection for .NET
License: MIT License
I have have an error as follows:
Error while resolving dependencies for 'Pttox.Core.ViewModels.GroupPttViewModel': We have no source for instance of type 'Xamarin.Forms.Page?'
GroupPttViewModel doesn't have a direct dependency on Page, and GroupPttViewModel's depedency tree is quite large so finding the things that needs Page is hard.
Searching the code for Page is hopeless because it's a Xamarin.Forms app so page is everywhere.
Would it be possible to make the error message include the full dependency chain, or at least include the type that has a direct dependency on Page.
It's nice if classes don't require a type that they can mutate (T[]
). It's a small thing, but it could matter if the type is also used without StrongInject, for example in test code.
I use ImmutableArray<T>
except in existing projects that can't or don't yet reference System.Collections.Immutable.
The reason I'm not asking about IReadOnlyList<T>
is that I use that type instead of IReadOnlyCollection<T>
when I want to imply that the index order is significant. I'm assuming that StrongInject doesn't have an ordering guarantee that is obvious to the reader since it could be collecting implementations from various separate files.
If an optional parameter cannot be resolved it should be a warning.
If users do not want this warning it is simple enough for them to suppress it.
If this causes users issues we may revisit this later.
For users that don't require async initialization, having to making everything asynchronous is unneccessary complication.
We should have essentially two API, a synchronous one and an asychronous one:
IFactory and IAsyncFactory
IContainer and IAsyncContainer
IInstanceProvider and IAsyncInstanceProvider
IRequiresInitialization and IRequiresAsyncInitialization
It will be an error if any async dependency is required for resolving an IContainer
resolution.
This led me to believe visually that there was no preview in this feed. It also confuses NuGet tooling when you ask for prerelease updates.
Ordinarily, the version would be 1.2.1-x
. I use a build script that autoincrements the csproj/props version based on git tags so that manual commits are not needed to achieve this. (https://github.com/Techsola/InstantReplay/blob/main/build.ps1#L17-L20)
I have a .net 5 WPF project that I tried to use StronInject on. But the injection code is not being generated.
My Container is as follows:
using JsonSrcGenInstantAnswer.ViewModels;
using StrongInject;
namespace JsonSrcGenInstantAnswer
{
[Register(typeof(SearchViewModel))]
[Register(typeof(MainWindow))]
public partial class InstantAnswerContainer : IContainer<MainWindow>
{
}
}
However when I build the build fails I get the following errors in my build output (but strangely not in my Errors list)
1>C:\Work\JsonSrcGenInstantAnswer\JsonSrcGenInstantAnswer\InstantAnswerContainer.cs(8,50,8,72): error CS0535: 'InstantAnswerContainer' does not implement interface member 'IContainer<MainWindow>.Run<TResult, TParam>(Func<MainWindow, TParam, TResult>, TParam)'
1>C:\Work\JsonSrcGenInstantAnswer\JsonSrcGenInstantAnswer\InstantAnswerContainer.cs(8,50,8,72): error CS0535: 'InstantAnswerContainer' does not implement interface member 'IContainer<MainWindow>.Resolve()'
1>C:\Work\JsonSrcGenInstantAnswer\JsonSrcGenInstantAnswer\InstantAnswerContainer.cs(8,50,8,72): error CS0535: 'InstantAnswerContainer' does not implement interface member 'IDisposable.Dispose()'
Hi Yair,
Version 1.0.8 generates for code like this:
private global::Project.Services.SymbolConverter GetSingleInstanceField2()
Would you consider to change it to something like this:
private global::Project.Services.SymbolConverter GetSymbolConverterField2()
?
Obviously, it's not perfect but it would be much easier to read the generated code. I realize that not that many people actually are supposed to read the generated code. However, I suspect that it is necessary to read the generated code when one hits a bug.
Anyway, it's an idea, maybe you find it worth it.
Thanks for any feedback!
This will prevent the confusing RunAsync
is not implemented error.
This would allow Factories
to have control of how to dispose of the objects they create.
Spec:
Add type:
namespace StrongInject
{
public interface IReleasingFactory<T> : IFactory<T>
{
System.Threading.Tasks.ValueTask ReleaseAsync(T instance);
}
}
For any type registered as an IFactory<T>
which implements IReleasingFactory<T>
, then for any instance of T
created by an instance of the factory, we will call factory.ReleaseAsync(instance)
rather than use the default disposal.
Registering a type as IReleasingFactory
should produce a warning.
cc @jnm2
So @ACPWintiate and I are experimenting with a few ideas. Here is an initial idea, but please feel free to give feedback.
Whilst modules are great for reuse of anything that can be registered statically they can't be used to register anything dynamically, because you can't inject dependencies into them.
If you could inherit from a module that would solve this issue.
This will involve the following changes:
Instances/factories in modules must be public static, or protected.
Inheriting from a module is equivalent to importing a module, except that you also import protected Instances/Factories.
Hi,
I think a "How it works?" part would be important for the README file, as it would help newcomers to understand the basic concepts of the container (because it is a lot more different than most popular IoC containers available).
I thought of a short chapter early on in the README file, which summarizes the internal mechanisms in a few line, and after the few lines it provides a link for a wiki page which describes it in more detail.
Similar to factory methods, instances can be marked as an [Instance]. To be exported they must be public and static.
I'm getting this as a warning, but I don't know what it means or why I am getting it, as far as I can tell I don't have any constructors taking object.
The ReadMe is getting longer and longer. We should create a proper wiki/static website for documentation. This will be an appropriate place for other things like examples as well.
The documentation must be kept in source control in this repository. If we use github wiki we should use a github action to copy documentation to the wiki repository.
IInstanceProvider<T>
and IFactory<T>
have identical APIs and it seems pointless keeping them both,
Instead if an [Instance]
field/property or a [Factory]
method returns something of, or inheriting from, IFactory<T>
we should automatically resolve it as a factory.
Then we can remove InstanceProviders, and replace their usages with [Instance] IFactory<T> _instanceProvder;
Great job on stronginject @YairHalberstadt, I'm really impressed by the execution of this really clever idea.
A lot of frameworks and libraries today use Microsoft.Extensions.DependencyInjection
, and offer extensions methods that hang off of ServiceCollection
. I see the ASP.NET Core example here showing how to integrate the two worlds:
[Register(typeof(WeatherForecastController), Scope.InstancePerResolution)]
[Register(typeof(WeatherForecastProvider), Scope.InstancePerDependency, typeof(IWeatherForecastProvider))]
[Register(typeof(WeatherSummarizer), Scope.SingleInstance, typeof(IWeatherSummarizer))]
[Register(typeof(UsersController), Scope.InstancePerResolution)]
[Register(typeof(DatabaseUsersCache), Scope.SingleInstance, typeof(IUsersCache))]
[Register(typeof(MockDatabase), Scope.SingleInstance, typeof(IDatabase))]
[RegisterDecorator(typeof(DatabaseDecorator), typeof(IDatabase))]
public partial class Container : IContainer<WeatherForecastController>, IContainer<UsersController>
{
private readonly IServiceProvider _serviceProvider;
public Container(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[Factory] private ILogger<T> GetLogger<T>() => _serviceProvider.GetRequiredService<ILogger<T>>();
}
I think it would be really nice if there was a simple attribute that could be used (such as ForwardToServiceProvider
, or something to that effect), so that the generic code is auto-generated, and the factory is added automatically. So the sample would become something like:
[Register(typeof(WeatherForecastController), Scope.InstancePerResolution)]
[Register(typeof(WeatherForecastProvider), Scope.InstancePerDependency, typeof(IWeatherForecastProvider))]
[Register(typeof(WeatherSummarizer), Scope.SingleInstance, typeof(IWeatherSummarizer))]
[Register(typeof(UsersController), Scope.InstancePerResolution)]
[Register(typeof(DatabaseUsersCache), Scope.SingleInstance, typeof(IUsersCache))]
[Register(typeof(MockDatabase), Scope.SingleInstance, typeof(IDatabase))]
[RegisterDecorator(typeof(DatabaseDecorator), typeof(IDatabase))]
[ForwardToServiceProvider(typeof(ILogger<>))]
public partial class Container : IContainer<WeatherForecastController>, IContainer<UsersController>
{
}
If a type takes e.g. a Func<T>
in it's constructor we should auto-implement that.
If it takes a Func<int, T>
we should also auto-implement that provided int
is used anywhere in the transient dependency chain for that type.
I have type that takes a Func<CallInfo, ISession> as a constructor argument.
I have injected it using a Factory in a module
[Factory]
public Func<CallInfo, ISession> CreateSessionFactory()
{
....
}
However I'm getting the following error:
Error while resolving dependencies for Pttox.Core.Services.ILinkHandler: we have no source for instance of type 'Pttox.Core.Services.ISession'
This is odd for several reasons,
Currently resolution is linear - dependency 1 is resolved, then dependency 2, then dependency 3, etc.
However when resolution is async, we could kick it off, and then only await it when it's needed, allowing multiple things to be resolved in parallel.
There's 2 parts to this:
Change resolution so it doesn't immediately await dependencies only once they're needed.
Order resolution so that we kick off async tasks as early as possible, and await them as late as possible. I need to think a bit about what algorithm we could use for this.
Just do structural stuff.
Hi,
I've included your repository in my personal C# Source Generators list to help gather these kind of projects in one place and hopefully under single umbrella, thus I propose to add the csharp-sourcegenerator
topic to this repository to help with visibility.
I hope you're okay with this. If you happen to know other generator projects, I'll be happy to enlist those in my list as well.
Thanks!
Is it possible for users to use convention based registration. For example a user might want to auto-register all types, or all types with a specific suffix, etc.
In general this relates to plugin functionality. We need to work out how it's possible for users to extend out StrongInjects functionality. dotnet/roslyn#48358 would allow this to be done by running a source generator before StrongInject to generate StrongInject registrations. This does make customizing things painful, but I'm not sure I can think of better alternatives.
I have started using StrongInject and love the idea of compile-time DI, but have not been able to get things to work with the [FromServices]
attribute.
Specifically I get the following error when trying to inject a service into a controller action using [FromServices]
, which I presume means that the service is not being resolved by StrongInject at all:
System.InvalidOperationException: No service for type 'StrongInjectTest.Services.IMyTestService' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinder.BindModelAsync(ModelBindingContext bindingContext)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
I find the [FromServices]
pattern to be very useful to avoid creating too many services in the constructor when they are only used in a single controller action.
Is this currently possible and, if not, is it something which is planned for the future?
Currently internal types cannot be registered at all with modules. This is because modules can be used by containers outside the current assembly, and code generation can not generate code to instantiate internal types outside their declaring assembly.
This is a significant limitation. At the very least we should allow these to be registered with internal modules and containers. Whilst it makes sense to want to allow this on public containers, containers are also modules so I'm not sure whether this will work well.
[Registration] -> [Register]
[FactoryRegistration] -> [RegisterFactory]
[ModuleRegistration] -> [RegisterModule]
etc.
Initially mark existing attributes as [Obsolete("Use RegisterAttribute instead", error: true)]
However after a month or two delete the old attributes.
First of all, love the project. I've been really excited for source generators and this project just uses then so well.
Now, I'm trying to inject a logging interface in my application.
The interface is:
public interface ILogger
{
void Info(...);
void Error(...);
}
And my implementation is:
public class Logger<T> : ILogger
{
public Logger()
{
//uses T to get the actual logger from some library
}
public void Info(...);
public void Error(...);
}
As you can see, the consumer type is used in the implementation but not in the interface.
I know that default (at least for microsoft) is to use something like ILogger<T>
, but all my projects uses ILogger
(and I personally thinks that is better).
I don't know if currently is possible to use StrongInject to resolve this.
One possible solution is to provide the consumer type (the type begin resolved) in the factory methods and in the IFactory
interface.
I'm thinking something like this:
public interface ILogger { }
public class Logger<T> : ILogger { }
public class A { public A(ILogger logger) { } }
public class B { public B(C c) { } }
public class C { public C(ILogger logger) { } }
public partial class Container : IContainer<A>, IContainer<B>
{
[Factory]
private static ILogger Create<TConsumer>() => new Logger<TConsumer>();
}
var container = new Container();
container.Resolve<A>(); // Here TConsumer is 'A'
container.Resolve<B>() // Here TConsumer is 'C'
Or maybe something like this:
public interface ILogger { }
public class Logger : ILogger { public Logger(Type type) { } }
public class A { public A(ILogger logger) { } }
public class B { public B(C c) { } }
public class C { public C(ILogger logger) { } }
public partial class Container : IContainer<A>, IContainer<B>
{
[Factory]
private static ILogger Create(Type constumerType) => new Logger(constumerType);
}
var container = new Container();
container.Resolve<A>(); // Here the parameter 'constumerType' is 'A'
container.Resolve<B>() // Here is 'C'
I understand that this is probably a breaking change and if it's not something that you're not willing to do.
Thanks for the wonderful project ๐
This should wait till support for generic registrations is merged. This will allow us to register Lazy<T>
.
Predefined registrations should be overridden by an user defined registrations for the same type.
A Decorator of SomeInterface
must must have exactly one constructor parameter of type SomeInterface
and must implement SomeInterface
. Multiple decorators can be registered for a type, and all will be used to wrap the underlying instance.
The order decorators will be applied is an implementation detail. No guarantee as to the order is provided and it may change in the future.
It should be possible to mark a generic method as a factory, on condition the return type uses all the method type parameters.
When we can't find a registration for a type, we'll fall back to looking at generic factory methods, and seeing if any of them can provide the type.
Things to think about:
Should we allow the method factory to just return T
?
How do we efficiently lookup the which generic methods match the type?
Should we disambiguate generic method candidates based on the constraints?
Since installing and starting to use StrongInject I get the following error showing up at the top of the IDE:
Feature 'CodeLens references' is currently unavailable due to an internal error
StreamJsonRpc.RemoteInvocationException: Object reference not set to an instance of an object.
at StreamJsonRpc.JsonRpc.<InvokeCoreAsync>d__139`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.<TryInvokeAsync>d__17`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)
RPC server exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.CodeAnalysis.CodeLens.CodeLensReferencesService.<TryGetMethodDescriptorAsync>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.CodeLens.CodeLensReferencesService.<>c__DisplayClass9_0.<<FindReferenceMethodsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.CodeLens.CodeLensReferencesService.<FindAsync>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.Remote.RemoteCodeLensReferencesService.<>c__DisplayClass5_0.<<FindReferenceMethodsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Threading.Tasks.ValueTask`1.get_Result()
at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.<RunServiceImplAsync>d__12`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)
It would be nice to be able to use existing infrastructure and just drop in stronginject as a replacement. For example, an existing application might depend on:
Following up from #134 (comment).
(I missed the readme section about the standard module because this tool is so darn intuitive to use...)
We use serilog for logging and it needs to be initialised with the type of the object it will be logging for (the type it is injected into)
ILogger specificLogger = logger.ForContext(typeof(Parent))
Our types have the ILogger as constructor arguments and out current injection framework creates them for us with the correct context.
I can't see anyway to achieve this with stronginject Factories
The current IFactory<T>
interface is essentially a Func<ConstructerParams, T>
This requires a lot of boilerplate, since you need to define a constructor, fields and the CreateAsync method.
We could use a marker interface and look for a convention based CreateAsync method which can take parameters.
The current workaround would be to use records/primary constructors to simplify the declaration of a constructor/fields to the bare minimum.
The two options are
Current:
public record Factory(A a, B b) : IFactory<(A, B)>
{
public ValueTask<(A, B)> CreateAsync() => new ValueTask<(A, B)>((a, b));
}
Suggested:
public class Factory : IFactory<(A, B)>
{
public ValueTask<(A, B)> CreateAsync(A a, B b) => new ValueTask<(A, B)>((a, b));
}
These should be published as seperate Nuget packages
Also, what do you think about only making it explicit if there is more than one IContainer<>
implementation on the container class?
Here's the readme source:
Lines 181 to 195 in 4a18700
This should be allowed:
using StrongInject;
public class A {} : IEnumerable<string>
[Registration(typeof(A), typeof(IEnumerable<object>))]
public partial class Container : IContainer<IEnumerable<object>>
At the moment StrongInject cannot be used by other containers because it cannot resolve anything for use outside the container.
It can only run a func inside a Run
method.
We should add a method Resolve
which returns an Owned<T>
.
namespace StrongInject
{
public interface IContainer<T> : IDisposable
{
TResult Run<TResult, TParam>(Func<T, TParam, TResult> func, TParam param);
Owned<T> Resolve();
}
public sealed class Owned<T> : IDisposable
{
private readonly Action _dispose;
private int _disposed = 0;
public Owned(T value, Action dispose)
{
Value = value;
_dispose = dispose;
}
public T Value { get; }
public void Dispose()
{
var disposed = Interlocked.Exchange(ref _disposed, 1);
if (disposed == 1)
{
_dispose();
}
}
}
}
This would allow resolving IEnumerable<T>
for example.
Currently StrongInject doesn't allow you to provide multiple registrations for a type.
We'll have to change the logic so you can register multiple registrations for a type. At resolution we disambiguate the best one as follows:
Any registrations provided by a module are better than any registrations it defines.
If this doesn't give any best registration we produce an error. The user can solve this by adding a registration for the type to the container, which will be better than any imported registrations. If the container already has multiple registrations for a type, the user will have to move all but one registrations to a module, and import the module.
Hi, I think it would be awesome if the C# 9.0 init-accessors for properties would be supported. Consider following example:
public interface IRunnable
{
void Run();
}
internal class Runnable : IRunnable
{
internal IHelloWorldPrinter HelloWorldPrinter { private get; init; }
public void Run() => HelloWorldPrinter.DoIt();
}
public interface IHelloWorldPrinter
{
void DoIt() => Console.WriteLine("Hello World!");
}
internal class HelloWorldPrinter : IHelloWorldPrinter
{
}
Without the init-accessor feature I would prefer construtor injection over property injection, because for the latter the property would need to become mutable. In my opinion the init-accessor is changing the game. In the example above, if constructor-injected, then the constructor would initialize a readonly field or get-only property anyway. So by supporting init-accessor the dependency could be injected into the "get-only" (of course there is also the init) property right away.
Additionally, that way the dependencies which are not relevant for the constructor but later on could be separated from the constructor.
One little caveat is of course that the init-injected properties cannot be used in the constructor, because they'll be initialized by the runtime after the constructor run through.
If a constructor has multiple parameters, one of which needs special treatment, you need to create a factory method which takes all the other parameters as dependencies and manually calls the constructor.
This means if a new parameter is added to the constructor, we have to update the factory as well.
If there was some way to modify a single parameter of a constructor, that would avoid this issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.