├── .editorconfig ├── .github └── workflows │ ├── dotnet-core.yml │ ├── toc.yml │ └── wiki.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── NuGet.config ├── README.md ├── Samples ├── AspNetCore │ ├── Container.cs │ ├── Controllers │ │ ├── UsersController.cs │ │ └── WeatherForecastController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Services │ │ ├── DatabaseDecorator.cs │ │ ├── DatabaseUsersCache.cs │ │ ├── IDatabase.cs │ │ ├── IUsersCache.cs │ │ ├── IWeatherForecastProvider.cs │ │ ├── IWeatherSummarizer.cs │ │ ├── MockDatabase.cs │ │ ├── User.cs │ │ ├── WeatherForecastProvider.cs │ │ └── WeatherSummarizer.cs │ ├── Startup.cs │ ├── StrongInject.Samples.AspNetCore.csproj │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Console │ ├── App.cs │ ├── Cache.cs │ ├── Config.cs │ ├── Container.cs │ ├── ICommitable.cs │ ├── IConfigLoader.cs │ ├── IConsumer.cs │ ├── IProducer.cs │ ├── JsonConfigLoader.cs │ ├── JsonDeserializer.cs │ ├── JsonSerializer.cs │ ├── KafkaConsumer.cs │ ├── KafkaModule.cs │ ├── KafkaProducer.cs │ ├── Message.cs │ ├── Program.cs │ ├── README.md │ ├── StrongInject.Samples.ConsoleApp.csproj │ ├── User.cs │ ├── config.json │ └── docker-compose.yml ├── Wpf │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Commands │ │ └── RelayCommand.cs │ ├── Container.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Models │ │ ├── IDatabase.cs │ │ ├── MockDatabase.cs │ │ └── User.cs │ ├── README.md │ ├── StrongInject.Samples.Wpf.csproj │ ├── ViewModels │ │ ├── MainWindowViewModel.cs │ │ ├── UserViewModel.cs │ │ └── UsersViewModel.cs │ └── Views │ │ ├── UsersView.xaml │ │ └── UsersView.xaml.cs └── Xamarin │ ├── README.md │ ├── StrongInject.Samples.XamarinApp.Android │ ├── Assets │ │ └── AboutAssets.txt │ ├── MainActivity.cs │ ├── Properties │ │ ├── AndroidManifest.xml │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── AboutResources.txt │ │ ├── Resource.designer.cs │ │ ├── drawable │ │ │ ├── icon_about.png │ │ │ ├── icon_feed.png │ │ │ └── xamarin_logo.png │ │ ├── layout │ │ │ ├── Tabbar.xml │ │ │ └── Toolbar.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── icon.xml │ │ │ └── icon_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ └── StrongInject.Samples.XamarinApp.Android.csproj │ └── StrongInject.Samples.XamarinApp │ ├── App.xaml │ ├── App.xaml.cs │ ├── AppShell.xaml │ ├── AppShell.xaml.cs │ ├── AssemblyInfo.cs │ ├── Container.cs │ ├── GettingStarted.txt │ ├── Models │ └── Item.cs │ ├── Services │ ├── Browser.cs │ ├── IBrowser.cs │ ├── IDataStore.cs │ ├── INavigationService.cs │ ├── MockDataStore.cs │ └── NavigationService.cs │ ├── StrongInject.Samples.XamarinApp.csproj │ ├── ViewModels │ ├── AboutViewModel.cs │ ├── BaseViewModel.cs │ ├── ItemDetailViewModel.cs │ ├── ItemsViewModel.cs │ ├── LoginViewModel.cs │ └── NewItemViewModel.cs │ └── Views │ ├── AboutPage.xaml │ ├── AboutPage.xaml.cs │ ├── IViewOf.cs │ ├── ItemDetailPage.xaml │ ├── ItemDetailPage.xaml.cs │ ├── ItemsPage.xaml │ ├── ItemsPage.xaml.cs │ ├── LoginPage.xaml │ ├── LoginPage.xaml.cs │ ├── NewItemPage.xaml │ └── NewItemPage.xaml.cs ├── StrongInject.Extensions.DependencyInjection.AspNetCore ├── MvcBuilderExtensions.cs └── StrongInject.Extensions.DependencyInjection.AspNetCore.csproj ├── StrongInject.Extensions.DependencyInjection.Tests ├── MvcBuilderExtensionTests.cs ├── ServiceCollectionExtensionTests.cs └── StrongInject.Extensions.DependencyInjection.Tests.csproj ├── StrongInject.Extensions.DependencyInjection ├── ServiceCollectionExtensions.cs └── StrongInject.Extensions.DependencyInjection.csproj ├── StrongInject.Generator.Roslyn38 ├── SourceGenerator.cs └── StrongInject.Generator.Roslyn38.csproj ├── StrongInject.Generator.Roslyn40 ├── IncrementalGenerator.cs └── StrongInject.Generator.Roslyn40.csproj ├── StrongInject.Generator ├── AutoIndenter.cs ├── ContainerGenerator.cs ├── DecoratorOptions.cs ├── DecoratorSource.cs ├── Disposal.cs ├── DisposalLowerer.cs ├── DisposalStyle.cs ├── DisposalStyleDeterminant.cs ├── Extensions.cs ├── FactoryOfMethod.cs ├── GenericDecoratorsResolver.cs ├── GenericRegistrationsResolver.cs ├── GenericResolutionHelpers.cs ├── ImmutableSetInInsertionOrder.cs ├── InstanceSource.cs ├── InstanceSources.cs ├── InstanceSourcesScope.cs ├── IsExternalInit.cs ├── Operation.cs ├── Options.cs ├── RegistrationCalculator.cs ├── RoslynExtensions.cs ├── Scope.cs ├── Statement.cs ├── StrongInject.Generator.csproj ├── Visitors │ ├── BaseVisitor.cs │ ├── DependencyCheckerVisitor.cs │ ├── IVisitor.cs │ ├── LoweringVisitor.cs │ ├── PartialOrderingOfSingleInstanceDependenciesVisitor.cs │ ├── RequiresAsyncVisitor.cs │ ├── RequiresUnsafeVisitor.cs │ ├── SimpleVisitor.cs │ └── SingleInstanceVariablesToCreateEarlyVisitor.cs └── WellKnownTypes.cs ├── StrongInject.Tests.Integration ├── DynamicProxyDecoratorTests.cs ├── IsExternalInit.cs ├── Modules │ ├── CollectionTests.cs │ ├── ImmutableArrayTests.cs │ └── LazyTests.cs ├── StrongInject.Tests.Integration.csproj ├── TestCircularFuncDependencies.cs ├── TestContainerExtensions.cs ├── TestDisposalAfterUsage.cs ├── TestFuncParameterInjection.cs ├── TestFuncScope.cs ├── TestInstancePerDependencyHasCorrectScope.cs ├── TestInstancePerResolutionHasCorrectScope.cs ├── TestNullableConversion.cs ├── TestOwnedInjection.cs ├── TestParallelismAndResolutionFailureBehaviour.cs ├── TestReadmeExamples.cs ├── TestReadmeExamples_AsOfCommit_008a07c7b075e72d491a9144e2f4233aff5a7b7b.cs ├── TestSingleInstanceHasCorrectScope.cs ├── TestSingleInstanceNull.cs └── TestSingleInstanceStruct.cs ├── StrongInject.Tests.Unit ├── AssertionExtensions.cs ├── DependencyCheckerTests.cs ├── DiagnosticVerifier.cs ├── GeneratorTests.cs ├── OwnedInjectionTests.cs ├── OwnedTests.cs ├── RoslynExtensions.cs ├── StrongInject.Tests.Unit.csproj ├── TestBase.cs └── WellKnownTypesTests.cs ├── StrongInject.sln ├── StrongInject ├── DecoratorFactoryAttribute.cs ├── DecoratorOptions.cs ├── DummyParameter.cs ├── FactoryAttribute.cs ├── FactoryOfAttribute.cs ├── Helpers.cs ├── IContainer.cs ├── IFactory.cs ├── IRequiresInitialization.cs ├── InstanceAttribute.cs ├── Modules │ ├── CollectionsModule.cs │ ├── LazyModule.cs │ ├── SafeImmutableArrayModule.cs │ ├── StandardModule.cs │ ├── UnsafeImmutableArrayModule.cs │ └── ValueTupleModule.cs ├── ObsoleteTypes │ ├── ContainerExtensions.cs │ ├── FactoryRegistrationAttribute.cs │ ├── ModuleRegistrationAtribute.cs │ └── RegistrationAttribute.cs ├── Options.cs ├── Owned.cs ├── RegisterAttribute.cs ├── RegisterDecoratorAttribute.cs ├── RegisterFactoryAttribute.cs ├── RegisterModuleAttribute.cs ├── Scope.cs ├── StrongInject.csproj ├── StrongInjectContainerExtensions.cs ├── StrongInjectException.cs ├── ValueTaskExtensions.cs └── buildTransitive │ └── StrongInject.targets ├── Utilities └── GeneratorTestsUpdater │ ├── GeneratorTestsUpdater.csproj │ └── Program.cs ├── docs ├── Containers.md ├── DesignDecisions.md ├── Home.md ├── Registration.md ├── Registration │ ├── BestRegistration.md │ ├── DecoratorFactoryRegistration.md │ ├── DecoratorTypeRegistration.md │ ├── Decorators.md │ ├── FactoryMethodRegistration.md │ ├── FactoryTypeRegistration.md │ ├── InstanceRegistration.md │ ├── ModuleRegistration.md │ ├── RegistrationOptions.md │ ├── Scopes.md │ └── TypeRegistration.md └── Resolution.md └── resources ├── logo-circle.ico ├── logo-circle.png ├── logo-circle.svg ├── logo-horizontal.ico ├── logo-horizontal.png └── logo-horizontal.svg /.github/workflows/toc.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: TOC Generator 6 | jobs: 7 | generateTOC: 8 | name: TOC Generator 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: technote-space/toc-generator@v2 12 | with: 13 | TOC_TITLE: | 14 | ## Table Of Contents 15 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 16 | TARGET_PATHS: README.md, docs/**.md, docs/*/**.md 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/wiki.yml: -------------------------------------------------------------------------------- 1 | name: Wiki 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v1 13 | # Additional steps to generate documentation in "Documentation" directory 14 | - name: Upload Documentation to Wiki 15 | uses: SwiftDocOrg/github-wiki-publish-action@rsync 16 | with: 17 | path: "docs/" 18 | env: 19 | GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 20 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | preview 4 | enable 5 | true 6 | CS1591;RS1024 7 | AD0001 8 | true 9 | AllEnabledByDefault 10 | preview 11 | true 12 | 1.4.5 13 | *-* 14 | 15 | 16 | 17 | StrongInject 18 | StrongInject 19 | MIT 20 | https://github.com/YairHalberstadt/stronginject 21 | https://github.com/YairHalberstadt/stronginject 22 | true 23 | true 24 | snupkg 25 | logo-circle.png 26 | true 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yair Halberstadt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Container.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using StrongInject.Samples.AspNetCore.Controllers; 4 | using StrongInject.Samples.AspNetCore.Services; 5 | using System; 6 | 7 | namespace StrongInject.Samples.AspNetCore 8 | { 9 | [Register(typeof(WeatherForecastController), Scope.InstancePerResolution)] 10 | [Register(typeof(WeatherForecastProvider), Scope.InstancePerDependency, typeof(IWeatherForecastProvider))] 11 | [Register(typeof(WeatherSummarizer), Scope.SingleInstance, typeof(IWeatherSummarizer))] 12 | [Register(typeof(UsersController), Scope.InstancePerResolution)] 13 | [Register(typeof(DatabaseUsersCache), Scope.SingleInstance, typeof(IUsersCache))] 14 | [Register(typeof(MockDatabase), Scope.SingleInstance, typeof(IDatabase))] 15 | [RegisterDecorator(typeof(DatabaseDecorator), typeof(IDatabase))] 16 | public partial class Container : IContainer, IContainer 17 | { 18 | private readonly IServiceProvider _serviceProvider; 19 | 20 | public Container(IServiceProvider serviceProvider) 21 | { 22 | _serviceProvider = serviceProvider; 23 | } 24 | 25 | [Factory] private ILogger GetLogger() => _serviceProvider.GetRequiredService>(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using StrongInject.Samples.AspNetCore.Services; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace StrongInject.Samples.AspNetCore.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class UsersController : ControllerBase 12 | { 13 | private readonly IUsersCache _usersCache; 14 | private readonly ILogger _logger; 15 | 16 | public UsersController(IUsersCache usersCache, ILogger logger) 17 | { 18 | _usersCache = usersCache; 19 | _logger = logger; 20 | } 21 | 22 | [HttpGet] 23 | public ValueTask> Get() 24 | { 25 | _logger.LogInformation($"Requesting users"); 26 | return _usersCache.GetUsersList(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using StrongInject.Samples.AspNetCore.Services; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace StrongInject.Samples.AspNetCore.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private readonly Func _weatherForecastProvider; 15 | private readonly ILogger _logger; 16 | 17 | public WeatherForecastController(Func weatherForecastProvider, ILogger logger) 18 | { 19 | _weatherForecastProvider = weatherForecastProvider; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | public IEnumerable Get(string location) 25 | { 26 | _logger.LogInformation($"Requesting weather forecasts at {location}"); 27 | var rng = new Random(); 28 | return Enumerable.Range(1, 5) 29 | .Select(index => _weatherForecastProvider(location).GetForecast(DateTime.Now.AddDays(index))) 30 | .ToArray(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace StrongInject.Samples.AspNetCore 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:50527", 8 | "sslPort": 44383 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast?location=London", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "WebApplication2": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "weatherforecast?location=London", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Samples/AspNetCore/README.md: -------------------------------------------------------------------------------- 1 | # StrongInject.Samples.AspNetCore 2 | 3 | ## Overview 4 | 5 | This sample demonstrates a simple rest API with two controllers. Both controllers are instantiated using StrongInject. 6 | 7 | The weather forecast controller is reached at https://localhost:44383/weatherforecast?location= and returns a (randomly generated) weather forecast for that location. 8 | 9 | The users controller is reached at https://localhost:44383/users and returns a list of users. 10 | 11 | ## Learning Points 12 | 13 | This app demonstrates how to integrate StrongInject with ASP.NET Core using the StrongInject.Extensions.DependencyInjection.AspNetCore package. 14 | 15 | It also demonstrates a number of other key features of StrongInject: 16 | 17 | 1. Usages of Scopes to control lifetimes. For example Controllers should be `InstancePerDependency` whilst caches should be `SingleInstance`. 18 | 2. Passing `IServiceProvider` as a parameter to the container, and using it to resolve `ILogger`, allowing for two way integration with other IOC containers. 19 | 3. Whilst `StrongInject` supports async resolution, Microsoft.Extensions.DependencyInjection does not. `DatabaseUsersCache` can only be prepared asynchronously so a different technique is used where requests on it become asynchronous instead. 20 | 4. Usage of a generic factory method to register `ILogger` for all `T` at once. 21 | 22 | ## Notes 23 | 24 | If you intend to use StrongInject to resolve all your controllers, then call `services.AddControllers().ResolveControllersThroughServiceProvider()` in `StartUp`. This tells ASP.NET Core to resolve all controllers through the service provider, but doesn't auto register them with the service provider. You can then register them yourselves manually. 25 | 26 | If however you want to auto register all controllers with the service provider, and only override some of them with StrongInject, then you will need to call `services.AddControllers().AddControllersAsServices()` instead. This not only tells ASP.NET Core to resolve all controllers through the service provider, but also auto registers them as well. You will then need to make sure you remove these auto registrations when you manually overwrite the default registration by calling `services.ReplaceWithTransientServiceUsingContainer()` (or any of the related `ReplaceWithXServiceUsingContainer` methods). 27 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/DatabaseDecorator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace StrongInject.Samples.AspNetCore.Services 8 | { 9 | public class DatabaseDecorator : IDatabase 10 | { 11 | private readonly IDatabase _underlying; 12 | private readonly ILogger _logger; 13 | 14 | public DatabaseDecorator(IDatabase underlying, ILogger logger) 15 | { 16 | _underlying = underlying; 17 | _logger = logger; 18 | } 19 | 20 | public async Task> Get() 21 | { 22 | _logger.LogInformation($"Requesting {typeof(T).Name}s from database..."); 23 | 24 | try 25 | { 26 | var results = await _underlying.Get(); 27 | _logger.LogInformation($"Successfully recieved {results.Count()} {typeof(T).Name}s from database"); 28 | return results; 29 | } 30 | catch (Exception e) 31 | { 32 | _logger.LogError($"Error when requesting {typeof(T).Name}s from database: {e}"); 33 | throw; 34 | } 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/DatabaseUsersCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace StrongInject.Samples.AspNetCore.Services 7 | { 8 | public class DatabaseUsersCache : IUsersCache, IDisposable 9 | { 10 | private Timer? _timer; 11 | 12 | private ValueTask> _users; 13 | 14 | public DatabaseUsersCache(IDatabase database) 15 | { 16 | _users = new ValueTask>(database.Get()); 17 | _timer = new Timer(async _ => 18 | { 19 | var users = await database.Get(); 20 | _users = new ValueTask>(users); 21 | }, null, 60000, 60000); 22 | } 23 | 24 | public void Dispose() 25 | { 26 | _timer?.Dispose(); 27 | _timer = null; 28 | 29 | } 30 | 31 | ValueTask> IUsersCache.GetUsersList() 32 | { 33 | return _users; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace StrongInject.Samples.AspNetCore.Services 5 | { 6 | public interface IDatabase 7 | { 8 | public Task> Get(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/IUsersCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace StrongInject.Samples.AspNetCore.Services 5 | { 6 | public interface IUsersCache 7 | { 8 | public ValueTask> GetUsersList(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/IWeatherForecastProvider.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.AspNetCore; 2 | using System; 3 | 4 | namespace StrongInject.Samples.AspNetCore.Services 5 | { 6 | public interface IWeatherForecastProvider 7 | { 8 | WeatherForecast GetForecast(DateTime day); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/IWeatherSummarizer.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Samples.AspNetCore.Services 2 | { 3 | public interface IWeatherSummarizer 4 | { 5 | string Summarize(int temperatureC); 6 | } 7 | } -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/MockDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace StrongInject.Samples.AspNetCore.Services 7 | { 8 | public class MockDatabase : IDatabase 9 | { 10 | public async Task> Get() 11 | { 12 | await Task.Yield(); 13 | 14 | if (typeof(T) == typeof(User)) 15 | { 16 | return new[] { 17 | new User("Rebekah Riley", DateTime.Parse("1/2/1996")), 18 | new User("Gene Simons", DateTime.Parse("3/4/1978")), 19 | new User("Nabilah Downs", DateTime.Parse("5/6/2002")) 20 | }.Cast(); 21 | } 22 | 23 | return Array.Empty(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Samples.AspNetCore.Services 4 | { 5 | public class User 6 | { 7 | public User(string name, DateTime dateOfBirth) 8 | { 9 | Name = name; 10 | DateOfBirth = dateOfBirth; 11 | } 12 | 13 | public string Name { get; } 14 | 15 | public DateTime DateOfBirth { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/WeatherForecastProvider.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.AspNetCore; 2 | using System; 3 | 4 | namespace StrongInject.Samples.AspNetCore.Services 5 | { 6 | public class WeatherForecastProvider : IWeatherForecastProvider 7 | { 8 | public WeatherForecastProvider(string location, IWeatherSummarizer weatherSummarizer) 9 | { 10 | _location = location; 11 | _weatherSummarizer = weatherSummarizer; 12 | } 13 | 14 | private readonly Random _random = new Random(); 15 | private readonly string _location; 16 | private readonly IWeatherSummarizer _weatherSummarizer; 17 | 18 | public WeatherForecast GetForecast(DateTime day) 19 | { 20 | var temperatureC = _random.Next(-20, 55); 21 | return new WeatherForecast 22 | { 23 | Location = _location, 24 | Date = day, 25 | TemperatureC = temperatureC, 26 | Summary = _weatherSummarizer.Summarize(temperatureC), 27 | }; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Services/WeatherSummarizer.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Samples.AspNetCore.Services 2 | { 3 | public class WeatherSummarizer : IWeatherSummarizer 4 | { 5 | public string Summarize(int temperatureC) 6 | { 7 | return temperatureC switch 8 | { 9 | < 0 => "Freezing", 10 | < 5 => "Bracing", 11 | < 10 => "Chilly", 12 | < 15 => "Cool", 13 | < 20 => "Mild", 14 | < 25 => "Warm", 15 | < 30 => "Balmy", 16 | < 35 => "Hot", 17 | < 40 => "Sweltering", 18 | _ => "Scorching", 19 | }; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Samples/AspNetCore/Startup.cs: -------------------------------------------------------------------------------- 1 | #define DontAutoRegisterControllers 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using StrongInject.Extensions.DependencyInjection; 9 | using StrongInject.Extensions.DependencyInjection.AspNetCore; 10 | using StrongInject.Samples.AspNetCore.Controllers; 11 | 12 | namespace StrongInject.Samples.AspNetCore 13 | { 14 | public class Startup 15 | { 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public IConfiguration Configuration { get; } 22 | 23 | // This method gets called by the runtime. Use this method to add services to the container. 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | #if DontAutoRegisterControllers 27 | services.AddControllers().ResolveControllersThroughServiceProvider(); // Tells ASP.NET to resolve controllers through the ServiceProvider, rather than calling their constructors directly. 28 | services.AddTransientServiceUsingContainer(); // register the controller with the ServiceProvider. 29 | services.AddTransientServiceUsingContainer(); 30 | #else 31 | services.AddControllers().AddControllersAsServices(); // Tells ASP.NET to resolve controllers through the ServiceProvider, rather than calling their constructors directly, and then auto registers all controllers with the service provider. 32 | services.ReplaceWithTransientServiceUsingContainer(); // register the controller with the ServiceProvider, and remove the existing registration that was added automatically by AddControllersAsServices(). 33 | services.ReplaceWithTransientServiceUsingContainer(); 34 | #endif 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseHttpsRedirection(); 46 | 47 | app.UseRouting(); 48 | 49 | app.UseAuthorization(); 50 | 51 | app.UseEndpoints(endpoints => 52 | { 53 | endpoints.MapControllers(); 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Samples/AspNetCore/StrongInject.Samples.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Debug;Release;Wpf;All 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Samples/AspNetCore/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Samples.AspNetCore 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string? Summary { get; set; } 14 | 15 | public string? Location { get; internal set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Samples/AspNetCore/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/AspNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /Samples/Console/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace StrongInject.Samples.ConsoleApp 5 | { 6 | public class App 7 | { 8 | private readonly IConsumer _consumer; 9 | private readonly Cache> _producerCache; 10 | private readonly string _targetTopicPrefix; 11 | 12 | public App(IConsumer consumer, Cache> producerCache, string targetTopicPrefix) 13 | { 14 | _consumer = consumer; 15 | _producerCache = producerCache; 16 | _targetTopicPrefix = targetTopicPrefix; 17 | } 18 | 19 | public async Task FanMessagesToRecipients() 20 | { 21 | await foreach (var commitableMessage in _consumer.Consume()) 22 | { 23 | var (key, value) = (commitableMessage.Key, commitableMessage.Value); 24 | Console.WriteLine($"Forwarding message from {key.Name} to {value.Recipient.Name}"); 25 | await _producerCache.Get(_targetTopicPrefix + value.Recipient.Id).Produce(key, value); 26 | commitableMessage.Commit(); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Samples/Console/Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace StrongInject.Samples.ConsoleApp 5 | { 6 | public class Cache where TKey : notnull 7 | { 8 | private readonly Func _factory; 9 | 10 | public Cache(Func factory) 11 | { 12 | _factory = factory; 13 | } 14 | 15 | private readonly ConcurrentDictionary _cached = new(); 16 | public TValue Get(TKey key) => _cached.GetOrAdd(key, _factory); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Samples/Console/Config.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Samples.ConsoleApp 2 | { 3 | public class Config 4 | { 5 | public string BootstrapServers { get; init; } = ""; 6 | public string GroupId { get; init; } = ""; 7 | public string ConsumedTopic { get; init; } = ""; 8 | public string TargetTopicPrefix { get; init; } = ""; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/Console/Container.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace StrongInject.Samples.ConsoleApp 5 | { 6 | 7 | [RegisterModule(typeof(KafkaModule))] 8 | [Register(typeof(JsonConfigLoader), Scope.SingleInstance, typeof(IConfigLoader))] 9 | public partial class Container : IAsyncContainer 10 | { 11 | [Factory] private App CreateApp(IConsumer consumer, Cache> producerCache, Config config) 12 | => new App(consumer, producerCache, config.TargetTopicPrefix); 13 | 14 | [Factory(Scope.SingleInstance)] ValueTask CreateConfig(IConfigLoader configLoader) => configLoader.LoadConfig(); 15 | 16 | [Factory] Cache CreateCache(Func factory) where TKey : notnull => new Cache(factory); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Samples/Console/ICommitable.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Samples.ConsoleApp 2 | { 3 | public interface ICommitable 4 | { 5 | TKey Key { get; } 6 | TValue Value { get; } 7 | void Commit(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/Console/IConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace StrongInject.Samples.ConsoleApp 4 | { 5 | public interface IConfigLoader 6 | { 7 | ValueTask LoadConfig(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/Console/IConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StrongInject.Samples.ConsoleApp 4 | { 5 | public interface IConsumer 6 | { 7 | IAsyncEnumerable> Consume(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/Console/IProducer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace StrongInject.Samples.ConsoleApp 4 | { 5 | public interface IProducer 6 | { 7 | public Task Produce(TKey key, TValue value); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/Console/JsonConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject.Samples.ConsoleApp 6 | { 7 | public class JsonConfigLoader : IConfigLoader 8 | { 9 | public async ValueTask LoadConfig() 10 | { 11 | await using (var fileStream = File.OpenRead("config.json")) 12 | { 13 | return await JsonSerializer.DeserializeAsync(fileStream) ?? throw new JsonException("Invalid Config"); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Samples/Console/JsonDeserializer.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | using System; 3 | using System.Text.Json; 4 | 5 | namespace StrongInject.Samples.ConsoleApp 6 | { 7 | public class JsonDeserializer : IDeserializer 8 | { 9 | public T? Deserialize(ReadOnlySpan data, bool isNull, SerializationContext context) 10 | { 11 | if (isNull) 12 | return default; 13 | return JsonSerializer.Deserialize(data); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Samples/Console/JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | using System; 3 | using System.Text.Json; 4 | 5 | namespace StrongInject.Samples.ConsoleApp 6 | { 7 | public class JsonSerializer : ISerializer 8 | { 9 | public byte[] Serialize(T? data, SerializationContext context) 10 | { 11 | return data is null ? Array.Empty() : JsonSerializer.SerializeToUtf8Bytes(data); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Samples/Console/KafkaConsumer.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject.Samples.ConsoleApp 6 | { 7 | public class KafkaConsumer : IConsumer 8 | { 9 | private readonly Confluent.Kafka.IConsumer _consumer; 10 | private readonly string _topic; 11 | 12 | public KafkaConsumer(Confluent.Kafka.IConsumer consumer, string topic) 13 | { 14 | _consumer = consumer; 15 | _topic = topic; 16 | } 17 | 18 | public async IAsyncEnumerable> Consume() 19 | { 20 | _consumer.Subscribe(_topic); 21 | 22 | while (true) 23 | { 24 | var result = _consumer.Consume(); 25 | if (result.IsPartitionEOF) 26 | { 27 | await Task.Delay(1000); 28 | } 29 | else 30 | { 31 | yield return new Commitable(result, _consumer); 32 | } 33 | } 34 | } 35 | 36 | private class Commitable : ICommitable 37 | { 38 | private readonly Confluent.Kafka.IConsumer _consumer; 39 | private readonly ConsumeResult _consumeResult; 40 | 41 | public Commitable(ConsumeResult consumeResult, Confluent.Kafka.IConsumer consumer) 42 | { 43 | _consumeResult = consumeResult; 44 | _consumer = consumer; 45 | } 46 | 47 | public TKey Key => _consumeResult.Message.Key; 48 | 49 | public TValue Value => _consumeResult.Message.Value; 50 | 51 | public void Commit() 52 | { 53 | _consumer.Commit(_consumeResult); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Samples/Console/KafkaModule.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace StrongInject.Samples.ConsoleApp 4 | { 5 | public class KafkaModule 6 | { 7 | [Factory(Scope.SingleInstance)] public static ISerializer CreateSerializer() => new JsonSerializer(); 8 | [Factory(Scope.SingleInstance)] public static IDeserializer CreateDeserializer() => new JsonDeserializer(); 9 | [Factory] public static Confluent.Kafka.IConsumer CreateConfluentConsumer(IDeserializer keyDeserializer, IDeserializer valueDeserializer, Config config) 10 | { 11 | return new ConsumerBuilder(new ConsumerConfig() { BootstrapServers = config.BootstrapServers, GroupId = config.GroupId }) 12 | .SetKeyDeserializer(keyDeserializer) 13 | .SetValueDeserializer(valueDeserializer) 14 | .Build(); 15 | } 16 | [Factory] public static Confluent.Kafka.IProducer CreateConfluentProducer(ISerializer keySerializer, ISerializer valueSerializer, Config config) 17 | { 18 | return new ProducerBuilder(new ProducerConfig() { BootstrapServers = config.BootstrapServers }) 19 | .SetKeySerializer(keySerializer) 20 | .SetValueSerializer(valueSerializer) 21 | .Build(); 22 | } 23 | 24 | [Factory] public static IConsumer CreateConsumer(Confluent.Kafka.IConsumer consumer, Config config) => new KafkaConsumer(consumer, config.ConsumedTopic); 25 | [Factory] public static IProducer CreateProducer(Confluent.Kafka.IProducer producer, string topic) => new KafkaProducer(producer, topic); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Samples/Console/KafkaProducer.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | using System.Threading.Tasks; 3 | 4 | namespace StrongInject.Samples.ConsoleApp 5 | { 6 | public class KafkaProducer : IProducer 7 | { 8 | private readonly Confluent.Kafka.IProducer _producer; 9 | private readonly string _topic; 10 | 11 | public KafkaProducer(Confluent.Kafka.IProducer producer, string topic) 12 | { 13 | _producer = producer; 14 | _topic = topic; 15 | } 16 | 17 | public Task Produce(TKey key, TValue value) 18 | { 19 | return _producer.ProduceAsync(_topic, new Message() { Key = key, Value = value }); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Samples/Console/Message.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Samples.ConsoleApp 2 | { 3 | public record Message 4 | { 5 | public string Content { get; init; } = null!; 6 | public User Recipient { get; init; } = null!; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Samples/Console/README.md: -------------------------------------------------------------------------------- 1 | # StrongInject.Samples.ConsoleApp 2 | 3 | ## Overview 4 | 5 | This sample demonstrates a console application that might be part of a messaging application. 6 | 7 | It reads messages from a kafka topic ("all_messages") then forwards the message to the recipients inbox (the "inbox_[user-id]" kafka topic). 8 | 9 | ## Requirements 10 | 11 | Docker 12 | 13 | ## Learning Points 14 | 15 | This app demonstrates a number of key features and techniques using StrongInject: 16 | 17 | 1. Factories are used extensively, to facilitate passing a config around, and to enable registering a lot of generic types. 18 | 2. Loading a config at runtime, in a way that makes it easy to swap out how the config is loaded. 19 | 3. Using async resolution (in this case to asynchronously load a config file from disk). 20 | 4. Usage of a module to separate out groups of registrations that conceptually belong together. 21 | 22 | ## Notes 23 | 24 | Program.cs has a lot of code to start some docker containers, create kafka topics, and produce messages to those topics. You can ignore most of this code. 25 | -------------------------------------------------------------------------------- /Samples/Console/StrongInject.Samples.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | Debug;Release;Wpf;All 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Samples/Console/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Samples.ConsoleApp 4 | { 5 | public record User 6 | { 7 | public Guid Id { get; init; } 8 | public string Name { get; init; } = null!; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/Console/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "BootstrapServers": "localhost:29092", 3 | "GroupId": "StrongInJectKafkaConsumer", 4 | "TargetTopicPrefix": "inbox_", 5 | "ConsumedTopic": "all_messages" 6 | } -------------------------------------------------------------------------------- /Samples/Console/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '2' 3 | 4 | services: 5 | zookeeper: 6 | image: confluentinc/cp-zookeeper 7 | hostname: zookeeper 8 | container_name: zookeeper 9 | ports: 10 | - "2181:2181" 11 | environment: 12 | ZOOKEEPER_CLIENT_PORT: 2181 13 | ZOOKEEPER_TICK_TIME: 2000 14 | 15 | broker: 16 | image: confluentinc/cp-kafka 17 | hostname: broker 18 | container_name: broker 19 | depends_on: 20 | - zookeeper 21 | ports: 22 | - "29092:29092" 23 | environment: 24 | KAFKA_BROKER_ID: 1 25 | KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' 26 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 27 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092,PLAINTEXT_HOST://localhost:29092 28 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 29 | KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 30 | KAFKA_TOOLS_LOG4J_LOGLEVEL: ERROR -------------------------------------------------------------------------------- /Samples/Wpf/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Samples/Wpf/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace StrongInject.Samples.Wpf 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | private async void Application_Startup(object sender, StartupEventArgs e) 11 | { 12 | var container = new Container(); 13 | MainWindow = (await container.ResolveAsync()).Value; 14 | 15 | // We never dispose of the container because its lifetime is the lifetime of the app. 16 | // Whilst we could hook into the Application Exit event, this will not be called if the app crashes or is forcefully terminated. 17 | // Therefore best practice is simply to make sure we don't rely on the container being disposed. 18 | // This will usually be the case as terminating the application frees up most resources. 19 | 20 | MainWindow.Show(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Samples/Wpf/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /Samples/Wpf/Commands/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Input; 7 | 8 | namespace StrongInject.Samples.Wpf.Commands 9 | { 10 | public class RelayCommand : ICommand 11 | { 12 | private readonly Action _command; 13 | private readonly Predicate? _canExecute; 14 | 15 | public event EventHandler? CanExecuteChanged 16 | { 17 | add { CommandManager.RequerySuggested += value; } 18 | remove { CommandManager.RequerySuggested -= value; } 19 | } 20 | 21 | public RelayCommand(Action command) : this(command, null) 22 | { 23 | } 24 | 25 | public RelayCommand(Action command, Predicate? canExecute) 26 | { 27 | _command = command ?? throw new ArgumentNullException(nameof(command)); 28 | _canExecute = canExecute; 29 | } 30 | 31 | public bool CanExecute(object? parameter) 32 | { 33 | return _canExecute?.Invoke(parameter) ?? true; 34 | } 35 | 36 | public void Execute(object? parameter) 37 | { 38 | _command.Invoke(parameter); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Samples/Wpf/Container.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.Wpf.Models; 2 | using StrongInject.Samples.Wpf.ViewModels; 3 | 4 | namespace StrongInject.Samples.Wpf 5 | { 6 | [Register(typeof(MainWindow))] 7 | [Register(typeof(MainWindowViewModel))] 8 | [Register(typeof(UsersViewModel))] 9 | [Register(typeof(UserViewModel), Scope.InstancePerDependency)] 10 | [Register(typeof(MockDatabase), Scope.SingleInstance, typeof(IDatabase))] 11 | public partial class Container : IAsyncContainer 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Samples/Wpf/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Samples/Wpf/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.Wpf.ViewModels; 2 | using System.Windows; 3 | 4 | namespace StrongInject.Samples.Wpf 5 | { 6 | /// 7 | /// Interaction logic for MainWindow.xaml 8 | /// 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow(MainWindowViewModel mainWindowViewModel) 12 | { 13 | MainWindowViewModel = mainWindowViewModel; 14 | InitializeComponent(); 15 | } 16 | 17 | public MainWindowViewModel MainWindowViewModel { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Samples/Wpf/Models/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject.Samples.Wpf.Models 6 | { 7 | public interface IDatabase 8 | { 9 | Task> GetUsers(); 10 | Task DeleteUser(Guid userId); 11 | Task AddOrUpdateUser(User user); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Samples/Wpf/Models/MockDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Documents; 9 | 10 | namespace StrongInject.Samples.Wpf.Models 11 | { 12 | public class MockDatabase : IDatabase 13 | { 14 | private readonly ConcurrentDictionary _users = new ConcurrentDictionary(new [] 15 | { 16 | User.CreateNew("Erik", "Hawkins"), 17 | User.CreateNew("Martine", "Guevara"), 18 | User.CreateNew("Keith", "Lin"), 19 | User.CreateNew("Jenson", "Fernandez"), 20 | User.CreateNew("Lynden", "Lancaster"), 21 | User.CreateNew("Shahid", "Fraser"), 22 | User.CreateNew("Blair", "Day"), 23 | User.CreateNew("Sofie", "Workman"), 24 | User.CreateNew("Abi", "Galloway"), 25 | User.CreateNew("Kean", "Rutledge"), 26 | }.Select(x => KeyValuePair.Create(x.Id, x))); 27 | 28 | public async Task AddOrUpdateUser(User user) 29 | { 30 | await Task.Yield(); 31 | _users.AddOrUpdate(user.Id, user, (_, _) => user); 32 | } 33 | 34 | public async Task DeleteUser(Guid userId) 35 | { 36 | await Task.Yield(); 37 | return _users.TryRemove(userId, out _); 38 | } 39 | 40 | public async Task> GetUsers() 41 | { 42 | await Task.Yield(); 43 | return _users.Values; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Samples/Wpf/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Samples.Wpf.Models 4 | { 5 | public record User(Guid Id, string FirstName, string LastName) 6 | { 7 | public static User CreateNew(string FirstName, string LastName) => new User(Guid.NewGuid(), FirstName, LastName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/Wpf/README.md: -------------------------------------------------------------------------------- 1 | # StrongInject.Samples.Wpf 2 | 3 | ## Overview 4 | 5 | This sample uses StrongInject to instantiate a simple WPF application. 6 | 7 | It consists of a window showing a list of users which can be added to, edited, or deleted. 8 | 9 | ## Debugging 10 | 11 | The WPF app is not built by the Debug/Release configurations as it can only be built on windows. To debug the app change the configuration to WPF, and then run/debug as normal. 12 | 13 | ## Dependency Injection in WPF 14 | 15 | WPF classically expects user controls and view models to have a parameterless constructor, and then use a service locator instead of DI to access needed services. This is rightly [considered an anti pattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). 16 | 17 | Instead we instantiate a tree of view models using StrongInject. Each ViewModel accepts any child ViewModels directly in its constructor, or via a `Func`. Every `ViewModel` exposes the child ViewModels as properties. 18 | 19 | We resolve the `MainWindow` directly, and the `MainWindow` sets the `MainWindowViewModel` as its `DataContext` in its constructor. 20 | 21 | All UserControls use Data Binding to bind the `DataContext` of any child User Controls to the relevant ChildViewModel property on the ViewModel. 22 | 23 | ## Learning Points 24 | 25 | This app uses a relatively simple and straightforward StrongInject container. It's main learning points are how to use Dependency Injection in WPF to create all the ViewModels and services, and then bind them to the UserControls. 26 | 27 | Take a look at all the ViewModels and their constructors, and then take a look at how the DataContext is set for each Window/UserControl. 28 | -------------------------------------------------------------------------------- /Samples/Wpf/StrongInject.Samples.Wpf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | false 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Samples/Wpf/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace StrongInject.Samples.Wpf.ViewModels 8 | { 9 | public class MainWindowViewModel 10 | { 11 | public MainWindowViewModel(UsersViewModel usersViewModel) 12 | { 13 | UsersViewModel = usersViewModel; 14 | } 15 | 16 | public UsersViewModel UsersViewModel { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Samples/Wpf/ViewModels/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.Wpf.Models; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Threading.Tasks; 5 | 6 | namespace StrongInject.Samples.Wpf.ViewModels 7 | { 8 | public class UserViewModel : INotifyPropertyChanged 9 | { 10 | public UserViewModel(User user, IDatabase database) 11 | { 12 | _firstName = user.FirstName; 13 | _lastName = user.LastName; 14 | _id = user.Id; 15 | _database = database; 16 | } 17 | 18 | public bool SaveChanges { get; set; } = true; 19 | 20 | private string _firstName; 21 | private string _lastName; 22 | private Guid _id; 23 | private readonly IDatabase _database; 24 | 25 | public event PropertyChangedEventHandler? PropertyChanged; 26 | 27 | public string FirstName 28 | { 29 | get => _firstName; 30 | set 31 | { 32 | if (_firstName == value) 33 | { 34 | return; 35 | } 36 | _firstName = value; 37 | RaisePropertyChanged(nameof(FirstName)); 38 | RaisePropertyChanged(nameof(FullName)); 39 | } 40 | } 41 | 42 | public string LastName 43 | { 44 | get => _lastName; 45 | set 46 | { 47 | if (_lastName == value) 48 | { 49 | return; 50 | } 51 | _lastName = value; 52 | RaisePropertyChanged(nameof(LastName)); 53 | RaisePropertyChanged(nameof(FullName)); 54 | } 55 | } 56 | 57 | public string FullName => string.Concat(_firstName, " ", _lastName); 58 | 59 | private void RaisePropertyChanged(string propertyName) 60 | { 61 | if (SaveChanges) 62 | { 63 | _ = Save(); 64 | } 65 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 66 | } 67 | 68 | public Task Delete() => _database.DeleteUser(_id); 69 | public Task Save() => _database.AddOrUpdateUser(new User(_id, FirstName, LastName)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Samples/Wpf/ViewModels/UsersViewModel.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.Wpf.Commands; 2 | using StrongInject.Samples.Wpf.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.ComponentModel; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace StrongInject.Samples.Wpf.ViewModels 11 | { 12 | public class UsersViewModel : IRequiresAsyncInitialization, INotifyPropertyChanged 13 | { 14 | private readonly IDatabase _database; 15 | private readonly Func _createUserViewModel; 16 | private UserViewModel _userToAdd; 17 | 18 | public event PropertyChangedEventHandler? PropertyChanged; 19 | 20 | public ObservableCollection Users { get; set; } = null!; 21 | public RelayCommand DeleteCommand { get; set; } 22 | public RelayCommand AddCommand { get; set; } 23 | public UserViewModel? SelectedUser { get; set; } 24 | public UserViewModel UserToAdd 25 | { 26 | get => _userToAdd; 27 | private set 28 | { 29 | _userToAdd = value; 30 | RaisePropertyChanged(nameof(UserToAdd)); 31 | } 32 | } 33 | 34 | public UsersViewModel(IDatabase database, Func createUserViewModel) 35 | { 36 | DeleteCommand = new RelayCommand(o => OnDelete(SelectedUser)); 37 | AddCommand = new RelayCommand(OnAdd); 38 | _database = database; 39 | _createUserViewModel = createUserViewModel; 40 | _userToAdd = CreateNewUser(); 41 | } 42 | 43 | private void OnDelete(UserViewModel? user) 44 | { 45 | if (user is not null) 46 | { 47 | Users.Remove(user); 48 | user.Delete(); 49 | } 50 | } 51 | 52 | private void OnAdd(object? obj) 53 | { 54 | _ = UserToAdd.Save(); 55 | Users.Add(UserToAdd); 56 | UserToAdd = CreateNewUser(); 57 | } 58 | 59 | private UserViewModel CreateNewUser() 60 | { 61 | var newUser = _createUserViewModel(User.CreateNew("", "")); 62 | newUser.SaveChanges = false; 63 | return newUser; 64 | } 65 | 66 | public async ValueTask InitializeAsync() 67 | { 68 | var users = await _database.GetUsers(); 69 | Users = new ObservableCollection(users.Select(_createUserViewModel)); 70 | } 71 | 72 | private void RaisePropertyChanged(string propertyName) 73 | { 74 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Samples/Wpf/Views/UsersView.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Samples/Xamarin/StrongInject.Samples.XamarinApp/Views/NewItemPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Samples.XamarinApp.Models; 2 | using StrongInject.Samples.XamarinApp.ViewModels; 3 | using Xamarin.Forms; 4 | 5 | namespace StrongInject.Samples.XamarinApp.Views 6 | { 7 | public partial class NewItemPage : ContentPage, IViewOf 8 | { 9 | public Item? Item { get; set; } 10 | 11 | public NewItemPage(NewItemViewModel newItemViewModel) 12 | { 13 | InitializeComponent(); 14 | BindingContext = newItemViewModel; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /StrongInject.Extensions.DependencyInjection.AspNetCore/MvcBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Controllers; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace StrongInject.Extensions.DependencyInjection.AspNetCore 6 | { 7 | public static class MvcBuilderExtensions 8 | { 9 | /// 10 | /// Use the ServiceProvider to resolve controllers. Will error if the controller isn't explicitly registered 11 | /// 12 | /// The . 13 | /// The . 14 | public static IMvcBuilder ResolveControllersThroughServiceProvider(this IMvcBuilder builder) 15 | { 16 | builder.PartManager.PopulateFeature(new ControllerFeature()); 17 | builder.Services.Replace(ServiceDescriptor.Transient()); 18 | return builder; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StrongInject.Extensions.DependencyInjection.AspNetCore/StrongInject.Extensions.DependencyInjection.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | True 6 | Debug;Release;Wpf;All 7 | 8 | 9 | 10 | $(StrongInjectVersion) 11 | StrongInject.Extensions.DependencyInjection.AspNetCore 12 | Provides integration between Microsoft.Extensions.DependencyInjection and StrongInject for AspNetCore 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /StrongInject.Extensions.DependencyInjection.Tests/StrongInject.Extensions.DependencyInjection.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | preview 6 | enable 7 | true 8 | Debug;Release;Wpf;All 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /StrongInject.Extensions.DependencyInjection/StrongInject.Extensions.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | True 6 | Debug;Release;Wpf;All 7 | 8 | 9 | 10 | $(StrongInjectVersion) 11 | StrongInject.Extensions.DependencyInjection 12 | Provides integration between Microsoft.Extensions.DependencyInjection and StrongInject 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /StrongInject.Generator.Roslyn38/StrongInject.Generator.Roslyn38.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Debug;Release;Wpf;All 6 | StrongInject.Generator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /StrongInject.Generator.Roslyn40/StrongInject.Generator.Roslyn40.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Debug;Release;Wpf;All 6 | StrongInject.Generator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /StrongInject.Generator/AutoIndenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Text; 4 | 5 | namespace StrongInject.Generator; 6 | 7 | internal class AutoIndenter 8 | { 9 | public int Indent => _indent; 10 | 11 | public AutoIndenter(int initialIndent) 12 | { 13 | _indent = initialIndent; 14 | BeginLine(); 15 | } 16 | 17 | private readonly StringBuilder _text = new(); 18 | private int _indent; 19 | const string INDENT = " "; 20 | public void Append(string str) 21 | { 22 | _text.Append(str); 23 | } 24 | 25 | public void Append(char c) 26 | { 27 | _text.Append(c); 28 | } 29 | 30 | public void AppendIndented(string str) 31 | { 32 | _text.Append(INDENT); 33 | _text.Append(str); 34 | } 35 | 36 | public void AppendLine(char c) 37 | { 38 | switch (c) 39 | { 40 | case '}': 41 | _indent--; 42 | _text.Remove(_text.Length - 5, 4); 43 | break; 44 | case '{': 45 | _indent++; 46 | break; 47 | } 48 | _text.Append(c); 49 | _text.AppendLine(); 50 | BeginLine(); 51 | } 52 | 53 | public void AppendLineIndented(string str) 54 | { 55 | _text.Append(INDENT); 56 | _text.AppendLine(str); 57 | BeginLine(); 58 | } 59 | 60 | public void AppendLine(string str) 61 | { 62 | switch (str[0]) 63 | { 64 | case '}': 65 | _indent--; 66 | _text.Remove(_text.Length - 4, 4); 67 | break; 68 | case '{': 69 | _indent++; 70 | break; 71 | } 72 | _text.AppendLine(str); 73 | BeginLine(); 74 | } 75 | 76 | public void AppendLine() 77 | { 78 | _text.Insert(_text.Length - _indent * 4, Environment.NewLine); 79 | } 80 | 81 | private void BeginLine() 82 | { 83 | for (int i = 0; i < _indent; i++) 84 | { 85 | _text.Append(INDENT); 86 | } 87 | } 88 | 89 | public override string ToString() 90 | { 91 | return _text.ToString(); 92 | } 93 | 94 | public AutoIndenter GetSubIndenter() 95 | { 96 | return new AutoIndenter(_indent); 97 | } 98 | 99 | public void Append(AutoIndenter subIndenter) 100 | { 101 | Debug.Assert(subIndenter._indent == _indent); 102 | _text.Remove(_text.Length - _indent * 4, _indent * 4); 103 | _text.Append(subIndenter._text); 104 | } 105 | } -------------------------------------------------------------------------------- /StrongInject.Generator/DecoratorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | /// 6 | /// Provides options to configure a decorator registration 7 | /// 8 | [Flags] 9 | public enum DecoratorOptions : long 10 | { 11 | Default = 0, 12 | 13 | /// 14 | /// Dispose the value returned by this decorator. 15 | /// 16 | /// Apply this only if the decorator itself needs disposal. 17 | /// The decorator should never dispose the underlying instance. 18 | /// If a decorator factory returns the same instance as was passed in, this must be false. 19 | /// 20 | Dispose = 1L << 1, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /StrongInject.Generator/DecoratorSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | abstract internal record DecoratorSource(int DecoratedParameter, bool Dispose, bool IsAsync) 6 | { 7 | public abstract ITypeSymbol OfType { get; } 8 | } 9 | 10 | internal record DecoratorRegistration( 11 | INamedTypeSymbol Type, 12 | ITypeSymbol DecoratedType, 13 | bool RequiresInitialization, 14 | IMethodSymbol Constructor, 15 | int DecoratedParameter, 16 | bool Dispose, 17 | bool IsAsync) : DecoratorSource(DecoratedParameter, Dispose, IsAsync) 18 | { 19 | public override ITypeSymbol OfType => DecoratedType; 20 | } 21 | 22 | internal record DecoratorFactoryMethod( 23 | IMethodSymbol Method, 24 | ITypeSymbol DecoratedType, 25 | bool IsOpenGeneric, 26 | int DecoratedParameter, 27 | bool Dispose, 28 | bool IsAsync) : DecoratorSource(DecoratedParameter, Dispose, IsAsync) 29 | { 30 | public override ITypeSymbol OfType => DecoratedType; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /StrongInject.Generator/Disposal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | internal abstract record Disposal(bool IsAsync) 6 | { 7 | internal sealed record IDisposable(string VariableName, bool IsAsync) : Disposal(IsAsync); 8 | internal sealed record DisposalHelpers(string VariableName, bool IsAsync) : Disposal(IsAsync); 9 | internal sealed record FactoryDisposal(string VariableName, string FactoryName, bool IsAsync) : Disposal(IsAsync); 10 | internal sealed record DelegateDisposal(string DisposeActionsName, string DisposeActionsTypeName, bool IsAsync) : Disposal(IsAsync); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /StrongInject.Generator/DisposalStyle.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Generator 2 | { 3 | internal readonly struct DisposalStyle 4 | { 5 | public DisposalStyle(bool isAsync, DisposalStyleDeterminant determinant) 6 | { 7 | IsAsync = isAsync; 8 | Determinant = determinant; 9 | } 10 | 11 | public bool IsAsync { get; } 12 | public DisposalStyleDeterminant Determinant { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /StrongInject.Generator/DisposalStyleDeterminant.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Generator 2 | { 3 | internal enum DisposalStyleDeterminant 4 | { 5 | Container, 6 | OwnedType, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /StrongInject.Generator/FactoryOfMethod.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | internal record FactoryOfMethod(FactoryMethod Underlying, ITypeSymbol FactoryOfType); 6 | } 7 | -------------------------------------------------------------------------------- /StrongInject.Generator/InstanceSources.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace StrongInject.Generator 5 | { 6 | internal class InstanceSources : IReadOnlyCollection 7 | { 8 | public InstanceSources(InstanceSource? best, ImmutableSetInInsertionOrder others) 9 | { 10 | Best = best; 11 | _others = others; 12 | } 13 | 14 | private readonly ImmutableSetInInsertionOrder _others; 15 | public InstanceSource? Best { get; } 16 | 17 | public int Count => Best is null ? _others.Count : _others.Count + 1; 18 | 19 | public static InstanceSources Create(InstanceSource best) => new InstanceSources(best, ImmutableSetInInsertionOrder.Empty); 20 | 21 | public InstanceSources Add(InstanceSource instanceSource) 22 | { 23 | if (Best is null) 24 | { 25 | return new InstanceSources(null, _others.Add(instanceSource)); 26 | } 27 | else 28 | { 29 | return new InstanceSources(null, _others.Add(instanceSource).Add(Best)); 30 | } 31 | } 32 | 33 | public InstanceSources Merge(InstanceSources instanceSources) 34 | { 35 | if (Best?.Equals(instanceSources.Best) ?? false) 36 | { 37 | return new InstanceSources(Best, _others.Union(instanceSources._others)); 38 | } 39 | 40 | var others = _others.Union(instanceSources._others); 41 | if (Best is not null) 42 | { 43 | others = others.Add(Best); 44 | } 45 | if (instanceSources.Best is not null) 46 | { 47 | others = others.Add(instanceSources.Best); 48 | } 49 | return new InstanceSources(null, others); 50 | } 51 | 52 | public InstanceSources MergeWithPreferred(InstanceSources instanceSources) 53 | { 54 | var others = _others.Union(instanceSources._others); 55 | if (instanceSources.Best is not null) 56 | { 57 | others = others.Remove(instanceSources.Best); 58 | } 59 | if (Best is not null && !Best.Equals(instanceSources.Best)) 60 | { 61 | others = others.Add(Best); 62 | } 63 | return new InstanceSources(instanceSources.Best, others); 64 | } 65 | 66 | public IEnumerator GetEnumerator() 67 | { 68 | if (Best is not null) 69 | { 70 | yield return Best; 71 | } 72 | 73 | foreach (var other in _others) 74 | { 75 | yield return other; 76 | } 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /StrongInject.Generator/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices { internal class IsExternalInit { } } 2 | -------------------------------------------------------------------------------- /StrongInject.Generator/Operation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | internal class Operation 6 | { 7 | public Operation(Statement statement, Disposal? disposal, List dependencies, bool canDisposeLocally, AwaitStatement? awaitStatement = null) 8 | { 9 | Statement = statement; 10 | Disposal = disposal; 11 | Dependencies = dependencies; 12 | AwaitStatement = awaitStatement; 13 | CanDisposeLocally = canDisposeLocally; 14 | } 15 | 16 | public Statement Statement { get; } 17 | public Disposal? Disposal { get; } 18 | public AwaitStatement? AwaitStatement { get; } 19 | public bool CanDisposeLocally { get; } 20 | public List Dependencies { get; } 21 | } 22 | 23 | internal static class OperationExtensions 24 | { 25 | public static bool CanDisposeAwaitStatementResultLocally(this Operation operation) 26 | { 27 | if (operation.Statement is not AwaitStatement { VariableName: not null }) 28 | return false; 29 | return operation.Dependencies[0].CanDisposeLocally; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /StrongInject.Generator/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Generator 4 | { 5 | /// 6 | /// Provides options to configure a registration 7 | /// 8 | [Flags] 9 | public enum Options : long 10 | { 11 | Default = 0, 12 | 13 | #region As Options (bits 0 - 23) 14 | 15 | AsImplementedInterfaces = 1L << 0, 16 | 17 | AsBaseClasses = 1L << 1, 18 | 19 | UseAsFactory = 1L << 2, 20 | 21 | ApplySameOptionsToFactoryTargets = 1L << 3, 22 | 23 | #endregion 24 | 25 | #region FactoryTargetScope Options (bits 24 - 31) 26 | 27 | FactoryTargetScopeShouldBeInstancePerResolution = Scope.InstancePerResolution << 24, 28 | 29 | FactoryTargetScopeShouldBeInstancePerDependency = Scope.InstancePerDependency << 24, 30 | 31 | FactoryTargetScopeShouldBeSingleInstance = Scope.SingleInstance << 24, 32 | 33 | #endregion 34 | 35 | #region Other Options (bits 32 - 63) 36 | 37 | DoNotDecorate = 1L << 32 38 | 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StrongInject.Generator/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Generator 2 | { 3 | public enum Scope 4 | { 5 | /// 6 | /// Default scope. 7 | /// A single instance is shared between all dependencies created for a single resolution. 8 | /// For example if 'A' depends on 'B' and 'C', and 'B' and 'C' both depend on an instance of 'D', 9 | /// then when 'A' is resolved 'B' and 'C' will share the same instance of 'D'. 10 | /// 11 | /// Note every SingleInstance dependency defines a separate resolution, 12 | /// so if 'B' and/or 'C' are SingleInstance they would not share an instance of 'D'. 13 | /// 14 | /// Similarly every lambda defines a separate resolution, so if A depends on Func<B>, 15 | /// then each time Func<B> is invoked a fresh instance of both B and D will be created. 16 | /// 17 | InstancePerResolution = 0, 18 | 19 | /// 20 | /// A new instance is created for every usage. 21 | /// For example even if type 'B' appears twice in the constructor of 'A', 22 | /// two different instances will be passed into the constructor. 23 | /// 24 | InstancePerDependency = 1, 25 | 26 | /// 27 | /// A single instance will be shared across all dependencies, from any resolution 28 | /// 29 | SingleInstance = 2, 30 | } 31 | } -------------------------------------------------------------------------------- /StrongInject.Generator/Statement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Immutable; 3 | 4 | namespace StrongInject.Generator 5 | { 6 | internal abstract record Statement; 7 | internal sealed record DependencyCreationStatement( 8 | string VariableName, 9 | InstanceSource Source, 10 | ImmutableArray Dependencies) : Statement; 11 | internal sealed record DelegateCreationStatement( 12 | string VariableName, 13 | DelegateSource Source, 14 | ImmutableArray InternalOperations, 15 | string InternalTargetName) : Statement 16 | { 17 | public string DisposeActionsName { get; } = "disposeActions_" + VariableName; 18 | } 19 | internal sealed record DisposeActionsCreationStatement(string VariableName, string TypeName) : Statement; 20 | internal sealed record SingleInstanceReferenceStatement(string VariableName, InstanceSource Source, bool IsAsync) : Statement; 21 | internal sealed record InitializationStatement(string? VariableName, string VariableToInitializeName, bool IsAsync) : Statement; 22 | internal sealed record AwaitStatement(string? VariableName, string VariableToAwaitName, ITypeSymbol? Type) : Statement 23 | { 24 | public string HasAwaitStartedVariableName { get; } = "hasAwaitStarted_" + VariableToAwaitName; 25 | public string HasAwaitCompletedVariableName { get; } = "hasAwaitCompleted_" + VariableToAwaitName; 26 | } 27 | 28 | internal sealed record OwnedCreationStatement( 29 | string VariableName, 30 | OwnedSource Source, 31 | bool IsAsyncLocalFunction, 32 | string LocalFunctionName) : Statement; 33 | 34 | internal sealed record OwnedCreationLocalFunctionStatement( 35 | OwnedSource Source, 36 | bool IsAsyncLocalFunction, 37 | string LocalFunctionName, 38 | ImmutableArray InternalOperations, 39 | string InternalTargetName) : Statement; 40 | } 41 | -------------------------------------------------------------------------------- /StrongInject.Generator/StrongInject.Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Debug;Release;Wpf;All 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/IVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace StrongInject.Generator.Visitors 7 | { 8 | internal interface IVisitor 9 | { 10 | void Visit(Registration registration, TState state); 11 | void Visit(FactorySource factorySource, TState state); 12 | void Visit(DelegateSource delegateSource, TState state); 13 | void Visit(DelegateParameter delegateParameter, TState state); 14 | void Visit(FactoryMethod factoryMethod, TState state); 15 | void Visit(InstanceFieldOrProperty instanceFieldOrProperty, TState state); 16 | void Visit(ArraySource arraySource, TState state); 17 | void Visit(WrappedDecoratorInstanceSource wrappedDecoratorInstanceSource, TState state); 18 | void Visit(ForwardedInstanceSource forwardedInstanceSource, TState state); 19 | void Visit(OwnedSource ownedSource, TState state); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/PartialOrderingOfSingleInstanceDependenciesVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | 6 | namespace StrongInject.Generator.Visitors 7 | { 8 | internal class PartialOrderingOfSingleInstanceDependenciesVisitor : SimpleVisitor 9 | { 10 | private List? _results; 11 | private readonly HashSet _alreadyAdded = new(); 12 | 13 | private PartialOrderingOfSingleInstanceDependenciesVisitor(InstanceSourcesScope containerScope, CancellationToken cancellationToken) : base(containerScope, cancellationToken) 14 | { 15 | } 16 | 17 | public static IEnumerable GetPartialOrdering(InstanceSourcesScope containerScope, HashSet usedSingleInstanceSources, CancellationToken cancellationToken) 18 | { 19 | var visitor = new PartialOrderingOfSingleInstanceDependenciesVisitor(containerScope, cancellationToken); 20 | IEnumerable results = Array.Empty(); 21 | foreach (var source in usedSingleInstanceSources) 22 | { 23 | visitor.VisitCore(source, new State(containerScope)); 24 | if (visitor._results is { } visitResults) 25 | results = visitResults.Concat(results); 26 | visitor._results = null; 27 | } 28 | return results; 29 | } 30 | 31 | protected override bool ShouldVisitBeforeUpdateState(InstanceSource? source, State state) 32 | { 33 | if (source is null) 34 | return false; 35 | if (source.Scope == Scope.SingleInstance && source is not (InstanceFieldOrProperty or ForwardedInstanceSource)) 36 | { 37 | if (_alreadyAdded.Add(source)) 38 | { 39 | (_results ??= new()).Add(source); 40 | } 41 | else 42 | { 43 | return false; 44 | } 45 | } 46 | return base.ShouldVisitBeforeUpdateState(source, state); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/RequiresAsyncVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | namespace StrongInject.Generator.Visitors 5 | { 6 | internal class RequiresAsyncChecker 7 | { 8 | private readonly InstanceSourcesScope _containerScope; 9 | private readonly CancellationToken _cancellationToken; 10 | private readonly Dictionary _cache = new(); 11 | 12 | public RequiresAsyncChecker(InstanceSourcesScope containerScope, CancellationToken cancellationToken) 13 | { 14 | _containerScope = containerScope; 15 | _cancellationToken = cancellationToken; 16 | } 17 | 18 | public bool RequiresAsync(InstanceSource source) 19 | { 20 | return _cache.GetOrCreate(source, (_containerScope, _cancellationToken), static (i, s) => Visitor.RequiresAsync(i, s._containerScope, s._cancellationToken)); 21 | } 22 | 23 | private class Visitor : SimpleVisitor 24 | { 25 | private bool _requiresAsync = false; 26 | 27 | private Visitor(InstanceSourcesScope containerScope, CancellationToken cancellationToken) : base(containerScope, cancellationToken) 28 | { 29 | } 30 | 31 | public static bool RequiresAsync(InstanceSource source, InstanceSourcesScope containerScope, CancellationToken cancellationToken) 32 | { 33 | var visitor = new Visitor(containerScope, cancellationToken); 34 | visitor.VisitCore(source, new State(containerScope)); 35 | return visitor._requiresAsync; 36 | } 37 | 38 | protected override bool ShouldVisitBeforeUpdateState(InstanceSource? source, State state) 39 | { 40 | if (source is null) 41 | return false; 42 | if (source is DelegateSource { IsAsync: true }) 43 | return false; 44 | if (source.IsAsync) 45 | { 46 | _requiresAsync = true; 47 | ExitFast(); 48 | return false; 49 | } 50 | 51 | return base.ShouldVisitBeforeUpdateState(source, state); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/RequiresUnsafeVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Threading; 3 | 4 | namespace StrongInject.Generator.Visitors 5 | { 6 | internal class RequiresUnsafeVisitor : SimpleVisitor 7 | { 8 | private bool _requiresUnsafe = false; 9 | 10 | private RequiresUnsafeVisitor(InstanceSourcesScope containerScope, CancellationToken cancellationToken) : base(containerScope, cancellationToken) 11 | { 12 | } 13 | 14 | public static bool RequiresUnsafe(ITypeSymbol target, InstanceSourcesScope containerScope, CancellationToken cancellationToken) 15 | { 16 | var visitor = new RequiresUnsafeVisitor(containerScope, cancellationToken); 17 | var state = new State(containerScope); 18 | visitor.VisitCore(visitor.GetInstanceSource(target, state, parameterSymbol: null), state); 19 | return visitor._requiresUnsafe; 20 | } 21 | 22 | protected override bool ShouldVisitBeforeUpdateState(InstanceSource? source, State state) 23 | { 24 | if (source is null) 25 | return false; 26 | if (IsUnsafeType(source.OfType)) 27 | { 28 | _requiresUnsafe = true; 29 | ExitFast(); 30 | return false; 31 | } 32 | return base.ShouldVisitBeforeUpdateState(source, state); 33 | } 34 | 35 | private static bool IsUnsafeType(ITypeSymbol type) => type.IsPointerOrFunctionPointer() || type is IArrayTypeSymbol { ElementType: var elementType } && IsUnsafeType(elementType); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/SimpleVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | namespace StrongInject.Generator.Visitors 5 | { 6 | internal abstract class SimpleVisitor : BaseVisitor 7 | { 8 | private readonly HashSet _visited = new(); 9 | protected readonly InstanceSourcesScope _containerScope; 10 | 11 | protected SimpleVisitor(InstanceSourcesScope containerScope, CancellationToken cancellationToken) : base(cancellationToken) 12 | { 13 | _containerScope = containerScope; 14 | } 15 | 16 | protected override bool ShouldVisitBeforeUpdateState(InstanceSource? source, State state) 17 | { 18 | if (source is null) 19 | return false; 20 | return true; 21 | } 22 | 23 | protected override void UpdateState(InstanceSource source, ref State state) 24 | { 25 | if (source.Scope == Scope.SingleInstance) 26 | { 27 | state.CurrentlyVisitingDelegates = new(); 28 | } 29 | base.UpdateState(source, ref state); 30 | } 31 | 32 | protected override bool ShouldVisitAfterUpdateState(InstanceSource source, State state) 33 | { 34 | if ((ReferenceEquals(state.InstanceSourcesScope, _containerScope) 35 | || ReferenceEquals(state.PreviousScope, _containerScope)) 36 | && !_visited.Add(source)) 37 | return false; 38 | if (source is DelegateSource ds && !state.CurrentlyVisitingDelegates.Add(ds)) 39 | return false; 40 | return true; 41 | } 42 | 43 | protected override void AfterVisit(InstanceSource source, State state) 44 | { 45 | if (source is DelegateSource ds) 46 | state.CurrentlyVisitingDelegates.Remove(ds); 47 | base.AfterVisit(source, state); 48 | } 49 | 50 | public struct State : IState 51 | { 52 | public State(InstanceSourcesScope instanceSourcesScope) 53 | { 54 | _instanceSourcesScope = instanceSourcesScope; 55 | CurrentlyVisitingDelegates = new(); 56 | PreviousScope = null; 57 | } 58 | 59 | private InstanceSourcesScope _instanceSourcesScope; 60 | 61 | public InstanceSourcesScope? PreviousScope { get; private set; } 62 | public InstanceSourcesScope InstanceSourcesScope 63 | { 64 | get => _instanceSourcesScope; 65 | set 66 | { 67 | PreviousScope = _instanceSourcesScope; 68 | _instanceSourcesScope = value; 69 | } 70 | } 71 | 72 | public HashSet CurrentlyVisitingDelegates { get; set; } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /StrongInject.Generator/Visitors/SingleInstanceVariablesToCreateEarlyVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | namespace StrongInject.Generator.Visitors 5 | { 6 | /// 7 | /// Calculates which single instance variables require an async context to be resolved, 8 | /// but are used in a sync delegate. 9 | /// In order to allow them to be resolved, they must be resolved in a parent async scope, 10 | /// and then captured by the delegate. 11 | /// 12 | internal class SingleInstanceVariablesToCreateEarlyVisitor : SimpleVisitor 13 | { 14 | private readonly RequiresAsyncChecker _requiresAsyncChecker; 15 | private readonly List _singleInstanceVariablesToCreateEarly = new(); 16 | 17 | private SingleInstanceVariablesToCreateEarlyVisitor(RequiresAsyncChecker requiresAsyncChecker, InstanceSourcesScope containerScope, CancellationToken cancellationToken) : base(containerScope, cancellationToken) 18 | { 19 | _requiresAsyncChecker = requiresAsyncChecker; 20 | } 21 | 22 | public static List CalculateVariables(RequiresAsyncChecker requiresAsyncChecker, InstanceSource source, InstanceSourcesScope currentScope, InstanceSourcesScope containerScope, CancellationToken cancellationToken) 23 | { 24 | var visitor = new SingleInstanceVariablesToCreateEarlyVisitor(requiresAsyncChecker, containerScope, cancellationToken); 25 | visitor.VisitCore(source, new State(currentScope)); 26 | return visitor._singleInstanceVariablesToCreateEarly; 27 | } 28 | 29 | protected override bool ShouldVisitBeforeUpdateState(InstanceSource? source, State state) 30 | { 31 | if (source is null) 32 | return false; 33 | if (source is DelegateSource { IsAsync: true }) 34 | return false; 35 | if (source.Scope == Scope.SingleInstance) 36 | { 37 | if (_requiresAsyncChecker.RequiresAsync(source)) 38 | { 39 | _singleInstanceVariablesToCreateEarly.Add(source); 40 | } 41 | return false; 42 | } 43 | return base.ShouldVisitBeforeUpdateState(source, state); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices { internal class IsExternalInit { } } -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/Modules/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Modules; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace StrongInject.Tests.Integration.Modules 7 | { 8 | public partial class CollectionTests 9 | { 10 | [RegisterModule(typeof(CollectionsModule))] 11 | [Register(typeof(A), typeof(I))] 12 | [Register(typeof(B), typeof(I))] 13 | [Register(typeof(Collections))] 14 | public partial class Container : IContainer 15 | { 16 | 17 | } 18 | 19 | public record Collections( 20 | IEnumerable Enumerable1, 21 | IEnumerable Enumerable2, 22 | IReadOnlyList List1, 23 | IReadOnlyList List2, 24 | IReadOnlyCollection Collection1, 25 | IReadOnlyCollection Collection2) { } 26 | 27 | public interface I { } 28 | public class A : I { } 29 | public class B : I { } 30 | 31 | [Fact] 32 | public void TestCanResolveCollections() 33 | { 34 | var container = new Container(); 35 | using var scope = container.Resolve(); 36 | var collections = scope.Value; 37 | Assert.Equal(2, collections.Enumerable1.Count()); 38 | Assert.Equal(2, collections.Enumerable2.Count()); 39 | Assert.Equal(2, collections.List1.Count); 40 | Assert.Equal(2, collections.List2.Count); 41 | Assert.Equal(2, collections.Collection1.Count); 42 | Assert.Equal(2, collections.Collection2.Count); 43 | Assert.NotSame(collections.Enumerable1, collections.Enumerable2); 44 | Assert.NotSame(collections.List1, collections.List2); 45 | Assert.NotSame(collections.Collection1, collections.Collection2); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/Modules/ImmutableArrayTests.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Modules; 2 | using System; 3 | using System.Collections.Immutable; 4 | using System.Runtime.CompilerServices; 5 | using Xunit; 6 | 7 | namespace StrongInject.Tests.Integration.Modules 8 | { 9 | public partial class ImmutableArrayTests 10 | { 11 | [RegisterModule(typeof(SafeImmutableArrayModule))] 12 | [RegisterModule(typeof(StandardModule))] 13 | [Register(typeof(A), typeof(I))] 14 | [Register(typeof(B), typeof(I))] 15 | public partial class SafeContainer : IContainer<(ImmutableArray first, ImmutableArray second)> 16 | { 17 | 18 | } 19 | 20 | [RegisterModule(typeof(UnsafeImmutableArrayModule))] 21 | [RegisterModule(typeof(StandardModule))] 22 | [Register(typeof(A), typeof(I))] 23 | [Register(typeof(B), typeof(I))] 24 | public partial class UnsafeContainer : IContainer<(ImmutableArray first, ImmutableArray second)> 25 | { 26 | 27 | } 28 | 29 | public interface I { } 30 | public class A : I { } 31 | public class B : I { } 32 | 33 | [Fact] 34 | public void TestCanResolveSafe() 35 | { 36 | var container = new SafeContainer(); 37 | using var aScope1 = container.Resolve(); 38 | var (first, second) = aScope1.Value; 39 | Assert.Equal(2, first.Length); 40 | Assert.Equal(2, second.Length); 41 | Assert.NotSame(Unsafe.As, I[]>(ref first), Unsafe.As, I[]>(ref second)); 42 | } 43 | 44 | [Fact] 45 | public void TestCanResolveUnsafe() 46 | { 47 | var container = new UnsafeContainer(); 48 | using var aScope1 = container.Resolve(); 49 | var (first, second) = aScope1.Value; 50 | Assert.Equal(2, first.Length); 51 | Assert.Equal(2, second.Length); 52 | Assert.NotSame(Unsafe.As, I[]>(ref first), Unsafe.As, I[]>(ref second)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/Modules/LazyTests.cs: -------------------------------------------------------------------------------- 1 | using StrongInject.Modules; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace StrongInject.Tests.Integration.Modules 9 | { 10 | public partial class LazyTests 11 | { 12 | [RegisterModule(typeof(LazyModule))] 13 | [Register(typeof(A))] 14 | public partial class Container : IContainer> 15 | { 16 | 17 | } 18 | 19 | public class A { } 20 | 21 | [Fact] 22 | public void TestCanResolveLazy() 23 | { 24 | var container = new Container(); 25 | using var aScope1 = container.Resolve(); 26 | using var aScope2 = container.Resolve(); 27 | Assert.Same(aScope1.Value.Value, aScope1.Value.Value); 28 | Assert.NotSame(aScope1.Value.Value, aScope2.Value.Value); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/StrongInject.Tests.Integration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | preview 6 | enable 7 | Debug;Release;Wpf;All 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestFuncParameterInjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration 5 | { 6 | public partial class TestFuncParameterInjection 7 | { 8 | public record A(B b, C c) { } 9 | public record B(C c) { } 10 | public class C { } 11 | 12 | [Register(typeof(C))] 13 | [Register(typeof(B))] 14 | [Register(typeof(A))] 15 | public partial class Container : IContainer> 16 | { 17 | } 18 | 19 | [Fact] 20 | public void Test() 21 | { 22 | var container = new Container(); 23 | var c1 = new C(); 24 | var c2 = new C(); 25 | container.Run(aF => 26 | { 27 | var a1 = aF(c1); 28 | Assert.Same(c1, a1.c); 29 | Assert.Same(c1, a1.b.c); 30 | var a2 = aF(c2); 31 | Assert.Same(c2, a2.c); 32 | Assert.Same(c2, a2.b.c); 33 | }); 34 | 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestFuncScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace StrongInject.Tests.Integration 6 | { 7 | public partial class TestFuncScope 8 | { 9 | public record A(B b, C c, E e) { } 10 | public record B(D d) { } 11 | public record C(D d) { } 12 | public record D { } 13 | public record E { } 14 | 15 | [Register(typeof(E), Scope.SingleInstance)] 16 | [Register(typeof(D), Scope.InstancePerDependency)] 17 | [Register(typeof(C))] 18 | [Register(typeof(B))] 19 | [Register(typeof(A))] 20 | public partial class Container : IContainer> 21 | { 22 | } 23 | 24 | [Fact] 25 | public void Test() 26 | { 27 | var container = new Container(); 28 | var e1 = container.Run(aF => 29 | { 30 | var a1 = aF(); 31 | Assert.NotSame(a1.b.d, a1.c.d); 32 | var a2 = aF(); 33 | Assert.NotSame(a2.b.d, a2.c.d); 34 | Assert.NotSame(a1, a2); 35 | Assert.NotSame(a1.b, a2.b); 36 | Assert.NotSame(a1.c, a2.c); 37 | Assert.NotSame(a1.b.d, a2.b.d); 38 | Assert.Same(a1.e, a2.e); 39 | return a1.e; 40 | }); 41 | var e2 = container.Run(aF => aF().e); 42 | Assert.Same(e1, e2); 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestInstancePerDependencyHasCorrectScope.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration 5 | { 6 | public partial class TestInstancePerDependencyHasCorrectScope 7 | { 8 | public record A(B b, C c) { } 9 | public record B(D d) { } 10 | public record C(D d) { } 11 | public record D { } 12 | 13 | [Register(typeof(D), Scope.InstancePerDependency)] 14 | [Register(typeof(C))] 15 | [Register(typeof(B))] 16 | [Register(typeof(A))] 17 | public partial class Container : IAsyncContainer 18 | { 19 | } 20 | 21 | [Fact] 22 | public async Task Test() 23 | { 24 | var container = new Container(); 25 | var a1 = await container.RunAsync(x => x); 26 | Assert.NotSame(a1.b.d, a1.c.d); 27 | var a2 = await container.RunAsync(x => x); 28 | Assert.NotSame(a2.b.d, a2.c.d); 29 | Assert.NotSame(a1, a2); 30 | Assert.NotSame(a1.b, a2.b); 31 | Assert.NotSame(a1.c, a2.c); 32 | Assert.NotSame(a1.b.d, a2.b.d); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestInstancePerResolutionHasCorrectScope.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration 5 | { 6 | public partial class TestInstancePerResolutionHasCorrectScope 7 | { 8 | public record A(B b, C c) { } 9 | public record B(D d) { } 10 | public record C(D d) { } 11 | public record D { } 12 | 13 | [Register(typeof(D))] 14 | [Register(typeof(C))] 15 | [Register(typeof(B))] 16 | [Register(typeof(A))] 17 | public partial class Container : IAsyncContainer 18 | { 19 | } 20 | 21 | [Fact] 22 | public async Task Test() 23 | { 24 | var container = new Container(); 25 | var a1 = await container.RunAsync(x => x); 26 | Assert.Same(a1.b.d, a1.c.d); 27 | var a2 = await container.RunAsync(x => x); 28 | Assert.Same(a2.b.d, a2.c.d); 29 | Assert.NotSame(a1, a2); 30 | Assert.NotSame(a1.b, a2.b); 31 | Assert.NotSame(a1.c, a2.c); 32 | Assert.NotSame(a1.b.d, a2.b.d); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestNullableConversion.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration 5 | { 6 | public partial class TestNullableConversion 7 | { 8 | public struct B { } 9 | public struct A 10 | { 11 | public B? B { get; } 12 | public A(B? b) => B = b; 13 | } 14 | 15 | 16 | [Register(typeof(B), typeof(B?))] 17 | [Register(typeof(A))] 18 | public partial class Container : IAsyncContainer 19 | { 20 | } 21 | 22 | [Fact] 23 | public async Task Test() 24 | { 25 | await new Container().RunAsync(x => Assert.NotNull(x.B)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestSingleInstanceHasCorrectScope.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration 5 | { 6 | public partial class TestSingleInstanceHasCorrectScope 7 | { 8 | public record A(B b, C c) { } 9 | public record B(D d) { } 10 | public record C(D d) { } 11 | public record D { } 12 | 13 | [Register(typeof(D), Scope.SingleInstance)] 14 | [Register(typeof(C))] 15 | [Register(typeof(B))] 16 | [Register(typeof(A))] 17 | public partial class Container : IAsyncContainer 18 | { 19 | } 20 | 21 | [Fact] 22 | public async Task Test() 23 | { 24 | var container = new Container(); 25 | var a1 = await container.RunAsync(x => x); 26 | Assert.Same(a1.b.d, a1.c.d); 27 | var a2 = await container.RunAsync(x => x); 28 | Assert.Same(a2.b.d, a2.c.d); 29 | Assert.NotSame(a1, a2); 30 | Assert.NotSame(a1.b, a2.b); 31 | Assert.NotSame(a1.c, a2.c); 32 | Assert.Same(a1.b.d, a2.b.d); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /StrongInject.Tests.Integration/TestSingleInstanceNull.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace StrongInject.Tests.Integration; 5 | 6 | public partial class TestSingleInstanceNull 7 | { 8 | public class A : IDisposable 9 | { 10 | public void Dispose() 11 | { 12 | } 13 | } 14 | 15 | public partial class Container : IContainer 16 | { 17 | [Factory(Scope.SingleInstance)] 18 | public A? GetA() 19 | { 20 | Count++; 21 | return null; 22 | } 23 | 24 | public int Count { get; private set; } 25 | } 26 | 27 | [Fact] 28 | public void Test() 29 | { 30 | using var container = new Container(); 31 | Assert.Equal(0, container.Count); 32 | Assert.Null(container.Run(x => x)); 33 | Assert.Equal(1, container.Count); 34 | Assert.Null(container.Run(x => x)); 35 | Assert.Equal(1, container.Count); 36 | } 37 | } -------------------------------------------------------------------------------- /StrongInject.Tests.Unit/AssertionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using FluentAssertions.Primitives; 3 | 4 | namespace StrongInject.Generator.Tests.Unit 5 | { 6 | public static class AssertionExtensions 7 | { 8 | public static AndConstraint BeIgnoringLineEndings(this StringAssertions stringAssertions, string expected, string because = "", params object[] becauseArgs) 9 | { 10 | return stringAssertions.Subject.Replace("\r\n", "\n").Should().Be(expected.Replace("\r\n", "\n")); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /StrongInject.Tests.Unit/OwnedTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace StrongInject.Generator.Tests.Unit 5 | { 6 | public static class OwnedTests 7 | { 8 | [Fact] 9 | public static void OwnedCanBeImplicitlyCastToWiderT() 10 | { 11 | var narrowerType = new Owned("", dispose: () => { }); 12 | IOwned widerType = narrowerType; 13 | 14 | // This should survive any refactoring of the types or APIs. 15 | // (E.g. Replacing IOwned with owned.Cast() returning an Owned wrapper would not be acceptable.) 16 | Assert.Same(narrowerType, widerType); 17 | } 18 | 19 | [Fact] 20 | public static void AsyncOwnedCanBeImplicitlyCastToWiderT() 21 | { 22 | var narrowerType = new AsyncOwned("", dispose: () => ValueTask.CompletedTask); 23 | IAsyncOwned widerType = narrowerType; 24 | 25 | // This should survive any refactoring of the types or APIs. 26 | // (E.g. Replacing IOwned with owned.Cast() returning an Owned wrapper would not be acceptable.) 27 | Assert.Same(narrowerType, widerType); 28 | } 29 | 30 | [Fact] 31 | public static void OwnedCanBeUsedWithNullDisposeAction() 32 | { 33 | var owned = new Owned("", dispose: null); 34 | 35 | // If a check is ever added in the future to return null or throw if Value is accessed after disposal, 36 | // the null action should not cause Owned to think that disposal has already happened. 37 | Assert.Equal("", owned.Value); 38 | 39 | owned.Dispose(); 40 | } 41 | 42 | [Fact] 43 | public static void AsyncOwnedCanBeUsedWithNullDisposeAction() 44 | { 45 | var owned = new AsyncOwned("", dispose: null); 46 | 47 | // If a check is ever added in the future to return null or throw if Value is accessed after disposal, 48 | // the null action should not cause Owned to think that disposal has already happened. 49 | Assert.Equal("", owned.Value); 50 | 51 | // This should complete instantly since there is nothing to do. 52 | Assert.True(owned.DisposeAsync().IsCompletedSuccessfully); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /StrongInject.Tests.Unit/RoslynExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace StrongInject.Generator.Tests.Unit 8 | { 9 | public static class RoslynExtensions 10 | { 11 | public static INamedTypeSymbol AssertGetTypeByMetadataName(this Compilation compilation, string name) 12 | { 13 | var type = compilation.GetTypeByMetadataName(name); 14 | Assert.NotNull(type); 15 | return type!; 16 | } 17 | 18 | public static void Verify( 19 | this IEnumerable diagnostics, 20 | params DiagnosticResult[] expected) => DiagnosticVerifier.VerifyDiagnostics(diagnostics, expected); 21 | } 22 | } -------------------------------------------------------------------------------- /StrongInject.Tests.Unit/StrongInject.Tests.Unit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | preview 6 | enable 7 | Debug;Release;Wpf;All 8 | StrongInject.Generator.Tests.Unit 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /StrongInject/DecoratorFactoryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this to mark a method as a decorator for its return type. 7 | /// Exactly one of the parameters must be the same type as the return type. 8 | /// When resolving an instance of the return type, once the type has been resolved as normal it will be passed as a parameter to the method. 9 | /// You can mark both normal methods and generic methods with this attribute. 10 | /// 11 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 12 | public sealed class DecoratorFactoryAttribute : Attribute 13 | { 14 | public DecoratorFactoryAttribute(DecoratorOptions decoratorOptions = DecoratorOptions.Default) 15 | { 16 | DecoratorOptions = decoratorOptions; 17 | } 18 | 19 | public DecoratorOptions DecoratorOptions { get; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StrongInject/DecoratorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Provides options to configure a decorator registration 7 | /// 8 | [Flags] 9 | public enum DecoratorOptions : long 10 | { 11 | Default = 0, 12 | 13 | /// 14 | /// Dispose the value returned by this decorator. 15 | /// 16 | /// Apply this only if the decorator itself needs disposal. 17 | /// The decorator should never dispose the underlying instance. 18 | /// If a decorator factory returns the same instance as was passed in, this must be false. 19 | /// 20 | Dispose = 1L << 1, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /StrongInject/DummyParameter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace StrongInject.Internal 4 | { 5 | /// 6 | /// A class with no possible value other than null. Used to mark an optional parameter which should never be set. 7 | /// 8 | [EditorBrowsable(EditorBrowsableState.Never)] 9 | public sealed class DummyParameter 10 | { 11 | private DummyParameter() { } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /StrongInject/FactoryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this to mark a method as a factory. 7 | /// The method will be used to resolve instances of its return type. 8 | /// You can mark both normal methods and generic methods with this attribute. 9 | /// 10 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 11 | public sealed class FactoryAttribute : Attribute 12 | { 13 | /// The scope of each instance resolved from the method - i.e. how often the method will be called. 14 | public FactoryAttribute(Scope scope = Scope.InstancePerResolution) 15 | { 16 | Scope = scope; 17 | AsTypes = Array.Empty(); 18 | } 19 | 20 | /// The scope of each instance resolved from the method - i.e. how often the method will be called. 21 | /// An optional list of types for this to be used as the factory of. 22 | /// If left empty it will be used as a factory of the return type. 23 | /// If not left empty you will have to explicitly register it as the return type if desired. 24 | /// All types must be supertypes of the return type. 25 | public FactoryAttribute(Scope scope, params Type[] asTypes) 26 | { 27 | Scope = scope; 28 | AsTypes = asTypes; 29 | } 30 | 31 | /// An optional list of types for this to be used as the factory of. 32 | /// If left empty it will be used as a factory of the return type. 33 | /// If not left empty you will have to explicitly register it as the return type if desired. 34 | /// All types must be supertypes of the return type. 35 | public FactoryAttribute(params Type[] asTypes) 36 | { 37 | AsTypes = asTypes; 38 | } 39 | 40 | public Scope Scope { get; } 41 | public Type[] AsTypes { get; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /StrongInject/FactoryOfAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this to mark a generic method as a factory, but only to be used to resolve instances of . 7 | /// can be a concrete type or an open generic. 8 | /// You can mark a method with this attribute multiple times. 9 | /// Do not also mark it with the . 10 | /// 11 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] 12 | public sealed class FactoryOfAttribute : Attribute 13 | { 14 | /// 15 | /// 16 | /// 17 | /// The type which the method should be used to resolve 18 | /// The scope of each instance resolved from the method - i.e. how often the method will be called. 19 | public FactoryOfAttribute(Type type, Scope scope = Scope.InstancePerResolution) 20 | { 21 | Type = type; 22 | Scope = scope; 23 | } 24 | public Type Type { get; } 25 | public Scope Scope { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /StrongInject/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject 6 | { 7 | [EditorBrowsable(EditorBrowsableState.Never)] 8 | public static class Helpers 9 | { 10 | [EditorBrowsable(EditorBrowsableState.Never)] 11 | public static void Dispose(T instance) 12 | { 13 | if (instance is IDisposable disposable) 14 | { 15 | disposable.Dispose(); 16 | } 17 | } 18 | 19 | [EditorBrowsable(EditorBrowsableState.Never)] 20 | public static ValueTask DisposeAsync(T instance) 21 | { 22 | if (instance is IAsyncDisposable asyncDisposable) 23 | { 24 | return asyncDisposable.DisposeAsync(); 25 | } 26 | 27 | if (instance is IDisposable disposable) 28 | { 29 | disposable.Dispose(); 30 | } 31 | 32 | return default; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StrongInject/IContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject 6 | { 7 | /// 8 | /// Implement this interface to tell StrongInject to generate implementations for and . 9 | /// You can implement this interface multiple times for different values of , and Single Instances will be shared. 10 | /// 11 | /// 12 | public interface IContainer : IDisposable 13 | { 14 | [EditorBrowsable(EditorBrowsableState.Never)] 15 | TResult Run(Func func, TParam param); 16 | Owned Resolve(); 17 | } 18 | 19 | /// Implement this interface to tell StrongInject to generate implementations for and . 20 | /// You can implement this interface multiple times for different values of , and Single Instances will be shared. 21 | public interface IAsyncContainer : IAsyncDisposable 22 | { 23 | [EditorBrowsable(EditorBrowsableState.Never)] 24 | ValueTask RunAsync(Func> func, TParam param); 25 | ValueTask> ResolveAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /StrongInject/IFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// A type implementing this interface can be registered as a factory for . 7 | /// It can be registered either via the , 8 | /// or by marking a field/property with the and configuring its . 9 | /// 10 | /// In general it's easier to use factory methods instead (methods marked with the . 11 | /// Only use this if you either need control over the lifetime of the factory, 12 | /// or you need to control disposal of the created type. 13 | /// 14 | /// For example, you may want to implement this if you would like to cache and reuse instances after they've been released. 15 | /// 16 | /// 17 | public interface IFactory 18 | { 19 | T Create(); 20 | 21 | void Release(T instance) 22 | #if !NETSTANDARD2_0 23 | => Helpers.Dispose(instance) 24 | #endif 25 | ; 26 | } 27 | 28 | /// 29 | /// A type implementing this interface can be registered as an async factory for . 30 | /// It can be registered either via the , 31 | /// or by marking a field/property with the and configuring its . 32 | /// 33 | /// In general it's easier to use factory methods instead (methods marked with the . 34 | /// Only use this if you either need control over the lifetime of the factory, 35 | /// or you need to control disposal of the created type. 36 | /// 37 | /// For example, you may want to implement this if you would like to cache and reuse instances after they've been released. 38 | /// 39 | /// 40 | public interface IAsyncFactory 41 | { 42 | ValueTask CreateAsync(); 43 | 44 | ValueTask ReleaseAsync(T instance) 45 | #if !NETSTANDARD2_0 46 | => Helpers.DisposeAsync(instance) 47 | #endif 48 | ; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /StrongInject/IRequiresInitialization.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Implement this interface to inform StrongInject that a type will need initialization once it is instantiated before it is ready to be used. 7 | /// 8 | public interface IRequiresInitialization 9 | { 10 | void Initialize(); 11 | } 12 | 13 | /// 14 | /// Implement this interface to inform StrongInject that a type will need asynchronous initialization once it is instantiated before it is ready to be used. 15 | /// 16 | public interface IRequiresAsyncInitialization 17 | { 18 | ValueTask InitializeAsync(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /StrongInject/InstanceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this attribute to register a field or property, so it can be used in resolution. 7 | /// 8 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 9 | public sealed class InstanceAttribute : Attribute 10 | { 11 | /// 12 | /// 13 | /// 14 | /// Options to configure how the field/property should be registered. 15 | /// See the documentation on . 16 | public InstanceAttribute(Options options = Options.Default) 17 | { 18 | Options = options; 19 | } 20 | 21 | public Options Options { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /StrongInject/Modules/CollectionsModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices.ComTypes; 3 | 4 | namespace StrongInject.Modules 5 | { 6 | /// 7 | /// Provides registrations for , and . 8 | /// 9 | public static class CollectionsModule 10 | { 11 | [Factory(Scope.InstancePerDependency)] public static IEnumerable CreateEnumerable(T[] arr) => arr; 12 | [Factory(Scope.InstancePerDependency)] public static IReadOnlyList CreateReadOnlyList(T[] arr) => arr; 13 | [Factory(Scope.InstancePerDependency)] public static IReadOnlyCollection CreateReadOnlyCollection(T[] arr) => arr; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /StrongInject/Modules/LazyModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject.Modules 4 | { 5 | /// 6 | /// Provides a registration for . 7 | /// 8 | public static class LazyModule 9 | { 10 | [Factory(Scope.InstancePerResolution)] public static Lazy CreateLazy(Func func) => new Lazy(func); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /StrongInject/Modules/SafeImmutableArrayModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace StrongInject.Modules 4 | { 5 | /// 6 | /// Provides a registration for . 7 | /// 8 | /// This copies the resolved array into an . 9 | /// If you require a non-copying implementation for performance, use instead. 10 | /// 11 | public static class SafeImmutableArrayModule 12 | { 13 | [Factory(Scope.InstancePerDependency)] public static ImmutableArray CreateImmutableArray(T[] arr) => arr.ToImmutableArray(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /StrongInject/Modules/StandardModule.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Modules 2 | { 3 | /// 4 | /// Provides registrations for the most common and least opinionated inbuilt modules. 5 | /// 6 | [RegisterModule(typeof(CollectionsModule))] 7 | [RegisterModule(typeof(LazyModule))] 8 | [RegisterModule(typeof(ValueTupleModule))] 9 | public class StandardModule 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /StrongInject/Modules/UnsafeImmutableArrayModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace StrongInject.Modules 5 | { 6 | /// 7 | /// Provides a registration for . 8 | /// 9 | /// WARNING: 10 | /// 11 | /// This casts the resolved array directly to an . 12 | /// This is fine if the user hasn't provided a custom registration for T[], since the array will be unique for this dependency. 13 | /// However if the user does provide a custom registration, it is possible the ImmutableArray will be mutated by someone holding a reference to the original array. 14 | /// 15 | /// If it's possible a custom registration for T[] exists, use instead. 16 | /// 17 | public static class UnsafeImmutableArrayModule 18 | { 19 | [Factory(Scope.InstancePerDependency)] public static ImmutableArray UnsafeCreateImmutableArray(T[] arr) => Unsafe.As>(ref arr); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StrongInject/Modules/ValueTupleModule.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject.Modules 2 | { 3 | /// 4 | /// Provides registrations for tuples for all arities from 2 till 10. 5 | /// 6 | public static class ValueTupleModule 7 | { 8 | [Factory(Scope.InstancePerDependency)] public static (T1, T2) CreateValueTuple(T1 a, T2 b) => (a, b); 9 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3) CreateValueTuple(T1 a, T2 b, T3 c) => (a, b, c); 10 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4) CreateValueTuple(T1 a, T2 b, T3 c, T4 d) => (a, b, c, d); 11 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e) => (a, b, c, d, e); 12 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5, T6) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e, T6 f) => (a, b, c, d, e, f); 13 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5, T6, T7) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e, T6 f, T7 g) => (a, b, c, d, e, f, g); 14 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5, T6, T7, T8) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e, T6 f, T7 g, T8 h) => (a, b, c, d, e, f, g, h); 15 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5, T6, T7, T8, T9) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e, T6 f, T7 g, T8 h, T9 i) => (a, b, c, d, e, f, g, h, i); 16 | [Factory(Scope.InstancePerDependency)] public static (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) CreateValueTuple(T1 a, T2 b, T3 c, T4 d, T5 e, T6 f, T7 g, T8 h, T9 i, T10 j) => (a, b, c, d, e, f, g, h, i, j); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /StrongInject/ObsoleteTypes/FactoryRegistrationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace StrongInject 5 | { 6 | [Obsolete("Use RegisterFactoryAttribute instead", error: true)] 7 | [EditorBrowsable(EditorBrowsableState.Never)] 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 9 | public class FactoryRegistrationAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /StrongInject/ObsoleteTypes/ModuleRegistrationAtribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace StrongInject 5 | { 6 | [Obsolete("Use RegisterModuleAttribute instead", error: true)] 7 | [EditorBrowsable(EditorBrowsableState.Never)] 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 9 | public class ModuleRegistrationAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /StrongInject/ObsoleteTypes/RegistrationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace StrongInject 5 | { 6 | [Obsolete("Use RegisterAttribute instead", error: true)] 7 | [EditorBrowsable(EditorBrowsableState.Never)] 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 9 | public class RegistrationAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /StrongInject/Owned.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace StrongInject 6 | { 7 | /// 8 | /// 9 | /// A disposable wrapper for an instance of . 10 | /// 11 | /// 12 | /// Make sure to dispose this once you are done using . This will dispose and all dependencies of it. 13 | /// 14 | /// 15 | /// Do not dispose directly as that will not dispose its dependencies. 16 | /// 17 | /// 18 | /// Do not use after this is disposed. 19 | /// 20 | /// 21 | public interface IOwned : IDisposable 22 | { 23 | T Value { get; } 24 | } 25 | 26 | /// 27 | public sealed class Owned : IOwned 28 | { 29 | private Action? _dispose; 30 | 31 | public Owned(T value, Action? dispose) 32 | { 33 | Value = value; 34 | _dispose = dispose; 35 | } 36 | 37 | public T Value { get; } 38 | 39 | public void Dispose() 40 | { 41 | Interlocked.Exchange(ref _dispose, null)?.Invoke(); 42 | } 43 | } 44 | 45 | /// 46 | /// 47 | /// An async disposable wrapper for an instance of . 48 | /// 49 | /// 50 | /// Make sure to dispose this once you are done using . This will dispose and all dependencies of it. 51 | /// 52 | /// 53 | /// Do not dispose directly as that will not dispose its dependencies. 54 | /// 55 | /// 56 | /// Do not use after this is disposed. 57 | /// 58 | /// 59 | public interface IAsyncOwned : IAsyncDisposable 60 | { 61 | T Value { get; } 62 | } 63 | 64 | /// 65 | public sealed class AsyncOwned : IAsyncOwned 66 | { 67 | private Func? _dispose; 68 | 69 | public AsyncOwned(T value, Func? dispose) 70 | { 71 | Value = value; 72 | _dispose = dispose; 73 | } 74 | 75 | public T Value { get; } 76 | 77 | public ValueTask DisposeAsync() 78 | { 79 | return Interlocked.Exchange(ref _dispose, null)?.Invoke() ?? default; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /StrongInject/RegisterDecoratorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this attribute to register a decorator for a type. 7 | /// When resolving an instance of the type, once the decorated type has been resolved as normal it will be wrapped by an instance of the decorator. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 10 | public sealed class RegisterDecoratorAttribute : Attribute 11 | { 12 | /// 13 | /// 14 | /// 15 | /// The type to use as a decorator. Must be a subtype of . 16 | /// The type which will be decorated (wrapped) by . 17 | /// 18 | public RegisterDecoratorAttribute(Type type, Type decoratedType, DecoratorOptions decoratorOptions = DecoratorOptions.Default) 19 | { 20 | Type = type; 21 | DecoratedType = decoratedType; 22 | DecoratorOptions = decoratorOptions; 23 | } 24 | 25 | public Type Type { get; } 26 | public Type DecoratedType { get; } 27 | public DecoratorOptions DecoratorOptions { get; } 28 | } 29 | 30 | /// 31 | /// Use this attribute to register a decorator for a type. 32 | /// When resolving an instance of the type, once the decorated type has been resolved as normal it will be wrapped by an instance of the decorator. 33 | /// The type to use as a decorator. Must be a subtype of . 34 | /// The type which will be decorated (wrapped) by . 35 | /// 36 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 37 | public sealed class RegisterDecoratorAttribute : Attribute where TDecorator : TDecorated 38 | { 39 | public RegisterDecoratorAttribute(DecoratorOptions decoratorOptions = DecoratorOptions.Default) 40 | { 41 | DecoratorOptions = decoratorOptions; 42 | } 43 | 44 | public DecoratorOptions DecoratorOptions { get; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StrongInject/RegisterFactoryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this attribute to register a type implementing or as a factory for T, 7 | /// meaning T will be resolved by resolving an instance of the factory, and then calling or . 8 | /// 9 | /// It's usually simpler to write a factory method and mark it with the . 10 | /// Use this only if you need tight control over the lifetime of your factory, or if you want to control disposal of the factory target. 11 | /// 12 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 13 | public sealed class RegisterFactoryAttribute : Attribute 14 | { 15 | /// 16 | /// 17 | /// 18 | /// The factory to register. Must implement or . 19 | /// The scope of the factory - i.e. how often should a new factory be created? 20 | /// The scope of the factory target - i.e. how often should or be called? 21 | public RegisterFactoryAttribute(Type factoryType, Scope factoryScope = Scope.InstancePerResolution, Scope factoryTargetScope = Scope.InstancePerResolution) 22 | { 23 | FactoryType = factoryType; 24 | FactoryScope = factoryScope; 25 | FactoryTargetScope = factoryTargetScope; 26 | } 27 | 28 | public Type FactoryType { get; } 29 | public Scope FactoryScope { get; } 30 | public Scope FactoryTargetScope { get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /StrongInject/RegisterModuleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StrongInject 4 | { 5 | /// 6 | /// Use this attribute to import registrations defined on a different type. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 9 | public sealed class RegisterModuleAttribute : Attribute 10 | { 11 | /// 12 | /// The module to register 13 | /// An optional list of types which should not be resolved via this module 14 | public RegisterModuleAttribute(Type type, params Type[] exclusionList) 15 | { 16 | Type = type; 17 | ExclusionList = exclusionList; 18 | } 19 | 20 | public Type Type { get; } 21 | public Type[] ExclusionList { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /StrongInject/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace StrongInject 2 | { 3 | public enum Scope 4 | { 5 | /// 6 | /// Default scope. 7 | /// A single instance is shared between all dependencies created for a single resolution. 8 | /// For example if 'A' depends on 'B' and 'C', and 'B' and 'C' both depend on an instance of 'D', 9 | /// then when 'A' is resolved 'B' and 'C' will share the same instance of 'D'. 10 | /// 11 | /// Note every SingleInstance dependency defines a separate resolution, 12 | /// so if 'B' and/or 'C' are SingleInstance they would not share an instance of 'D'. 13 | /// 14 | /// Similarly every lambda defines a separate resolution, so if A depends on Func<B>, 15 | /// then each time Func<B> is invoked a fresh instance of both B and D will be created. 16 | /// 17 | InstancePerResolution = 0, 18 | 19 | /// 20 | /// A new instance is created for every usage. 21 | /// For example even if type 'B' appears twice in the constructor of 'A', 22 | /// two different instances will be passed into the constructor. 23 | /// 24 | InstancePerDependency = 1, 25 | 26 | /// 27 | /// A single instance will be shared across all dependencies, from any resolution 28 | /// 29 | SingleInstance = 2, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /StrongInject/StrongInject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | True 6 | Debug;Release;Wpf;All 7 | 8 | 9 | 10 | $(StrongInjectVersion) 11 | StrongInject 12 | compile time dependency injection for C# 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /StrongInject/StrongInjectException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace StrongInject 6 | { 7 | public sealed class StrongInjectException : Exception 8 | { 9 | public StrongInjectException(string message) : base(message) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /StrongInject/ValueTaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace StrongInject 4 | { 5 | internal static class ValueTaskExtensions 6 | { 7 | public static ValueTask AsValueTask(this ValueTask valueTask) 8 | { 9 | if (valueTask.IsCompletedSuccessfully) 10 | { 11 | valueTask.GetAwaiter().GetResult(); 12 | return default; 13 | } 14 | 15 | return new ValueTask(valueTask.AsTask()); 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /StrongInject/buildTransitive/StrongInject.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Utilities/GeneratorTestsUpdater/GeneratorTestsUpdater.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | Debug;Release;Wpf;All 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | TargetFramework=netstandard2.0 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/Containers.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Of Contents 4 | 5 | - [Containers](#containers) 6 | - [Run/RunAsync](#runrunasync) 7 | - [Resolve/ResolveAsync](#resolveresolveasync) 8 | - [Disposal](#disposal) 9 | 10 | 11 | 12 | # Containers 13 | 14 | A container is esentially a factory that knows how to provide an instance of a type on demand, and then dispose of it once it's no longer needed. 15 | 16 | You can create a container by inheriting from `IContainer`, or `IAsyncContainer`. The latter is necessary if resolution has to be asynchronous. 17 | 18 | You need to make sure that the container is capable of [resolving](https://github.com/YairHalberstadt/stronginject/wiki/Resolution) `T` by providing suitable [registrations](https://github.com/YairHalberstadt/stronginject/wiki/Registration). Otherwise you will get a compile time error. 19 | 20 | There are two possible ways to use the container: 21 | 22 | ## Run/RunAsync 23 | 24 | These methods take a delegate with 1 parameter of type `T`, resolvee an instance of `T` and then call the delegate. Once the delegate is complete, they dispose of any dependencies that were created as part of the resolution. 25 | 26 | For example: 27 | 28 | ```csharp 29 | using StrongInject; 30 | 31 | [Register(typeof(A))] 32 | public partial class MyContainer : IContainer {} 33 | 34 | public class A : IDisposable { public void Dispose() { } } 35 | 36 | var myContainer = new MyContainer(); 37 | myContainer.Run(a => Console.WriteLine($"We've resolved an instance of A: {a.ToString()}!!")); 38 | ``` 39 | 40 | You can also return a result from `Run` and use that: 41 | 42 | ```csharp 43 | using StrongInject; 44 | 45 | [Register(typeof(A))] 46 | public partial class MyContainer : IContainer { } 47 | 48 | public class A : IDisposable { public void Dispose() { } } 49 | 50 | var myContainer = new MyContainer(); 51 | var aString = myContainer.Run(a => a.ToString()); 52 | Console.WriteLine($"We've resolved an instance of A: {aString}!!"); 53 | ``` 54 | 55 | Either way, you must make sure that the resolved type, and any of its dependencies, do not escape the scope of the delegate, or you may end up using them after they are disposed. 56 | 57 | ## Resolve/ResolveAsync 58 | 59 | In some cases you need greater control over the lifetime of the resolved type, and when it is resolved. In such cases, you can call `Resolve`/`ResolveAsync`. These methods return an `Owned`/`AsyncOwned`. You can access the resolved instance via `Owned.Value`/`AsyncOwned.Value` and must manually dispose of `T` and all its dependencies by calling `Owned.Dispose()`/`AsyncOwned.DisposeAsync()`. 60 | Note that it is not sufficient to dispose `T` directly, as that will not dispose of resolved dependencies. 61 | 62 | ## Disposal 63 | 64 | Once you are finished using your container, make sure to dispose of it to dispose of any Single Instance dependencies. 65 | -------------------------------------------------------------------------------- /docs/Registration/BestRegistration.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Of Contents 4 | 5 | - [Best Registration](#best-registration) 6 | 7 | 8 | 9 | # Best Registration 10 | 11 | You can register multiple registrations for a type `T` which allows you to resolve `T[]` and other collections of `T`. However, when resolving just one instance of `T`, StrongInject requires that there is a *best registration* for `T`, or it will error. 12 | 13 | A *best registration* is defined as follows: 14 | 15 | 1. Any registration declared directly on a module/container is *better* than registrations declared on other modules and imported by the module/container. 16 | 2. If there is a single registration which is *better* than all other registrations it is considered the *best registration*. Else there is no *best registration*. 17 | 18 | So for example, imagine there is a Container, and two modules, ModuleA and ModuleB. 19 | 20 | If all three define a registration for SomeInterface, then the one defined on the container will always be the *best registration*. 21 | 22 | If just ModuleA and ModuleB define a registration for SomeInterface then things will depend: 23 | 24 | - If Container imports both modules, resolving SomeInterface will always result in an error (even if ModuleA imports ModuleB or vice versa). 25 | - If Container imports ModuleA, and ModuleA imports ModuleB, then ModuleA's registration will be the *best registration*. 26 | - If Container imports ModuleB, and ModuleB imports ModuleA, then ModuleB's registration will be the *best registration*. 27 | 28 | To fix errors as a result of multiple registrations for a type, the simplest thing to do is to add a single *best registration* directly to the container. If the container already has multiple registrations for the type, then move those registrations to a separate module and import them. 29 | -------------------------------------------------------------------------------- /docs/Registration/Decorators.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Of Contents 4 | 5 | - [Decorators](#decorators) 6 | - [Decorator Registration](#decorator-registration) 7 | - [Decorator Type Registration](#decorator-type-registration) 8 | - [Decorator Factory Method Registration](#decorator-factory-method-registration) 9 | - [Disposal](#disposal) 10 | 11 | 12 | 13 | # Decorators 14 | 15 | A Decorator is different to other registrations, in that it does not provide an instance of a type, but rather wraps/modifies an *underlying* instance of a type, which is resolved in the normal manner. 16 | 17 | If multiple decorators are registered for a type, all of them will be applied, by wrapping one decorator in another, onion style. The order in which decorators wrap each other is deterministic but an implementation detail - you should not rely on this order. 18 | 19 | Decorators are not applied to parameters of delegates, or to `[Instance]` fields and properties with Options.DoNotDecorate applied. They are applied to everything else. 20 | 21 | ## Decorator Registration 22 | 23 | There are two ways to register decorators. 24 | 25 | ### [Decorator Type Registration](https://github.com/YairHalberstadt/stronginject/wiki/DecoratorTypeRegistration) 26 | 27 | You can register a type as a decorator for an interface it implements, if its constructor has exactly one parameter whose type is the interface. 28 | 29 | ### Decorator Factory Method Registration 30 | 31 | You can register a method returning `T` as a decorator of `T` if it has exactly one parameter of type `T`. 32 | 33 | ## Disposal 34 | 35 | Decorators are not disposed by default, for a number of reasons: 36 | 1. In many cases a decorator implements `IDisposable` as the interface requires it, but does not actually require disposal. 37 | 2. In many cases a decorator will delegate to the underlying's `Dispose` method: 38 | 1. Since the underlying is disposed separately, this can lead to double disposal. 39 | 2. The underlying may be an `Instance` field or property, which should never be disposed. 40 | 3. In many cases a `DecoratorFactory` may return the same instance as was passed in, also leading to issues 2.i and 2.ii. 41 | 42 | If your decorator needs to be disposed, make sure it does not dispose the underlying instance, only resources it owns directly. Use `DecoratorOptions.Dispose` to mark it as requiring disposal. 43 | 44 | Decorators are disposed from outermost to innermost, followed by the underlying instance. 45 | -------------------------------------------------------------------------------- /docs/Registration/FactoryMethodRegistration.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Of Contents 4 | 5 | - [Factory Method Registration](#factory-method-registration) 6 | - [Generic Factory Methods](#generic-factory-methods) 7 | - [Usage in Modules](#usage-in-modules) 8 | - [Example](#example) 9 | 10 | 11 | 12 | # Factory Method Registration 13 | 14 | A method may be marked as a Factory Method by applying the `[Factory]` attribute: 15 | 16 | ```csharp 17 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 18 | public class FactoryAttribute : Attribute 19 | { 20 | public FactoryAttribute(Scope scope = Scope.InstancePerResolution); 21 | public Scope Scope { get; } 22 | } 23 | ``` 24 | 25 | A factory method is registered as a provider for the return type of the factory method. If the return type is `Task`/`ValueTask` then it is a provider for `T`, and can only be resolved asynchronously. 26 | 27 | A new instance will be created by resolving all the parameters of the method and calling the method. The `scope` parameter controls how often the factory method will be called. 28 | 29 | ## Generic Factory Methods 30 | 31 | A Factory Method can be generic, and can have type parameter constraints. 32 | 33 | It is an error if not all the type parameters are used in the return type. 34 | 35 | When resolving an instance of type `T`, StrongInject tries to see if we can construct `T` out of the return type by substituting type parameters for concrete types. If so we check to make sure these substitutions match the type parameter constraints. If the constraints do match, the constructed method with the type parameters substituted is used as a registration for `T`. 36 | 37 | ## Usage in Modules 38 | 39 | A Factory Method in a module must be `public` and `static` in order to be exported. For it to be inherited it can also be `protected`. Factory Methods in containers can be `private` and `instance` if desired. 40 | 41 | ## Example 42 | 43 | ```csharp 44 | using StrongInject; 45 | using System; 46 | using System.Collections.Generic; 47 | 48 | public class A { } 49 | 50 | public partial class Container : IContainer> 51 | { 52 | [Factory(Scope.SingleInstance)] private A CreateA() => new A(); // Registration for A 53 | [Factory] private List CreateList(T t) => new List { t }; // Registration for List by substituting T for A 54 | [Factory] private List CreateList() where T : struct => new List { new T() }; // Not a registration for List because substituting T for A does not match the struct constraint 55 | } 56 | 57 | var container = new Container(); 58 | container.Run(x => Console.WriteLine(x.ToString())); // Resolves a new instance `A` by calling CreateA(), and calls CreateList(A) with it as a parameter. 59 | container.Run(x => Console.WriteLine(x.ToString())); // Reuses the single instance of `A`, and calls CreateList(A) with it as a parameter. 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/Registration/Scopes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Of Contents 4 | 5 | - [Scopes](#scopes) 6 | - [List Of Scopes](#list-of-scopes) 7 | - [InstancePerResolution](#instanceperresolution) 8 | - [InstancePerDependency](#instanceperdependency) 9 | - [SingleInstance](#singleinstance) 10 | - [Disposal](#disposal) 11 | 12 | 13 | 14 | # Scopes 15 | 16 | All registrations have a particular Scope. This tells StrongInject how long an instance resolved using that registration should live for, and how widely it should be shared. 17 | 18 | ## List Of Scopes 19 | 20 | There are currently 3 possible scopes. The default scope is `InstancePerResolution` but the scope can be modified per registration via the `Scope` enum. 21 | 22 | ### InstancePerResolution 23 | 24 | The default scope. 25 | 26 | A single instance is shared between all dependencies created for a single resolution. 27 | 28 | For example if `A` depends on `B` and `C`, and `B` and `C` both depend on an instance of `D`, then when `A` is resolved `B` and `C` will share the same instance of `D`. 29 | 30 | Note every SingleInstance dependency defines a separate resolution, so if `B` and/or `C` are `SingleInstance` they would not share an instance of `D`. Similarly every lambda defines a separate resolution, so if `A` depends on `Func`, then each time `Func` is invoked a fresh instance of both `B` and `D` will be created. 31 | 32 | ### InstancePerDependency 33 | 34 | A new instance is created for every usage. 35 | 36 | For example even if type `B` appears twice in the constructor of `A`, two different instances will be passed into the constructor. 37 | 38 | ### SingleInstance 39 | 40 | A single instance will be shared across all dependencies, from any resolution. 41 | 42 | ## Disposal 43 | 44 | A `SingleInstance` instance will be disposed when the container is disposed (except [`Instance`](https://github.com/YairHalberstadt/stronginject/wiki/InstanceRegistration) fields and properties which are never disposed). 45 | 46 | An `InstancePerResolution` or `InstancePerDependency` instance will be disposed when StrongInject knows it can no longer be used. This means: 47 | 48 | - If it is a dependency of a `SingleInstance` it will be disposed when the container is disposed. 49 | 50 | - Else, if it was created as part of a call to `Run`/`RunAsync`, it will be disposed, once the delegate paramater to `Run`/`RunAsync` completes. 51 | 52 | - Else, if it was created as part of a call to `Resolve`/`ResolveAsync`, it will be disposed once the returned `IOwned`/`IOwnedAsync` is disposed. 53 | -------------------------------------------------------------------------------- /resources/logo-circle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YairHalberstadt/stronginject/f877bde7da049fd0c6952de7632b6b7aa72b0788/resources/logo-circle.ico -------------------------------------------------------------------------------- /resources/logo-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YairHalberstadt/stronginject/f877bde7da049fd0c6952de7632b6b7aa72b0788/resources/logo-circle.png -------------------------------------------------------------------------------- /resources/logo-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/logo-horizontal.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YairHalberstadt/stronginject/f877bde7da049fd0c6952de7632b6b7aa72b0788/resources/logo-horizontal.ico -------------------------------------------------------------------------------- /resources/logo-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YairHalberstadt/stronginject/f877bde7da049fd0c6952de7632b6b7aa72b0788/resources/logo-horizontal.png -------------------------------------------------------------------------------- /resources/logo-horizontal.svg: -------------------------------------------------------------------------------- 1 | INJECTSTRNG --------------------------------------------------------------------------------