diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a4ab645..67d247d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,9 +1,11 @@ version: 2 updates: + - package-ecosystem: "nuget" directory: "/" schedule: interval: "daily" + - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9775990..09c5d76 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,3 +1,5 @@ +# https://github.com/github/codeql +# https://github.com/github/codeql-action name: CodeQL analysis on: @@ -9,12 +11,29 @@ on: jobs: analyze: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 + - name: Checkout source + uses: actions/checkout@v3 + + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v1 with: - fetch-depth: 2 - - uses: github/codeql-action/init@v1 + dotnet-version: '6.0.102' + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 with: + queries: security-and-quality languages: csharp - - uses: github/codeql-action/autobuild@v1 - - uses: github/codeql-action/analyze@v1 + + - name: Install dependencies + working-directory: src + run: dotnet restore + + - name: Build solution + working-directory: src + run: dotnet build --no-restore + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index b80b71f..eeb3b96 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -21,10 +21,10 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v3 - - name: Setup .NET Core 5.0 SDK + - name: Setup .NET Core 6.0 SDK uses: actions/setup-dotnet@v2 with: - dotnet-version: '5.0.x' + dotnet-version: '6.0.x' source-url: https://nuget.pkg.github.com/sungam3r/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index e32b637..0f19fcb 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -27,10 +27,10 @@ jobs: version="${github_ref:10}" echo version=$version echo "version=$version" >> $GITHUB_ENV - - name: Setup .NET Core 5.0 SDK + - name: Setup .NET Core 6.0 SDK uses: actions/setup-dotnet@v2 with: - dotnet-version: '5.0.x' + dotnet-version: '6.0.x' source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.NUGET_API_KEY}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86bd7d6..83f5c93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,17 +31,13 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v3 - - name: Setup .NET Core 3.1 SDK + - name: Setup .NET Core SDKs uses: actions/setup-dotnet@v2 with: - dotnet-version: '3.1.x' - source-url: https://nuget.pkg.github.com/sungam3r/index.json - env: - NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - - name: Setup .NET Core 5.0 SDK - uses: actions/setup-dotnet@v2 - with: - dotnet-version: '5.0.x' + dotnet-version: | + 3.1.x + 5.0.x + 6.0.102 source-url: https://nuget.pkg.github.com/sungam3r/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} @@ -52,8 +48,7 @@ jobs: if: ${{ startsWith(matrix.os, 'ubuntu') }} working-directory: src run: | - dotnet tool install -g dotnet-format --version 6.0.243104 --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json - dotnet format --check -v diag --fix-style warn --fix-analyzers warn || (echo "Run 'dotnet format' to fix formatting issues" && exit 1) + dotnet format --no-restore --verify-no-changes --severity warn || (echo "Run 'dotnet format' to fix issues" && exit 1) - name: Build solution [Release] working-directory: src run: dotnet build --no-restore -c Release @@ -67,7 +62,7 @@ jobs: if: ${{ startsWith(matrix.os, 'ubuntu') }} uses: codecov/codecov-action@v2.1.0 with: - files: .coverage/SteroidsDI.Tests/coverage.netcoreapp3.1.opencover.xml + files: .coverage/SteroidsDI.Tests/coverage.net6.opencover.xml buildcheck: needs: diff --git a/src/Example/Models/EntryPoint.cs b/src/Example/Models/EntryPoint.cs index 8fa63f8..4e4a31f 100644 --- a/src/Example/Models/EntryPoint.cs +++ b/src/Example/Models/EntryPoint.cs @@ -1,50 +1,47 @@ -using System; +namespace Example; -namespace Example +public interface IEntryPoint { - public interface IEntryPoint + int DoSomethingImportant(); +} + +public class EntryPoint : IEntryPoint +{ + //private IRepository _repository; - this is an example of what you wanted to do but got the error below + private readonly Defer _repository1; + private readonly Func _repository2; + private readonly IRepositoryFactory _repoFactory; + + public EntryPoint( + // Error while validating the service descriptor 'ServiceType: Example.IEntryPoint Lifetime: Singleton ImplementationType: Example.EntryPoint': + // Cannot consume scoped service 'Example.IRepository' from singleton 'Example.IEntryPoint'. + //IRepository repository + Defer repository1, + Func repository2, + IRepositoryFactory repoFactory) { - int DoSomethingImportant(); + _repository1 = repository1; + _repository2 = repository2; + _repoFactory = repoFactory; } - public class EntryPoint : IEntryPoint + public int DoSomethingImportant() { - //private IRepository _repository; - this is an example of what you wanted to do but got the error below - private readonly Defer _repository1; - private readonly Func _repository2; - private readonly IRepositoryFactory _repoFactory; - - public EntryPoint( - // Error while validating the service descriptor 'ServiceType: Example.IEntryPoint Lifetime: Singleton ImplementationType: Example.EntryPoint': - // Cannot consume scoped service 'Example.IRepository' from singleton 'Example.IEntryPoint'. - //IRepository repository - Defer repository1, - Func repository2, - IRepositoryFactory repoFactory) - { - _repository1 = repository1; - _repository2 = repository2; - _repoFactory = repoFactory; - } + // All 3 APIs return the same instance + var repo1 = _repository1.Value; + var repo2 = _repository2(); + var repo3 = _repoFactory.GetPersonsRepo(); - public int DoSomethingImportant() - { - // All 3 APIs return the same instance - var repo1 = _repository1.Value; - var repo2 = _repository2(); - var repo3 = _repoFactory.GetPersonsRepo(); - - if (!ReferenceEquals(repo1, repo2) || !ReferenceEquals(repo1, repo3)) - throw new InvalidOperationException(); - - var persons = repo1.GetPersons(); + if (!ReferenceEquals(repo1, repo2) || !ReferenceEquals(repo1, repo3)) + throw new InvalidOperationException(); - foreach (var person in persons) - { - Console.WriteLine($"{person.Name} ({person.Age})"); - } + var persons = repo1.GetPersons(); - return persons.Count; + foreach (var person in persons) + { + Console.WriteLine($"{person.Name} ({person.Age})"); } + + return persons.Count; } } diff --git a/src/Example/Models/IRepositoryFactory.cs b/src/Example/Models/IRepositoryFactory.cs index d8af0c7..b80f5c9 100644 --- a/src/Example/Models/IRepositoryFactory.cs +++ b/src/Example/Models/IRepositoryFactory.cs @@ -1,8 +1,7 @@ -namespace Example +namespace Example; + +public interface IRepositoryFactory { - public interface IRepositoryFactory - { - // method name does not matter - IRepository GetPersonsRepo(); - } + // method name does not matter + IRepository GetPersonsRepo(); } diff --git a/src/Example/Models/Person.cs b/src/Example/Models/Person.cs index d69eaf0..4879793 100644 --- a/src/Example/Models/Person.cs +++ b/src/Example/Models/Person.cs @@ -1,9 +1,8 @@ -namespace Example +namespace Example; + +public class Person { - public class Person - { - public string? Name { get; set; } + public string? Name { get; set; } - public int Age { get; set; } - } + public int Age { get; set; } } diff --git a/src/Example/Models/Repository.cs b/src/Example/Models/Repository.cs index bbe6e1c..b406b18 100644 --- a/src/Example/Models/Repository.cs +++ b/src/Example/Models/Repository.cs @@ -1,38 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Threading; +namespace Example; -namespace Example +public interface IRepository { - public interface IRepository + List GetPersons(); +} + +public class Repository : IRepository +{ + public Repository() { - List GetPersons(); + Console.WriteLine("Initializing repository"); + Thread.Sleep(3000); + Console.WriteLine("Repository initialized"); } - public class Repository : IRepository + public List GetPersons() { - public Repository() + return new List { - Console.WriteLine("Initializing repository"); - Thread.Sleep(3000); - Console.WriteLine("Repository initialized"); - } - - public List GetPersons() - { - return new List + new Person + { + Name = "Chip", + Age = 31 + }, + new Person { - new Person - { - Name = "Chip", - Age = 31 - }, - new Person - { - Name = "Dale", - Age = 32 - } - }; - } + Name = "Dale", + Age = 32 + } + }; } } diff --git a/src/Example/PersonsController.cs b/src/Example/PersonsController.cs index 81c4ef8..8cddca9 100644 --- a/src/Example/PersonsController.cs +++ b/src/Example/PersonsController.cs @@ -1,18 +1,17 @@ using Microsoft.AspNetCore.Mvc; -namespace Example -{ - [Route("api/[controller]")] - public class PersonsController : ControllerBase - { - private readonly IEntryPoint _entry; +namespace Example; - public PersonsController(IEntryPoint entry) - { - _entry = entry; - } +[Route("api/[controller]")] +public class PersonsController : ControllerBase +{ + private readonly IEntryPoint _entry; - [HttpGet] - public object Get() => new { Count = _entry.DoSomethingImportant() }; + public PersonsController(IEntryPoint entry) + { + _entry = entry; } + + [HttpGet] + public object Get() => new { Count = _entry.DoSomethingImportant() }; } diff --git a/src/Example/Program.cs b/src/Example/Program.cs index 0930111..b0b1348 100644 --- a/src/Example/Program.cs +++ b/src/Example/Program.cs @@ -1,14 +1,10 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +namespace Example; -namespace Example +public class Program { - public class Program - { - public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); + public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); - } + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); } diff --git a/src/Example/Startup.cs b/src/Example/Startup.cs index 2aa9785..0b7d3a7 100644 --- a/src/Example/Startup.cs +++ b/src/Example/Startup.cs @@ -1,57 +1,50 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using SteroidsDI; -namespace Example +namespace Example; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - // register data models - services.AddSingleton(); - services.AddScoped(); - - // register DI stuff - services.AddDefer(); - services.AddFunc(); - services.AddFactory(); // implementation will be generated at runtime - services.AddHttpScope(); - services.Configure(Configuration.GetSection("Steroids")); - - // register standard stuff - services.AddControllers(); - } + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + // register data models + services.AddSingleton(); + services.AddScoped(); + + // register DI stuff + services.AddDefer(); + services.AddFunc(); + services.AddFactory(); // implementation will be generated at runtime + services.AddHttpScope(); + services.Configure(Configuration.GetSection("Steroids")); + + // register standard stuff + services.AddControllers(); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + app.UseDeveloperExceptionPage(); + } - app.UseRouting(); + app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); - endpoints.MapGet("/", async context => await context.Response.WriteAsync("Hello World!")); - }); - } + endpoints.MapGet("/", async context => await context.Response.WriteAsync("Hello World!")); + }); } } diff --git a/src/SteroidsDI.AspNetCore/AspNetCoreHttpScopeProvider.cs b/src/SteroidsDI.AspNetCore/AspNetCoreHttpScopeProvider.cs index de4e747..92576f0 100644 --- a/src/SteroidsDI.AspNetCore/AspNetCoreHttpScopeProvider.cs +++ b/src/SteroidsDI.AspNetCore/AspNetCoreHttpScopeProvider.cs @@ -1,27 +1,25 @@ -using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI.AspNetCore +namespace SteroidsDI.AspNetCore; + +/// for ASP.NET Core working with . +public sealed class AspNetCoreHttpScopeProvider : IScopeProvider { - /// for ASP.NET Core working with . - public sealed class AspNetCoreHttpScopeProvider : IScopeProvider + /// Gets scoped , for the current HTTP request. + /// The root object to obtain . + /// The scoped object or null if there is no current HTTP request. + public IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider) { - /// Gets scoped , for the current HTTP request. - /// The root object to obtain . - /// The scoped object or null if there is no current HTTP request. - public IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider) + var accessor = rootProvider.GetService(); + if (accessor != null) { - var accessor = rootProvider.GetService(); - if (accessor != null) - { - var context = accessor.HttpContext; - if (context != null) - return context.RequestServices; - } - - return null; + var context = accessor.HttpContext; + if (context != null) + return context.RequestServices; } + + return null; } } diff --git a/src/SteroidsDI.AspNetCore/Extensions/ServiceCollectionExtensions.cs b/src/SteroidsDI.AspNetCore/Extensions/ServiceCollectionExtensions.cs index ba4d75a..7f7e190 100644 --- a/src/SteroidsDI.AspNetCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/SteroidsDI.AspNetCore/Extensions/ServiceCollectionExtensions.cs @@ -2,19 +2,18 @@ using SteroidsDI.AspNetCore; using SteroidsDI.Core; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// Extension methods for . +public static class ServiceCollectionExtensions { - /// Extension methods for . - public static class ServiceCollectionExtensions + /// Register in DI as one of the possible implementations for . + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddHttpScope(this IServiceCollection services) { - /// Register in DI as one of the possible implementations for . - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddHttpScope(this IServiceCollection services) - { - services.AddHttpContextAccessor(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - return services; - } + services.AddHttpContextAccessor(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + return services; } } diff --git a/src/SteroidsDI.Core/Defer.cs b/src/SteroidsDI.Core/Defer.cs index 87e8a20..1ecb2e9 100644 --- a/src/SteroidsDI.Core/Defer.cs +++ b/src/SteroidsDI.Core/Defer.cs @@ -1,14 +1,13 @@ -namespace System // yes, this is exactly the namespace by analogy with Func +namespace System; // yes, this is exactly the namespace by analogy with Func + +/// +/// An analogue of for deferred resolving of the required object from a DI container. +/// Has a more explicit/simple API compared to Func. This type must be used for those constructor arguments +/// that requires deferred calculation of T. +/// +/// Dependency type. +public abstract class Defer : IDefer { - /// - /// An analogue of for deferred resolving of the required object from a DI container. - /// Has a more explicit/simple API compared to Func. This type must be used for those constructor arguments - /// that requires deferred calculation of T. - /// - /// Dependency type. - public abstract class Defer : IDefer - { - /// Get required dependency. - public abstract T Value { get; } - } + /// Get required dependency. + public abstract T Value { get; } } diff --git a/src/SteroidsDI.Core/GenericScope.cs b/src/SteroidsDI.Core/GenericScope.cs index bf92b33..45a14c3 100644 --- a/src/SteroidsDI.Core/GenericScope.cs +++ b/src/SteroidsDI.Core/GenericScope.cs @@ -1,25 +1,21 @@ -using System; -using System.Threading; +namespace SteroidsDI.Core; -namespace SteroidsDI.Core +/// +/// A helper class that provides access to the current scope stored in the AsyncLocal field. +/// Works in conjunction with . +/// +/// +/// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique +/// closed type, thereby providing its own storage, to which only it will have access. +/// +public static class GenericScope { - /// - /// A helper class that provides access to the current scope stored in the AsyncLocal field. - /// Works in conjunction with . - /// - /// - /// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique - /// closed type, thereby providing its own storage, to which only it will have access. - /// - public static class GenericScope - { - private static readonly AsyncLocal _currentScope = new AsyncLocal(); + private static readonly AsyncLocal _currentScope = new AsyncLocal(); - /// Gets or sets the current scope. - public static IDisposable? CurrentScope - { - get => _currentScope.Value; - set => _currentScope.Value = value; - } + /// Gets or sets the current scope. + public static IDisposable? CurrentScope + { + get => _currentScope.Value; + set => _currentScope.Value = value; } } diff --git a/src/SteroidsDI.Core/IDefer.cs b/src/SteroidsDI.Core/IDefer.cs index fe64f0a..82e79ce 100644 --- a/src/SteroidsDI.Core/IDefer.cs +++ b/src/SteroidsDI.Core/IDefer.cs @@ -1,15 +1,14 @@ -namespace System +namespace System; + +/// +/// Covariant interface for which is an analogue of +/// for deferred resolving of the required object from a DI container. +/// Has a more explicit/simple API compared to Func. This type must be used for those constructor arguments +/// that requires deferred calculation of T. +/// +/// Dependency type. +public interface IDefer { - /// - /// Covariant interface for which is an analogue of - /// for deferred resolving of the required object from a DI container. - /// Has a more explicit/simple API compared to Func. This type must be used for those constructor arguments - /// that requires deferred calculation of T. - /// - /// Dependency type. - public interface IDefer - { - /// Get required dependency. - T Value { get; } - } + /// Get required dependency. + T Value { get; } } diff --git a/src/SteroidsDI.Core/IScopeFactory.cs b/src/SteroidsDI.Core/IScopeFactory.cs index 01233e8..049f7ef 100644 --- a/src/SteroidsDI.Core/IScopeFactory.cs +++ b/src/SteroidsDI.Core/IScopeFactory.cs @@ -1,17 +1,14 @@ -using System; +namespace SteroidsDI.Core; -namespace SteroidsDI.Core +/// +/// A factory for creating scopes. Works in conjunction with . +/// +public interface IScopeFactory { - /// - /// A factory for creating scopes. Works in conjunction with . - /// - public interface IScopeFactory - { - /// Create scope. - /// - /// Scope object which should be destroyed at the end of scope. - /// It SHOULD NOT be null, but MAY be null. - /// - IDisposable CreateScope(); - } + /// Create scope. + /// + /// Scope object which should be destroyed at the end of scope. + /// It SHOULD NOT be null, but MAY be null. + /// + IDisposable CreateScope(); } diff --git a/src/SteroidsDI.Core/IScopeProvider.cs b/src/SteroidsDI.Core/IScopeProvider.cs index 0fe4146..e3a65e8 100644 --- a/src/SteroidsDI.Core/IScopeProvider.cs +++ b/src/SteroidsDI.Core/IScopeProvider.cs @@ -1,17 +1,14 @@ -using System; +namespace SteroidsDI.Core; -namespace SteroidsDI.Core +/// +/// Provider of scoped . The mission of this interface is not to CREATE something, +/// but to provide the required object, which was created one way or another before that. That is, it is an interface +/// for STORAGE of objects. This is its difference from . +/// +public interface IScopeProvider { - /// - /// Provider of scoped . The mission of this interface is not to CREATE something, - /// but to provide the required object, which was created one way or another before that. That is, it is an interface - /// for STORAGE of objects. This is its difference from . - /// - public interface IScopeProvider - { - /// Gets scoped , that is, one in which scoped dependencies can be resolved. - /// The root object, which the provider may need in order to calculate the current scope. - /// The scoped object or null if the provider does not have the current scope. - IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider); - } + /// Gets scoped , that is, one in which scoped dependencies can be resolved. + /// The root object, which the provider may need in order to calculate the current scope. + /// The scoped object or null if the provider does not have the current scope. + IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider); } diff --git a/src/SteroidsDI.Core/Scoped.cs b/src/SteroidsDI.Core/Scoped.cs index e0802db..d625db6 100644 --- a/src/SteroidsDI.Core/Scoped.cs +++ b/src/SteroidsDI.Core/Scoped.cs @@ -1,44 +1,41 @@ -using System; +namespace SteroidsDI.Core; -namespace SteroidsDI.Core +/// +/// Auxiliary class for creating and destroying scopes. These are sections of code within which +/// scoped dependencies are resolved. This class controls the state of , +/// initializing its initial state and cleaning/destroying it at the exit. +/// +/// +/// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique +/// closed type, thereby providing its own storage, to which only it will have access. +/// +public struct Scoped : IDisposable { /// - /// Auxiliary class for creating and destroying scopes. These are sections of code within which - /// scoped dependencies are resolved. This class controls the state of , - /// initializing its initial state and cleaning/destroying it at the exit. + /// Initializes a new instance of the . /// - /// - /// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique - /// closed type, thereby providing its own storage, to which only it will have access. - /// - public struct Scoped : IDisposable + /// A factory for creating scopes. + public Scoped(IScopeFactory scopeFactory) { - /// - /// Initializes a new instance of the . - /// - /// A factory for creating scopes. - public Scoped(IScopeFactory scopeFactory) - { - if (scopeFactory == null) - throw new ArgumentNullException(nameof(scopeFactory)); + if (scopeFactory == null) + throw new ArgumentNullException(nameof(scopeFactory)); - if (GenericScope.CurrentScope != null) - throw new InvalidOperationException($"The current scope of GenericScope<{typeof(T).Name}> is not null when trying to initialize it."); + if (GenericScope.CurrentScope != null) + throw new InvalidOperationException($"The current scope of GenericScope<{typeof(T).Name}> is not null when trying to initialize it."); - Scope = scopeFactory.CreateScope(); - GenericScope.CurrentScope = Scope; - } + Scope = scopeFactory.CreateScope(); + GenericScope.CurrentScope = Scope; + } - /// - /// Gets current scope. - /// - public IDisposable Scope { get; } + /// + /// Gets current scope. + /// + public IDisposable Scope { get; } - /// Dispose scope. - public void Dispose() - { - GenericScope.CurrentScope = null; - Scope?.Dispose(); // in some cases scopeFactory.CreateScope() MAY return null - } + /// Dispose scope. + public void Dispose() + { + GenericScope.CurrentScope = null; + Scope?.Dispose(); // in some cases scopeFactory.CreateScope() MAY return null } } diff --git a/src/SteroidsDI.Tests/Cases.Approval/ApiApprovalTests.cs b/src/SteroidsDI.Tests/Cases.Approval/ApiApprovalTests.cs index d68be05..17f00c0 100644 --- a/src/SteroidsDI.Tests/Cases.Approval/ApiApprovalTests.cs +++ b/src/SteroidsDI.Tests/Cases.Approval/ApiApprovalTests.cs @@ -1,32 +1,30 @@ -using System; using NUnit.Framework; using PublicApiGenerator; using Shouldly; using SteroidsDI.AspNetCore; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +/// Tests for checking changes to the public API. +/// +[TestFixture] +public class ApiApprovalTests { - /// Tests for checking changes to the public API. - /// - [TestFixture] - public class ApiApprovalTests + /// Check for changes to the public APIs. + /// The type used as a marker for the assembly whose public API change you want to check. + [TestCase(typeof(MicrosoftScopeFactory))] + [TestCase(typeof(IScopeProvider))] + [TestCase(typeof(AspNetCoreHttpScopeProvider))] + public void PublicApi(Type type) { - /// Check for changes to the public APIs. - /// The type used as a marker for the assembly whose public API change you want to check. - [TestCase(typeof(MicrosoftScopeFactory))] - [TestCase(typeof(IScopeProvider))] - [TestCase(typeof(AspNetCoreHttpScopeProvider))] - public void PublicApi(Type type) + string publicApi = type.Assembly.GeneratePublicApi(new ApiGeneratorOptions { - string publicApi = type.Assembly.GeneratePublicApi(new ApiGeneratorOptions - { - IncludeAssemblyAttributes = false, - WhitelistedNamespacePrefixes = new[] { "Microsoft.Extensions.DependencyInjection" }, - ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" }, - }); + IncludeAssemblyAttributes = false, + WhitelistedNamespacePrefixes = new[] { "Microsoft.Extensions.DependencyInjection" }, + ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" }, + }); - publicApi.ShouldMatchApproved(options => options!.WithFilenameGenerator((testMethodInfo, discriminator, fileType, fileExtension) => $"{type.Assembly.GetName().Name!}.{fileType}.{fileExtension}")); - } + publicApi.ShouldMatchApproved(options => options!.WithFilenameGenerator((testMethodInfo, discriminator, fileType, fileExtension) => $"{type.Assembly.GetName().Name!}.{fileType}.{fileExtension}")); } } diff --git a/src/SteroidsDI.Tests/Cases/AllowRootProviderResolveTests.cs b/src/SteroidsDI.Tests/Cases/AllowRootProviderResolveTests.cs index e3ec642..97ac945 100644 --- a/src/SteroidsDI.Tests/Cases/AllowRootProviderResolveTests.cs +++ b/src/SteroidsDI.Tests/Cases/AllowRootProviderResolveTests.cs @@ -1,82 +1,80 @@ -using System; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +public class AllowRootProviderResolveTests { - [TestFixture] - public class AllowRootProviderResolveTests - { - private sealed class ScopedAsSingleton { } + private sealed class ScopedAsSingleton { } - private class Service + private class Service + { + public Service(Defer scoped) { - public Service(Defer scoped) - { - Scoped = scoped; - } - - public Defer Scoped { get; } + Scoped = scoped; } - [Test] - public void Should_Work_When_No_Scopes_And_AllowRootProviderResolve_Enabled() - { - var services = new ServiceCollection() - .Configure(opt => opt.AllowRootProviderResolve = true) - .AddDefer() - .AddSingleton() - .AddSingleton(); + public Defer Scoped { get; } + } + + [Test] + public void Should_Work_When_No_Scopes_And_AllowRootProviderResolve_Enabled() + { + var services = new ServiceCollection() + .Configure(opt => opt.AllowRootProviderResolve = true) + .AddDefer() + .AddSingleton() + .AddSingleton(); - using (var provider = services.BuildServiceProvider()) + using (var provider = services.BuildServiceProvider()) + { + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - service.Scoped.Value.ShouldNotBeNull(); - } + var service = scope.ServiceProvider.GetService()!; + service.Scoped.Value.ShouldNotBeNull(); } } + } - [Test] - [Category("Throw")] - public void Should_Throw_When_No_Scopes_And_AllowRootProviderResolve_Disabled() - { - var services = new ServiceCollection() - //.Configure(opt => opt.AllowRootProviderResolve = true) // false by default - .AddDefer() - .AddSingleton() - .AddSingleton(); + [Test] + [Category("Throw")] + public void Should_Throw_When_No_Scopes_And_AllowRootProviderResolve_Disabled() + { + var services = new ServiceCollection() + //.Configure(opt => opt.AllowRootProviderResolve = true) // false by default + .AddDefer() + .AddSingleton() + .AddSingleton(); - using (var provider = services.BuildServiceProvider()) + using (var provider = services.BuildServiceProvider()) + { + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"The current scope is missing. Unable to get object of type 'ScopedAsSingleton' from the root provider. + var service = scope.ServiceProvider.GetService()!; + Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"The current scope is missing. Unable to get object of type 'ScopedAsSingleton' from the root provider. Be sure to add the required provider (IScopeProvider) to the container using the TryAddEnumerable method or a special method from your transport library. An object can be obtained from the root provider if it has a non-scoped lifetime and the parameter 'ServiceProviderAdvancedOptions.AllowRootProviderResolve' = true."); - } } } + } - [Test] - [Category("Throw")] - public void Should_Throw_When_No_Scopes_And_No_Service_Registered_And_AllowRootProviderResolve_Enabled() - { - var services = new ServiceCollection() - .Configure(opt => opt.AllowRootProviderResolve = true) - .AddDefer() - .AddSingleton(); + [Test] + [Category("Throw")] + public void Should_Throw_When_No_Scopes_And_No_Service_Registered_And_AllowRootProviderResolve_Enabled() + { + var services = new ServiceCollection() + .Configure(opt => opt.AllowRootProviderResolve = true) + .AddDefer() + .AddSingleton(); - using (var provider = services.BuildServiceProvider()) + using (var provider = services.BuildServiceProvider()) + { + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - Should.Throw(() => service.Scoped.Value).Message.ShouldBe("No service for type 'SteroidsDI.Tests.Cases.AllowRootProviderResolveTests+ScopedAsSingleton' has been registered."); - } + var service = scope.ServiceProvider.GetService()!; + Should.Throw(() => service.Scoped.Value).Message.ShouldBe("No service for type 'SteroidsDI.Tests.Cases.AllowRootProviderResolveTests+ScopedAsSingleton' has been registered."); } } } diff --git a/src/SteroidsDI.Tests/Cases/AspNetCoreHttpScopeProviderTests.cs b/src/SteroidsDI.Tests/Cases/AspNetCoreHttpScopeProviderTests.cs index 581241d..5387ef5 100644 --- a/src/SteroidsDI.Tests/Cases/AspNetCoreHttpScopeProviderTests.cs +++ b/src/SteroidsDI.Tests/Cases/AspNetCoreHttpScopeProviderTests.cs @@ -1,46 +1,44 @@ -using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; using SteroidsDI.AspNetCore; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +public class AspNetCoreHttpScopeProviderTests { - [TestFixture] - public class AspNetCoreHttpScopeProviderTests + [Test] + [Category("Throw")] + public void Should_Throw_If_Called_With_Null_Provider() { - [Test] - [Category("Throw")] - public void Should_Throw_If_Called_With_Null_Provider() - { - Should.Throw(() => new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(null!).ShouldBe(null)); - } + Should.Throw(() => new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(null!).ShouldBe(null)); + } - [Test] - public void Should_Return_Null_If_Called_Out_Of_HttpContext() - { - var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); - new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldBe(null); - } + [Test] + public void Should_Return_Null_If_Called_Out_Of_HttpContext() + { + var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); + new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldBe(null); + } - [Test] - public void Should_Return_Null_If_Called_With_HttpContext_Without_Provider() - { - var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); - new HttpContextAccessor().HttpContext = new DefaultHttpContext(); - new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldBeNull(); - } + [Test] + public void Should_Return_Null_If_Called_With_HttpContext_Without_Provider() + { + var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); + new HttpContextAccessor().HttpContext = new DefaultHttpContext(); + new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldBeNull(); + } - [Test] - public void Should_Return_Provider_If_Called_With_HttpContext_With_Provider() + [Test] + public void Should_Return_Provider_If_Called_With_HttpContext_With_Provider() + { + var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); + new HttpContextAccessor().HttpContext = new DefaultHttpContext() { - var provider = new ServiceCollection().AddHttpScope().BuildServiceProvider(); - new HttpContextAccessor().HttpContext = new DefaultHttpContext() - { - RequestServices = new ServiceCollection().BuildServiceProvider() - }; - new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldNotBeNull(); - } + RequestServices = new ServiceCollection().BuildServiceProvider() + }; + new AspNetCoreHttpScopeProvider().GetScopedServiceProvider(provider).ShouldNotBeNull(); } } diff --git a/src/SteroidsDI.Tests/Cases/FactoryTests.cs b/src/SteroidsDI.Tests/Cases/FactoryTests.cs index 0446c62..435063d 100644 --- a/src/SteroidsDI.Tests/Cases/FactoryTests.cs +++ b/src/SteroidsDI.Tests/Cases/FactoryTests.cs @@ -1,161 +1,159 @@ -using System; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +[Category("Factory")] +public class FactoryTests { - [TestFixture] - [Category("Factory")] - public class FactoryTests + [Test] + [Category("Throw")] + public void Named_Binding_Should_Throw_On_Unknown_Lifetime() { - [Test] - [Category("Throw")] - public void Named_Binding_Should_Throw_On_Unknown_Lifetime() - { - var services = new ServiceCollection(); - var context = services.For(); - Should.Throw(() => context.Named("xxx")).Message.ShouldBe(@"The DI container does not have a default binding for the type 'SteroidsDI.Tests.IBuilder', so it is not possible to determine the value of Lifetime. + var services = new ServiceCollection(); + var context = services.For(); + Should.Throw(() => context.Named("xxx")).Message.ShouldBe(@"The DI container does not have a default binding for the type 'SteroidsDI.Tests.IBuilder', so it is not possible to determine the value of Lifetime. Use the 'Named' overload with explicit Lifetime or first set the default binding in the DI container."); - } + } - [Test] - public void Factory_And_Named_Bindings_Should_Work() + [Test] + public void Factory_And_Named_Bindings_Should_Work() + { + using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) { - using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - GenericScope.CurrentScope = scope; + GenericScope.CurrentScope = scope; - var controller = scope.ServiceProvider.GetRequiredService(); + var controller = scope.ServiceProvider.GetRequiredService(); - var builder1 = controller.Factory.AAA(); - builder1.ShouldBeAssignableTo(); - builder1.Build(); + var builder1 = controller.Factory.AAA(); + builder1.ShouldBeAssignableTo(); + builder1.Build(); - var notifier = controller.Factory.BBB(); - notifier.ShouldBeAssignableTo(); - notifier.Notify(); + var notifier = controller.Factory.BBB(); + notifier.ShouldBeAssignableTo(); + notifier.Notify(); - var builder2 = controller.Factory.CCC("xxx"); - builder2.ShouldBeAssignableTo(); - builder2.Build(); + var builder2 = controller.Factory.CCC("xxx"); + builder2.ShouldBeAssignableTo(); + builder2.Build(); - var builder3 = controller.Factory.CCC("yyy"); - builder3.ShouldBeAssignableTo(); - builder3.Build(); + var builder3 = controller.Factory.CCC("yyy"); + builder3.ShouldBeAssignableTo(); + builder3.Build(); - var builder4 = controller.Factory.CCC("oops"); - builder4.ShouldBeAssignableTo(); - builder4.Build(); + var builder4 = controller.Factory.CCC("oops"); + builder4.ShouldBeAssignableTo(); + builder4.Build(); - var builder5 = controller.Factory.DDD(ManagerType.Good); - builder5.ShouldBeAssignableTo(); - builder5.Build(); + var builder5 = controller.Factory.DDD(ManagerType.Good); + builder5.ShouldBeAssignableTo(); + builder5.Build(); - var builder6 = controller.Factory.DDD(ManagerType.Bad); - builder6.ShouldBeAssignableTo(); - builder6.Build(); - } + var builder6 = controller.Factory.DDD(ManagerType.Bad); + builder6.ShouldBeAssignableTo(); + builder6.Build(); } } + } - [Test] - [Category("Generic")] - public void Generic_Factory_And_Named_Bindings_Should_Work() + [Test] + [Category("Generic")] + public void Generic_Factory_And_Named_Bindings_Should_Work() + { + using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) { - using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - GenericScope.CurrentScope = scope; + GenericScope.CurrentScope = scope; - var controller = scope.ServiceProvider.GetRequiredService(); + var controller = scope.ServiceProvider.GetRequiredService(); - var builder1 = controller.Generic.AAA(); - builder1.ShouldBeAssignableTo(); - builder1.Build(); + var builder1 = controller.Generic.AAA(); + builder1.ShouldBeAssignableTo(); + builder1.Build(); - var notifier = controller.Generic.BBB(); - notifier.ShouldBeAssignableTo(); - notifier.Notify(); + var notifier = controller.Generic.BBB(); + notifier.ShouldBeAssignableTo(); + notifier.Notify(); - var builder2 = controller.Generic.CCC("xxx"); - builder2.ShouldBeAssignableTo(); - builder2.Build(); + var builder2 = controller.Generic.CCC("xxx"); + builder2.ShouldBeAssignableTo(); + builder2.Build(); - var builder3 = controller.Generic.CCC("yyy"); - builder3.ShouldBeAssignableTo(); - builder3.Build(); + var builder3 = controller.Generic.CCC("yyy"); + builder3.ShouldBeAssignableTo(); + builder3.Build(); - var builder4 = controller.Generic.CCC("oops"); - builder4.ShouldBeAssignableTo(); - builder4.Build(); + var builder4 = controller.Generic.CCC("oops"); + builder4.ShouldBeAssignableTo(); + builder4.Build(); - var builder5 = controller.Generic.DDD(ManagerType.Good); - builder5.ShouldBeAssignableTo(); - builder5.Build(); + var builder5 = controller.Generic.DDD(ManagerType.Good); + builder5.ShouldBeAssignableTo(); + builder5.Build(); - var builder6 = controller.Generic.DDD(ManagerType.Bad); - builder6.ShouldBeAssignableTo(); - builder6.Build(); - } + var builder6 = controller.Generic.DDD(ManagerType.Bad); + builder6.ShouldBeAssignableTo(); + builder6.Build(); } } + } - [Test] - [Category("Throw")] - public void Null_Binding_Should_Throw() + [Test] + [Category("Throw")] + public void Null_Binding_Should_Throw() + { + using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) { - using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) - { - var controller = provider.GetRequiredService(); + var controller = provider.GetRequiredService(); - Should.Throw(() => controller.Factory.CCC(null!)); - Should.Throw(() => controller.Generic.CCC(null!)); - } + Should.Throw(() => controller.Factory.CCC(null!)); + Should.Throw(() => controller.Generic.CCC(null!)); } + } - [Test] - [Category("Throw")] - public void Unknown_Binding_Should_Throw() + [Test] + [Category("Throw")] + public void Unknown_Binding_Should_Throw() + { + using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) { - using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) - { - var controller = provider.GetRequiredService(); - - Should.Throw(() => controller.Factory.CCC("Zorro")); - Should.Throw(() => controller.Generic.CCC("Zorro")); + var controller = provider.GetRequiredService(); - Should.Throw(() => controller.Factory.DDD(ManagerType.Angry)); - Should.Throw(() => controller.Generic.DDD(ManagerType.Angry)); - } - } + Should.Throw(() => controller.Factory.CCC("Zorro")); + Should.Throw(() => controller.Generic.CCC("Zorro")); - [Test] - [Category("Throw")] - public void Named_Binding_With_Invalid_Properties_Should_Throw() - { - Should.Throw(() => ServicesBuilder.BuildDefault().For().Named(null!).Services.BuildServiceProvider(validateScopes: true)); - Should.Throw(() => ServicesBuilder.BuildDefault().For().Named("oops", ServiceLifetime.Transient).Services.BuildServiceProvider(validateScopes: true)); + Should.Throw(() => controller.Factory.DDD(ManagerType.Angry)); + Should.Throw(() => controller.Generic.DDD(ManagerType.Angry)); } + } - [Test] - [Category("Throw")] - public void Null_Factory_Type_Should_Throw() - { - Should.Throw(() => new ServiceCollection().AddFactory(null!)); - Should.Throw(() => FactoryGenerator.Generate(null!)); - } + [Test] + [Category("Throw")] + public void Named_Binding_With_Invalid_Properties_Should_Throw() + { + Should.Throw(() => ServicesBuilder.BuildDefault().For().Named(null!).Services.BuildServiceProvider(validateScopes: true)); + Should.Throw(() => ServicesBuilder.BuildDefault().For().Named("oops", ServiceLifetime.Transient).Services.BuildServiceProvider(validateScopes: true)); + } - [Test] - [Category("Throw")] - [TestCase(typeof(int))] - [TestCase(typeof(IFactoryWithEvent))] - [TestCase(typeof(IFactoryWithProperty))] - [TestCase(typeof(IFactoryWithMethodWithManyArgs))] - public void Wrong_Factory_Type_Should_Throw(Type factoryType) => Should.Throw(() => new ServiceCollection().AddFactory(factoryType)); + [Test] + [Category("Throw")] + public void Null_Factory_Type_Should_Throw() + { + Should.Throw(() => new ServiceCollection().AddFactory(null!)); + Should.Throw(() => FactoryGenerator.Generate(null!)); } + + [Test] + [Category("Throw")] + [TestCase(typeof(int))] + [TestCase(typeof(IFactoryWithEvent))] + [TestCase(typeof(IFactoryWithProperty))] + [TestCase(typeof(IFactoryWithMethodWithManyArgs))] + public void Wrong_Factory_Type_Should_Throw(Type factoryType) => Should.Throw(() => new ServiceCollection().AddFactory(factoryType)); } diff --git a/src/SteroidsDI.Tests/Cases/FuncTests.cs b/src/SteroidsDI.Tests/Cases/FuncTests.cs index e105291..77f05d8 100644 --- a/src/SteroidsDI.Tests/Cases/FuncTests.cs +++ b/src/SteroidsDI.Tests/Cases/FuncTests.cs @@ -1,115 +1,113 @@ -using System; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +[Category("Func")] +public class FuncTests { - [TestFixture] - [Category("Func")] - public class FuncTests + [Test] + public void Func_Should_Work_With_Proper_Create_And_Dispose() { - [Test] - public void Func_Should_Work_With_Proper_Create_And_Dispose() - { - Controller controller1, controller2, controller3; - ScopedService service1, service2, service3; - TransientService transient1, transient2, transient3, transient11, transient22, transient33; + Controller controller1, controller2, controller3; + ScopedService service1, service2, service3; + TransientService transient1, transient2, transient3, transient11, transient22, transient33; - using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) + using (var provider = ServicesBuilder.BuildDefault().BuildServiceProvider(validateScopes: true)) + { + using (var scope1 = provider.CreateScope()) { - using (var scope1 = provider.CreateScope()) - { - GenericScope.CurrentScope = scope1; + GenericScope.CurrentScope = scope1; - controller1 = scope1.ServiceProvider.GetService()!; - service1 = controller1.ScopedFunc(); - var service11 = controller1.ScopedFunc(); - service11.ShouldBeSameAs(service1); - service1.Disposed.ShouldBeFalse(); + controller1 = scope1.ServiceProvider.GetService()!; + service1 = controller1.ScopedFunc(); + var service11 = controller1.ScopedFunc(); + service11.ShouldBeSameAs(service1); + service1.Disposed.ShouldBeFalse(); - transient1 = controller1.TransientFunc(); - transient11 = controller1.TransientFunc(); - transient11.ShouldNotBeSameAs(transient1); + transient1 = controller1.TransientFunc(); + transient11 = controller1.TransientFunc(); + transient11.ShouldNotBeSameAs(transient1); - var defer = controller1.ScopedDefer.Value; - defer.ShouldBeSameAs(service1); + var defer = controller1.ScopedDefer.Value; + defer.ShouldBeSameAs(service1); - var transientDefer1 = controller1.TransientDefer.Value; - var transietransientDefer11 = controller1.TransientDefer.Value; - transietransientDefer11.ShouldNotBeSameAs(transientDefer1); - } + var transientDefer1 = controller1.TransientDefer.Value; + var transietransientDefer11 = controller1.TransientDefer.Value; + transietransientDefer11.ShouldNotBeSameAs(transientDefer1); + } - Console.WriteLine(); + Console.WriteLine(); - service1.Disposed.ShouldBeTrue(); - transient1.Disposed.ShouldBeTrue(); - transient11.Disposed.ShouldBeTrue(); + service1.Disposed.ShouldBeTrue(); + transient1.Disposed.ShouldBeTrue(); + transient11.Disposed.ShouldBeTrue(); - using (var scope2 = provider.CreateScope()) - { - GenericScope.CurrentScope = scope2; + using (var scope2 = provider.CreateScope()) + { + GenericScope.CurrentScope = scope2; - controller2 = scope2.ServiceProvider.GetService()!; - controller2.ShouldBeSameAs(controller1); + controller2 = scope2.ServiceProvider.GetService()!; + controller2.ShouldBeSameAs(controller1); - service2 = controller2.ScopedFunc(); - var service22 = controller2.ScopedFunc(); - service22.ShouldBeSameAs(service2); - service2.Disposed.ShouldBeFalse(); + service2 = controller2.ScopedFunc(); + var service22 = controller2.ScopedFunc(); + service22.ShouldBeSameAs(service2); + service2.Disposed.ShouldBeFalse(); - transient2 = controller2.TransientFunc(); - transient22 = controller2.TransientFunc(); - transient22.ShouldNotBeSameAs(transient2); - } + transient2 = controller2.TransientFunc(); + transient22 = controller2.TransientFunc(); + transient22.ShouldNotBeSameAs(transient2); + } - Console.WriteLine(); + Console.WriteLine(); - service2.Disposed.ShouldBeTrue(); - transient2.Disposed.ShouldBeTrue(); - transient22.Disposed.ShouldBeTrue(); + service2.Disposed.ShouldBeTrue(); + transient2.Disposed.ShouldBeTrue(); + transient22.Disposed.ShouldBeTrue(); - using (var scope3 = provider.CreateScope()) - { - GenericScope.CurrentScope = scope3; + using (var scope3 = provider.CreateScope()) + { + GenericScope.CurrentScope = scope3; - controller3 = scope3.ServiceProvider.GetService()!; - controller3.ShouldBeSameAs(controller1); + controller3 = scope3.ServiceProvider.GetService()!; + controller3.ShouldBeSameAs(controller1); - service3 = controller3.ScopedFunc(); - var service33 = controller3.ScopedFunc(); - service33.ShouldBeSameAs(service3); - service3.Disposed.ShouldBeFalse(); + service3 = controller3.ScopedFunc(); + var service33 = controller3.ScopedFunc(); + service33.ShouldBeSameAs(service3); + service3.Disposed.ShouldBeFalse(); - transient3 = controller3.TransientFunc(); - transient33 = controller3.TransientFunc(); - transient33.ShouldNotBeSameAs(transient3); - } + transient3 = controller3.TransientFunc(); + transient33 = controller3.TransientFunc(); + transient33.ShouldNotBeSameAs(transient3); + } - Console.WriteLine(); + Console.WriteLine(); - service3.Disposed.ShouldBeTrue(); - transient3.Disposed.ShouldBeTrue(); - transient33.Disposed.ShouldBeTrue(); - } + service3.Disposed.ShouldBeTrue(); + transient3.Disposed.ShouldBeTrue(); + transient33.Disposed.ShouldBeTrue(); } + } - [Test] - [Category("Throw")] - public void Scoped_Func_Call_Without_ScopeProvider_Should_Throw() + [Test] + [Category("Throw")] + public void Scoped_Func_Call_Without_ScopeProvider_Should_Throw() + { + Should.Throw(() => { - Should.Throw(() => + using (var provider = ServicesBuilder.BuildDefault(addScopeProvider: false).BuildServiceProvider(validateScopes: true)) { - using (var provider = ServicesBuilder.BuildDefault(addScopeProvider: false).BuildServiceProvider(validateScopes: true)) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var controller = scope.ServiceProvider.GetService()!; - var service = controller.ScopedFunc(); - } + var controller = scope.ServiceProvider.GetService()!; + var service = controller.ScopedFunc(); } - }); - } + } + }); } } diff --git a/src/SteroidsDI.Tests/Cases/ScopedTestBase.cs b/src/SteroidsDI.Tests/Cases/ScopedTestBase.cs index 5989f16..6adfd7a 100644 --- a/src/SteroidsDI.Tests/Cases/ScopedTestBase.cs +++ b/src/SteroidsDI.Tests/Cases/ScopedTestBase.cs @@ -1,43 +1,41 @@ -using System; using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +/// +/// Base test that provides ability to work with scopes. Each test method in +/// derived classes can get query DI container via method, +/// see class. +/// +public class ScopedTestBase : IDisposable { - /// - /// Base test that provides ability to work with scopes. Each test method in - /// derived classes can get query DI container via method, - /// see class. - /// - public class ScopedTestBase : IDisposable + private readonly ServiceProvider _rootProvider; + private readonly Scoped _scoped; + + public ScopedTestBase() + { + var services = new ServiceCollection(); + ConfigureServices(services); + + _rootProvider = services.BuildServiceProvider(); + _scoped = new Scoped(_rootProvider.GetRequiredService()); + } + + // override this method to specify required services for unit tests + protected virtual void ConfigureServices(IServiceCollection services) + { + services.AddDefer(); + services.AddGenericScope(); + } + + protected T? GetService() => ((IServiceScope)_scoped.Scope).ServiceProvider.GetService(); + + protected T GetRequiredService() => ((IServiceScope)_scoped.Scope).ServiceProvider.GetRequiredService(); + + public void Dispose() { - private readonly ServiceProvider _rootProvider; - private readonly Scoped _scoped; - - public ScopedTestBase() - { - var services = new ServiceCollection(); - ConfigureServices(services); - - _rootProvider = services.BuildServiceProvider(); - _scoped = new Scoped(_rootProvider.GetRequiredService()); - } - - // override this method to specify required services for unit tests - protected virtual void ConfigureServices(IServiceCollection services) - { - services.AddDefer(); - services.AddGenericScope(); - } - - protected T GetService() => ((IServiceScope)_scoped.Scope).ServiceProvider.GetService(); - - protected T GetRequiredService() => ((IServiceScope)_scoped.Scope).ServiceProvider.GetRequiredService(); - - public void Dispose() - { - _scoped.Dispose(); - _rootProvider.Dispose(); - } + _scoped.Dispose(); + _rootProvider.Dispose(); } } diff --git a/src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs b/src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs index 589f067..492fc5c 100644 --- a/src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs +++ b/src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs @@ -1,49 +1,47 @@ -using System; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +public class ScopedTestDerived : ScopedTestBase { - public class ScopedTestDerived : ScopedTestBase + protected override void ConfigureServices(IServiceCollection services) { - protected override void ConfigureServices(IServiceCollection services) - { - base.ConfigureServices(services); - services.AddSingleton(); - services.AddScoped(); - } - - /// - /// This method works in context of prepared scope like ASP.NET Core app does. - /// - [Test] - public void Scoped_Should_Work() - { - var repo = GetService(); - repo.Name.ShouldBe("12345"); - } + base.ConfigureServices(services); + services.AddSingleton(); + services.AddScoped(); } - public interface IRepo + /// + /// This method works in context of prepared scope like ASP.NET Core app does. + /// + [Test] + public void Scoped_Should_Work() { - string Name { get; } + var repo = GetRequiredService(); + repo.Name.ShouldBe("12345"); } +} - public class Repo : IRepo - { - private readonly Defer _dep; - - public Repo(Defer dep) - { - _dep = dep; - } +public interface IRepo +{ + string Name { get; } +} - public string Name => _dep.Value.Name; - } +public class Repo : IRepo +{ + private readonly Defer _dep; - public class Dependency + public Repo(Defer dep) { - public virtual string Name => "12345"; + _dep = dep; } + + public string Name => _dep.Value.Name; +} + +public class Dependency +{ + public virtual string Name => "12345"; } diff --git a/src/SteroidsDI.Tests/Cases/ScopedTests.cs b/src/SteroidsDI.Tests/Cases/ScopedTests.cs index ecffcbf..fafcf36 100644 --- a/src/SteroidsDI.Tests/Cases/ScopedTests.cs +++ b/src/SteroidsDI.Tests/Cases/ScopedTests.cs @@ -1,50 +1,48 @@ -using System; using NUnit.Framework; using Shouldly; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +public class ScopedTests { - [TestFixture] - public class ScopedTests + [Test] + [Category("Throw")] + public void Should_Throw_If_Null() { - [Test] - [Category("Throw")] - public void Should_Throw_If_Null() - { - Should.Throw(() => new Scoped(null!)).ParamName.ShouldBe("scopeFactory"); - } + Should.Throw(() => new Scoped(null!)).ParamName.ShouldBe("scopeFactory"); + } - [Test] - [Category("Throw")] - public void Should_Throw_If_Current_Scope_Exist() - { - GenericScope.CurrentScope = new NoopScope(); - Should.Throw(() => new Scoped(new NoopScopeFactory())).Message.ShouldBe($"The current scope of GenericScope is not null when trying to initialize it."); - } + [Test] + [Category("Throw")] + public void Should_Throw_If_Current_Scope_Exist() + { + GenericScope.CurrentScope = new NoopScope(); + Should.Throw(() => new Scoped(new NoopScopeFactory())).Message.ShouldBe($"The current scope of GenericScope is not null when trying to initialize it."); + } - [Test] - public void Should_Not_Throw_If_Null_Scope() - { - using var s = new Scoped(new NullScopeFactory()); - s.Scope.ShouldBeNull(); - } + [Test] + public void Should_Not_Throw_If_Null_Scope() + { + using var s = new Scoped(new NullScopeFactory()); + s.Scope.ShouldBeNull(); + } - private sealed class NoopScope : IDisposable + private sealed class NoopScope : IDisposable + { + public void Dispose() { - public void Dispose() - { - } } + } - private sealed class NoopScopeFactory : IScopeFactory - { - public IDisposable CreateScope() => new NoopScope(); - } + private sealed class NoopScopeFactory : IScopeFactory + { + public IDisposable CreateScope() => new NoopScope(); + } - private sealed class NullScopeFactory : IScopeFactory - { - public IDisposable CreateScope() => null!; - } + private sealed class NullScopeFactory : IScopeFactory + { + public IDisposable CreateScope() => null!; } } diff --git a/src/SteroidsDI.Tests/Cases/ValidateParallelScopesTests.cs b/src/SteroidsDI.Tests/Cases/ValidateParallelScopesTests.cs index 63d26dd..d63f238 100644 --- a/src/SteroidsDI.Tests/Cases/ValidateParallelScopesTests.cs +++ b/src/SteroidsDI.Tests/Cases/ValidateParallelScopesTests.cs @@ -1,135 +1,133 @@ -using System; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; using SteroidsDI.Core; -namespace SteroidsDI.Tests.Cases +namespace SteroidsDI.Tests.Cases; + +[TestFixture] +public class ValidateParallelScopesTests { - [TestFixture] - public class ValidateParallelScopesTests - { - private sealed class A { } + private sealed class A { } - private sealed class B { } + private sealed class B { } - private sealed class Scoped { } + private sealed class Scoped { } - private class Service + private class Service + { + public Service(Defer scoped) { - public Service(Defer scoped) - { - Scoped = scoped; - } - - public Defer Scoped { get; } + Scoped = scoped; } - private sealed class TestScope : IDisposable, IServiceScope, IServiceProvider - { - public IServiceProvider ServiceProvider => this; + public Defer Scoped { get; } + } - public void Dispose() - { - } + private sealed class TestScope : IDisposable, IServiceScope, IServiceProvider + { + public IServiceProvider ServiceProvider => this; - public object GetService(Type serviceType) => Activator.CreateInstance(serviceType)!; + public void Dispose() + { } - [Test] - [Category("Throw")] - public void Should_Throw_When_No_Scopes() + public object GetService(Type serviceType) => Activator.CreateInstance(serviceType)!; + } + + [Test] + [Category("Throw")] + public void Should_Throw_When_No_Scopes() + { + var services = new ServiceCollection() + .AddDefer() + .AddGenericScope() + .AddGenericScope() + .AddScoped() + .AddSingleton(); + + using (var provider = services.BuildServiceProvider()) { - var services = new ServiceCollection() - .AddDefer() - .AddGenericScope() - .AddGenericScope() - .AddScoped() - .AddSingleton(); - - using (var provider = services.BuildServiceProvider()) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"An error occurred while resolving the type 'Scoped' + var service = scope.ServiceProvider.GetService()!; + Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"An error occurred while resolving the type 'Scoped' The type is declared as scoped within the context of the request, but an attempt to resolve the type is made outside the context of the request. An application can simultaneously have several entry points that initialize their request contexts. Be sure to add the required provider (IScopeProvider) to the container using the TryAddEnumerable method."); - } } } + } - [Test] - public void Should_Work_When_More_Than_One_Scope_And_ValidateParallelScopes_Disabled() + [Test] + public void Should_Work_When_More_Than_One_Scope_And_ValidateParallelScopes_Disabled() + { + var services = new ServiceCollection() + //.Configure(opt => opt.ValidateParallelScopes = false) // false by default + .AddDefer() + .AddGenericScope() + .AddGenericScope() + .AddScoped() + .AddSingleton(); + + GenericScope.CurrentScope = new TestScope(); + GenericScope.CurrentScope = new TestScope(); + + using (var provider = services.BuildServiceProvider()) { - var services = new ServiceCollection() - //.Configure(opt => opt.ValidateParallelScopes = false) // false by default - .AddDefer() - .AddGenericScope() - .AddGenericScope() - .AddScoped() - .AddSingleton(); - - GenericScope.CurrentScope = new TestScope(); - GenericScope.CurrentScope = new TestScope(); - - using (var provider = services.BuildServiceProvider()) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - service.Scoped.Value.ShouldNotBeNull(); - } + var service = scope.ServiceProvider.GetService()!; + service.Scoped.Value.ShouldNotBeNull(); } } + } - [Test] - public void Should_Work_When_Only_One_Scope_And_ValidateParallelScopes_Enabled() - { - var services = new ServiceCollection() - .Configure(opt => opt.ValidateParallelScopes = true) - .AddDefer() - .AddGenericScope() - .AddGenericScope() - .AddScoped() - .AddSingleton(); + [Test] + public void Should_Work_When_Only_One_Scope_And_ValidateParallelScopes_Enabled() + { + var services = new ServiceCollection() + .Configure(opt => opt.ValidateParallelScopes = true) + .AddDefer() + .AddGenericScope() + .AddGenericScope() + .AddScoped() + .AddSingleton(); - GenericScope.CurrentScope = new TestScope(); + GenericScope.CurrentScope = new TestScope(); - using (var provider = services.BuildServiceProvider()) + using (var provider = services.BuildServiceProvider()) + { + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - service.Scoped.Value.ShouldNotBeNull(); - } + var service = scope.ServiceProvider.GetService()!; + service.Scoped.Value.ShouldNotBeNull(); } } + } - [Test] - [Category("Throw")] - public void Should_Throw_When_More_Than_One_Scope_And_ValidateParallelScopes_Enabled() + [Test] + [Category("Throw")] + public void Should_Throw_When_More_Than_One_Scope_And_ValidateParallelScopes_Enabled() + { + var services = new ServiceCollection() + .Configure(opt => opt.ValidateParallelScopes = true) + .AddDefer() + .AddGenericScope() + .AddGenericScope() + .AddScoped() + .AddSingleton(); + + GenericScope.CurrentScope = new TestScope(); + GenericScope.CurrentScope = new TestScope(); + + using (var provider = services.BuildServiceProvider()) { - var services = new ServiceCollection() - .Configure(opt => opt.ValidateParallelScopes = true) - .AddDefer() - .AddGenericScope() - .AddGenericScope() - .AddScoped() - .AddSingleton(); - - GenericScope.CurrentScope = new TestScope(); - GenericScope.CurrentScope = new TestScope(); - - using (var provider = services.BuildServiceProvider()) + using (var scope = provider.CreateScope()) { - using (var scope = provider.CreateScope()) - { - var service = scope.ServiceProvider.GetService()!; - Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"When 'ServiceProviderAdvancedOptions.ValidateParallelScopes' option is turned on, the simultaneous existence of several scopes from different providers was detected. + var service = scope.ServiceProvider.GetService()!; + Should.Throw(() => service.Scoped.Value).Message.ShouldBe(@"When 'ServiceProviderAdvancedOptions.ValidateParallelScopes' option is turned on, the simultaneous existence of several scopes from different providers was detected. Scopes obtained from the following providers: SteroidsDI.GenericScopeProvider`1[SteroidsDI.Tests.Cases.ValidateParallelScopesTests+B], SteroidsDI.GenericScopeProvider`1[SteroidsDI.Tests.Cases.ValidateParallelScopesTests+A]"); - } } } } diff --git a/src/SteroidsDI.Tests/Factory/IGenericFactory.cs b/src/SteroidsDI.Tests/Factory/IGenericFactory.cs index 9a45a61..6b31530 100644 --- a/src/SteroidsDI.Tests/Factory/IGenericFactory.cs +++ b/src/SteroidsDI.Tests/Factory/IGenericFactory.cs @@ -1,16 +1,15 @@ -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +/// An generic factory for which implementation is generated in runtime. +/// The first generic parameter. +/// The second generic parameter. +public interface IGenericFactory { - /// An generic factory for which implementation is generated in runtime. - /// The first generic parameter. - /// The second generic parameter. - public interface IGenericFactory - { - TBuilder AAA(); + TBuilder AAA(); - TNotifier BBB(); + TNotifier BBB(); - TBuilder CCC(string name); + TBuilder CCC(string name); - TBuilder DDD(ManagerType type); - } + TBuilder DDD(ManagerType type); } diff --git a/src/SteroidsDI.Tests/Factory/IMegaFactory.cs b/src/SteroidsDI.Tests/Factory/IMegaFactory.cs index e268ddf..81eba60 100644 --- a/src/SteroidsDI.Tests/Factory/IMegaFactory.cs +++ b/src/SteroidsDI.Tests/Factory/IMegaFactory.cs @@ -1,14 +1,13 @@ -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +/// An factory for which implementation is generated in runtime. +public interface IMegaFactory { - /// An factory for which implementation is generated in runtime. - public interface IMegaFactory - { - IBuilder AAA(); + IBuilder AAA(); - INotifier BBB(); + INotifier BBB(); - IBuilder CCC(string name); + IBuilder CCC(string name); - IBuilder DDD(ManagerType type); - } + IBuilder DDD(ManagerType type); } diff --git a/src/SteroidsDI.Tests/Factory/WrongFactories.cs b/src/SteroidsDI.Tests/Factory/WrongFactories.cs index a5a02d6..051acb0 100644 --- a/src/SteroidsDI.Tests/Factory/WrongFactories.cs +++ b/src/SteroidsDI.Tests/Factory/WrongFactories.cs @@ -1,19 +1,16 @@ -using System; +namespace SteroidsDI.Tests; -namespace SteroidsDI.Tests +public interface IFactoryWithEvent { - public interface IFactoryWithEvent - { - event EventHandler Click; - } + event EventHandler Click; +} - public interface IFactoryWithProperty - { - int Age { get; } - } +public interface IFactoryWithProperty +{ + int Age { get; } +} - public interface IFactoryWithMethodWithManyArgs - { - IBuilder XXX(int a, string b, DateTime c); - } +public interface IFactoryWithMethodWithManyArgs +{ + IBuilder XXX(int a, string b, DateTime c); } diff --git a/src/SteroidsDI.Tests/Model/Controller.cs b/src/SteroidsDI.Tests/Model/Controller.cs index a23cc56..070908e 100644 --- a/src/SteroidsDI.Tests/Model/Controller.cs +++ b/src/SteroidsDI.Tests/Model/Controller.cs @@ -1,48 +1,46 @@ -using System; using SteroidsDI.Core; -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +internal class Controller : IDisposable { - internal class Controller : IDisposable + public Controller( + IScopeFactory scopeFactory, + IMegaFactory factory, + IGenericFactory generic, + Func scopedFunc, + Func transientFunc, + Defer scopedDefer, + Defer transientDefer) { - public Controller( - IScopeFactory scopeFactory, - IMegaFactory factory, - IGenericFactory generic, - Func scopedFunc, - Func transientFunc, - Defer scopedDefer, - Defer transientDefer) + Factory = factory; + Generic = generic; + ScopedFunc = scopedFunc; + ScopedDefer = scopedDefer; + TransientFunc = transientFunc; + TransientDefer = transientDefer; + + Console.WriteLine("Controller created"); + + using (new Scoped(scopeFactory)) { - Factory = factory; - Generic = generic; - ScopedFunc = scopedFunc; - ScopedDefer = scopedDefer; - TransientFunc = transientFunc; - TransientDefer = transientDefer; - - Console.WriteLine("Controller created"); - - using (new Scoped(scopeFactory)) - { - } } + } - public IMegaFactory Factory { get; } + public IMegaFactory Factory { get; } - public IGenericFactory Generic { get; set; } + public IGenericFactory Generic { get; set; } - public Func ScopedFunc { get; set; } + public Func ScopedFunc { get; set; } - public Defer ScopedDefer { get; set; } + public Defer ScopedDefer { get; set; } - public Func TransientFunc { get; set; } + public Func TransientFunc { get; set; } - public Defer TransientDefer { get; set; } + public Defer TransientDefer { get; set; } - public void Dispose() - { - Console.WriteLine("Controller disposed"); - } + public void Dispose() + { + Console.WriteLine("Controller disposed"); } } diff --git a/src/SteroidsDI.Tests/Model/IBuilder.cs b/src/SteroidsDI.Tests/Model/IBuilder.cs index 5398638..832e854 100644 --- a/src/SteroidsDI.Tests/Model/IBuilder.cs +++ b/src/SteroidsDI.Tests/Model/IBuilder.cs @@ -1,24 +1,21 @@ -using System; +namespace SteroidsDI.Tests; -namespace SteroidsDI.Tests +public interface IBuilder { - public interface IBuilder - { - void Build(); - } + void Build(); +} - internal class Builder : IBuilder - { - public void Build() => Console.WriteLine("Builder"); - } +internal class Builder : IBuilder +{ + public void Build() => Console.WriteLine("Builder"); +} - internal class SpecialBuilder : IBuilder - { - public void Build() => Console.WriteLine("SpecialBuilder!"); - } +internal class SpecialBuilder : IBuilder +{ + public void Build() => Console.WriteLine("SpecialBuilder!"); +} - internal class SpecialBuilderOver9000Level : IBuilder - { - public void Build() => Console.WriteLine("!!!!!!!SpecialBuilderOver9000Level!!!!!!!"); - } +internal class SpecialBuilderOver9000Level : IBuilder +{ + public void Build() => Console.WriteLine("!!!!!!!SpecialBuilderOver9000Level!!!!!!!"); } diff --git a/src/SteroidsDI.Tests/Model/INotifier.cs b/src/SteroidsDI.Tests/Model/INotifier.cs index 661a60c..695bc98 100644 --- a/src/SteroidsDI.Tests/Model/INotifier.cs +++ b/src/SteroidsDI.Tests/Model/INotifier.cs @@ -1,14 +1,11 @@ -using System; +namespace SteroidsDI.Tests; -namespace SteroidsDI.Tests +public interface INotifier { - public interface INotifier - { - void Notify(); - } + void Notify(); +} - internal class Notifier : INotifier - { - public void Notify() => Console.WriteLine("Notifier"); - } +internal class Notifier : INotifier +{ + public void Notify() => Console.WriteLine("Notifier"); } diff --git a/src/SteroidsDI.Tests/Model/ManagerType.cs b/src/SteroidsDI.Tests/Model/ManagerType.cs index febaa74..d43e5b0 100644 --- a/src/SteroidsDI.Tests/Model/ManagerType.cs +++ b/src/SteroidsDI.Tests/Model/ManagerType.cs @@ -1,11 +1,10 @@ -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +public enum ManagerType { - public enum ManagerType - { - Good, + Good, - Bad, + Bad, - Angry, - } + Angry, } diff --git a/src/SteroidsDI.Tests/Model/ScopedService.cs b/src/SteroidsDI.Tests/Model/ScopedService.cs index 32cc624..95daad1 100644 --- a/src/SteroidsDI.Tests/Model/ScopedService.cs +++ b/src/SteroidsDI.Tests/Model/ScopedService.cs @@ -1,31 +1,28 @@ -using System; using System.Diagnostics; -using System.Threading; -namespace SteroidsDI.Tests -{ - /// It was required to test scoped dependencies in singleton objects through Func. - [DebuggerDisplay("ScopedService {Index}")] - internal class ScopedService : IDisposable - { - private static int _counter; +namespace SteroidsDI.Tests; - public ScopedService() - { - Index = Interlocked.Increment(ref _counter); - Console.WriteLine($"ScopedService {Index} created"); - } +/// It was required to test scoped dependencies in singleton objects through Func. +[DebuggerDisplay("ScopedService {Index}")] +internal class ScopedService : IDisposable +{ + private static int _counter; - public int Index { get; } + public ScopedService() + { + Index = Interlocked.Increment(ref _counter); + Console.WriteLine($"ScopedService {Index} created"); + } - public bool Disposed { get; private set; } + public int Index { get; } - public void Dispose() - { - Disposed = true; - Console.WriteLine($"ScopedService {Index} disposed"); - } + public bool Disposed { get; private set; } - public int Sum(int first, int second) => first + second; + public void Dispose() + { + Disposed = true; + Console.WriteLine($"ScopedService {Index} disposed"); } + + public int Sum(int first, int second) => first + second; } diff --git a/src/SteroidsDI.Tests/Model/TransientService.cs b/src/SteroidsDI.Tests/Model/TransientService.cs index 118706d..311fb04 100644 --- a/src/SteroidsDI.Tests/Model/TransientService.cs +++ b/src/SteroidsDI.Tests/Model/TransientService.cs @@ -1,29 +1,26 @@ -using System; using System.Diagnostics; -using System.Threading; -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +/// It was required to test transient dependencies in singleton objects through Func. +[DebuggerDisplay("TransientService {Index}")] +internal class TransientService : IDisposable { - /// It was required to test transient dependencies in singleton objects through Func. - [DebuggerDisplay("TransientService {Index}")] - internal class TransientService : IDisposable - { - private static int _counter; + private static int _counter; - public TransientService() - { - Index = Interlocked.Increment(ref _counter); - Console.WriteLine($"TransientService {Index} created"); - } + public TransientService() + { + Index = Interlocked.Increment(ref _counter); + Console.WriteLine($"TransientService {Index} created"); + } - public int Index { get; } + public int Index { get; } - public bool Disposed { get; private set; } + public bool Disposed { get; private set; } - public void Dispose() - { - Disposed = true; - Console.WriteLine($"TransientService {Index} disposed"); - } + public void Dispose() + { + Disposed = true; + Console.WriteLine($"TransientService {Index} disposed"); } } diff --git a/src/SteroidsDI.Tests/ServicesBuilder.cs b/src/SteroidsDI.Tests/ServicesBuilder.cs index 92eeb33..a0f1dbc 100644 --- a/src/SteroidsDI.Tests/ServicesBuilder.cs +++ b/src/SteroidsDI.Tests/ServicesBuilder.cs @@ -1,37 +1,36 @@ using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI.Tests +namespace SteroidsDI.Tests; + +internal class ServicesBuilder { - internal class ServicesBuilder + public static IServiceCollection BuildDefault(bool addScopeProvider = true) { - public static IServiceCollection BuildDefault(bool addScopeProvider = true) - { - var services = new ServiceCollection() - .AddDefer() - .AddMicrosoftScopeFactory() - .Configure(opt => opt.AllowRootProviderResolve = true) + var services = new ServiceCollection() + .AddDefer() + .AddMicrosoftScopeFactory() + .Configure(opt => opt.AllowRootProviderResolve = true) - .AddScoped().AddFunc() - .AddTransient().AddFunc() + .AddScoped().AddFunc() + .AddTransient().AddFunc() - .AddSingleton() - .AddFactory() - .AddFactory>() - .AddTransient() - .AddSingleton() - .For() - .Named("xxx") - .Named("yyy") - .Named("oops", ServiceLifetime.Singleton) - .Named(ManagerType.Good) - .Named(ManagerType.Bad, ServiceLifetime.Singleton) - .Services; + .AddSingleton() + .AddFactory() + .AddFactory>() + .AddTransient() + .AddSingleton() + .For() + .Named("xxx") + .Named("yyy") + .Named("oops", ServiceLifetime.Singleton) + .Named(ManagerType.Good) + .Named(ManagerType.Bad, ServiceLifetime.Singleton) + .Services; - if (addScopeProvider) - services.AddSingleton>(); + if (addScopeProvider) + services.AddSingleton>(); - return services; - } + return services; } } diff --git a/src/SteroidsDI.Tests/SteroidsDI.Tests.csproj b/src/SteroidsDI.Tests/SteroidsDI.Tests.csproj index f01cf8d..4f9eab3 100644 --- a/src/SteroidsDI.Tests/SteroidsDI.Tests.csproj +++ b/src/SteroidsDI.Tests/SteroidsDI.Tests.csproj @@ -1,7 +1,7 @@ - + - netcoreapp3.1;net5 + netcoreapp3.1;net5;net6 false $(NoWarn);1591 diff --git a/src/SteroidsDI/DelegatedDefer.cs b/src/SteroidsDI/DelegatedDefer.cs index 5633389..8e77778 100644 --- a/src/SteroidsDI/DelegatedDefer.cs +++ b/src/SteroidsDI/DelegatedDefer.cs @@ -1,19 +1,17 @@ -using System; using Microsoft.Extensions.Options; -namespace SteroidsDI -{ - internal sealed class DelegatedDefer : Defer - { - private readonly IServiceProvider _provider; - private readonly ServiceProviderAdvancedOptions _options; +namespace SteroidsDI; - public DelegatedDefer(IServiceProvider provider, IOptions options) - { - _provider = provider; - _options = options.Value; - } +internal sealed class DelegatedDefer : Defer +{ + private readonly IServiceProvider _provider; + private readonly ServiceProviderAdvancedOptions _options; - public override T Value => _provider.Resolve(_options); + public DelegatedDefer(IServiceProvider provider, IOptions options) + { + _provider = provider; + _options = options.Value; } + + public override T Value => _provider.Resolve(_options); } diff --git a/src/SteroidsDI/Extensions/ServiceCollectionExtensions.cs b/src/SteroidsDI/Extensions/ServiceCollectionExtensions.cs index b3a6ff6..b784d8c 100644 --- a/src/SteroidsDI/Extensions/ServiceCollectionExtensions.cs +++ b/src/SteroidsDI/Extensions/ServiceCollectionExtensions.cs @@ -1,95 +1,93 @@ -using System; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using SteroidsDI; using SteroidsDI.Core; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// Extension methods for . +public static class ServiceCollectionExtensions { - /// Extension methods for . - public static class ServiceCollectionExtensions - { - private static IServiceCollection AddAdvancedOptions(this IServiceCollection services) - => services.Configure(opt => opt.Services = services); + private static IServiceCollection AddAdvancedOptions(this IServiceCollection services) + => services.Configure(opt => opt.Services = services); - /// Gets the binding context for the type . - /// The service type which context is customized. - /// A collection of DI container services. - /// Binding context. - public static BindingContext For(this IServiceCollection services) - where TService : class => new BindingContext(services); + /// Gets the binding context for the type . + /// The service type which context is customized. + /// A collection of DI container services. + /// Binding context. + public static BindingContext For(this IServiceCollection services) + where TService : class => new BindingContext(services); - /// Add the specified type to the DI container as a factory that performs factory methods for creating objects. - /// A collection of DI container services. - /// Factory type. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddFactory(this IServiceCollection services, Type factoryType) - { - if (factoryType == null) - throw new ArgumentNullException(nameof(factoryType)); + /// Add the specified type to the DI container as a factory that performs factory methods for creating objects. + /// A collection of DI container services. + /// Factory type. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddFactory(this IServiceCollection services, Type factoryType) + { + if (factoryType == null) + throw new ArgumentNullException(nameof(factoryType)); - services.AddAdvancedOptions(); - services.TryAddSingleton(factoryType, FactoryGenerator.Generate(factoryType)); - return services; - } + services.AddAdvancedOptions(); + services.TryAddSingleton(factoryType, FactoryGenerator.Generate(factoryType)); + return services; + } - /// Add the specified type to the DI container as a factory that performs factory methods for creating objects. - /// Factory type. - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddFactory(this IServiceCollection services) - => services.AddAdvancedOptions().AddFactory(typeof(TFactory)); + /// Add the specified type to the DI container as a factory that performs factory methods for creating objects. + /// Factory type. + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddFactory(this IServiceCollection services) + => services.AddAdvancedOptions().AddFactory(typeof(TFactory)); - /// - /// Register the factory to create an object of type . - /// This factory can find/select the correct scope (if one exists at all) through which you need to get the required object. - /// Differs from AddDefer by the fact that it works for only one specified - /// type, that is, this method may need to be called several times. - /// - /// Service type. - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddFunc(this IServiceCollection services) - => services.AddAdvancedOptions().AddSingleton(provider => - { - var options = provider.GetRequiredService>(); - return new Func(() => provider.Resolve(options.Value)); - }); + /// + /// Register the factory to create an object of type . + /// This factory can find/select the correct scope (if one exists at all) through which you need to get the required object. + /// Differs from AddDefer by the fact that it works for only one specified + /// type, that is, this method may need to be called several times. + /// + /// Service type. + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddFunc(this IServiceCollection services) + => services.AddAdvancedOptions().AddSingleton(provider => + { + var options = provider.GetRequiredService>(); + return new Func(() => provider.Resolve(options.Value)); + }); - /// - /// Adds support for and - deferring resolving - /// the object in the desired scope. The effect is completely similar to one from - /// AddFunc with the difference - /// that this method works immediately for all objects registered in the DI container. - /// - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddDefer(this IServiceCollection services) - => services.AddAdvancedOptions() - .AddSingleton(typeof(Defer<>), typeof(DelegatedDefer<>)) - .AddSingleton(typeof(IDefer<>), typeof(DelegatedDefer<>)); + /// + /// Adds support for and - deferring resolving + /// the object in the desired scope. The effect is completely similar to one from + /// AddFunc with the difference + /// that this method works immediately for all objects registered in the DI container. + /// + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddDefer(this IServiceCollection services) + => services.AddAdvancedOptions() + .AddSingleton(typeof(Defer<>), typeof(DelegatedDefer<>)) + .AddSingleton(typeof(IDefer<>), typeof(DelegatedDefer<>)); - /// Register in DI as one of the possible implementations of . - /// - /// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique - /// closed type, thereby providing its own storage, to which only it will have access. - /// - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddGenericScope(this IServiceCollection services) - { - services.AddMicrosoftScopeFactory(); // this is not necessary, but in 99.99% of cases this is exactly what you need - services.TryAddEnumerable(ServiceDescriptor.Singleton>()); - return services; - } + /// Register in DI as one of the possible implementations of . + /// + /// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique + /// closed type, thereby providing its own storage, to which only it will have access. + /// + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddGenericScope(this IServiceCollection services) + { + services.AddMicrosoftScopeFactory(); // this is not necessary, but in 99.99% of cases this is exactly what you need + services.TryAddEnumerable(ServiceDescriptor.Singleton>()); + return services; + } - /// Register in DI as one of the possible the implementations of . - /// A collection of DI container services. - /// Reference to the passed object to be able to call methods in a chain. - public static IServiceCollection AddMicrosoftScopeFactory(this IServiceCollection services) - { - services.TryAddSingleton(); - return services; - } + /// Register in DI as one of the possible the implementations of . + /// A collection of DI container services. + /// Reference to the passed object to be able to call methods in a chain. + public static IServiceCollection AddMicrosoftScopeFactory(this IServiceCollection services) + { + services.TryAddSingleton(); + return services; } } diff --git a/src/SteroidsDI/Factory/BindingContext.cs b/src/SteroidsDI/Factory/BindingContext.cs index cd61c13..267ef3f 100644 --- a/src/SteroidsDI/Factory/BindingContext.cs +++ b/src/SteroidsDI/Factory/BindingContext.cs @@ -1,77 +1,74 @@ -using System; -using System.Linq; using Microsoft.Extensions.DependencyInjection; -namespace SteroidsDI +namespace SteroidsDI; + +/// +/// Binding context for type . Currently serving named bindings, i.e. the context is +/// only the binding name, but may add additional context in the future. +/// +/// The service type which context is customized. +public sealed class BindingContext { /// - /// Binding context for type . Currently serving named bindings, i.e. the context is - /// only the binding name, but may add additional context in the future. + /// Initializes a new instance of the class to bind to the + /// type inside the given . /// - /// The service type which context is customized. - public sealed class BindingContext + /// A collection of DI container services. + public BindingContext(IServiceCollection services) { - /// - /// Initializes a new instance of the class to bind to the - /// type inside the given . - /// - /// A collection of DI container services. - public BindingContext(IServiceCollection services) - { - Services = services; - } + Services = services; + } - /// A collection of DI container services. - public IServiceCollection Services { get; } + /// A collection of DI container services. + public IServiceCollection Services { get; } - /// Register a named binding from type to type . - /// Implementation type. - /// - /// The name of the binding. The name of the binding can be not only a string, but an arbitrary object. - /// This object selects the required binding in the place where there is a binding context. - /// - /// The lifetime of the dependency object. - /// Reference to this to be able to call methods in a chain. - public BindingContext Named(object name, ServiceLifetime lifetime = ServiceLifetime.Transient) - where TImplementation : TService - { - if (name == null) - throw new ArgumentNullException(nameof(name), "No binding name specified."); + /// Register a named binding from type to type . + /// Implementation type. + /// + /// The name of the binding. The name of the binding can be not only a string, but an arbitrary object. + /// This object selects the required binding in the place where there is a binding context. + /// + /// The lifetime of the dependency object. + /// Reference to this to be able to call methods in a chain. + public BindingContext Named(object name, ServiceLifetime lifetime = ServiceLifetime.Transient) + where TImplementation : TService + { + if (name == null) + throw new ArgumentNullException(nameof(name), "No binding name specified."); - var existing = Services.SingleOrDefault(descriptor => descriptor.ServiceType == typeof(TImplementation)); - if (existing != null && (existing.ImplementationType != typeof(TImplementation) || existing.Lifetime != lifetime || existing.ImplementationFactory != null || existing.ImplementationInstance != null)) - { - throw new InvalidOperationException($@"It is not possible to add a named binding '{name}' for type {typeof(TService)}, because the DI container + var existing = Services.SingleOrDefault(descriptor => descriptor.ServiceType == typeof(TImplementation)); + if (existing != null && (existing.ImplementationType != typeof(TImplementation) || existing.Lifetime != lifetime || existing.ImplementationFactory != null || existing.ImplementationInstance != null)) + { + throw new InvalidOperationException($@"It is not possible to add a named binding '{name}' for type {typeof(TService)}, because the DI container already has a binding on type {typeof(TImplementation)} with different characteristics. This is a limitation of the current implementation."); - } + } - if (existing == null) - Services.Add(new ServiceDescriptor(typeof(TImplementation), typeof(TImplementation), lifetime)); + if (existing == null) + Services.Add(new ServiceDescriptor(typeof(TImplementation), typeof(TImplementation), lifetime)); - Services.AddSingleton(new NamedBinding(name, typeof(TService), typeof(TImplementation))); + Services.AddSingleton(new NamedBinding(name, typeof(TService), typeof(TImplementation))); - return this; - } + return this; + } - /// - /// Register a named binding from type to type - /// with a lifetime equal to the lifetime of the object from the default binding. - /// - /// Implementation type. - /// - /// The name of the binding. The name of the binding can be not only a string, but an arbitrary object. - /// This object selects the required binding in the place where there is a binding context. - /// - /// Reference to this to be able to call methods in a chain. - public BindingContext Named(object name) - where TImplementation : TService - => Named(name, GetServiceLifetime()); + /// + /// Register a named binding from type to type + /// with a lifetime equal to the lifetime of the object from the default binding. + /// + /// Implementation type. + /// + /// The name of the binding. The name of the binding can be not only a string, but an arbitrary object. + /// This object selects the required binding in the place where there is a binding context. + /// + /// Reference to this to be able to call methods in a chain. + public BindingContext Named(object name) + where TImplementation : TService + => Named(name, GetServiceLifetime()); - private ServiceLifetime GetServiceLifetime() - { - return Services.FirstOrDefault(s => s.ServiceType == typeof(TService))?.Lifetime - ?? throw new InvalidOperationException($@"The DI container does not have a default binding for the type '{typeof(TService)}', so it is not possible to determine the value of Lifetime. + private ServiceLifetime GetServiceLifetime() + { + return Services.FirstOrDefault(s => s.ServiceType == typeof(TService))?.Lifetime + ?? throw new InvalidOperationException($@"The DI container does not have a default binding for the type '{typeof(TService)}', so it is not possible to determine the value of Lifetime. Use the 'Named' overload with explicit Lifetime or first set the default binding in the DI container."); - } } } diff --git a/src/SteroidsDI/Factory/FactoryGenerator.cs b/src/SteroidsDI/Factory/FactoryGenerator.cs index 1e16ed9..7463d0f 100644 --- a/src/SteroidsDI/Factory/FactoryGenerator.cs +++ b/src/SteroidsDI/Factory/FactoryGenerator.cs @@ -1,139 +1,135 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; using System.Reflection.Emit; using Microsoft.Extensions.Options; -namespace SteroidsDI +namespace SteroidsDI; + +/// +/// The class generates a factory using the specified factory interface. The factory implementation delegates +/// resolve of objects to the appropriate . +/// +/// See the manually written IMegaFactory_Generated example class in the test assembly. +internal static class FactoryGenerator { - /// - /// The class generates a factory using the specified factory interface. The factory implementation delegates - /// resolve of objects to the appropriate . - /// - /// See the manually written IMegaFactory_Generated example class in the test assembly. - internal static class FactoryGenerator + private static readonly AssemblyBuilder _asmBuilder; + private static readonly ModuleBuilder _moduleBuilder; + + static FactoryGenerator() { - private static readonly AssemblyBuilder _asmBuilder; - private static readonly ModuleBuilder _moduleBuilder; + var asmName = new AssemblyName("DynamicAssembly_Factory_Projections"); + _asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndCollect); + _moduleBuilder = _asmBuilder.DefineDynamicModule(asmName.Name); + } - static FactoryGenerator() - { - var asmName = new AssemblyName("DynamicAssembly_Factory_Projections"); - _asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndCollect); - _moduleBuilder = _asmBuilder.DefineDynamicModule(asmName.Name); - } + private static void AssertType(Type type) + { + if (!type.IsInterface || !type.IsPublic) + throw new InvalidOperationException($"Type '{type}' must be a public interface in order to be able to build a projection."); - private static void AssertType(Type type) + foreach (var member in type.GetMembers()) { - if (!type.IsInterface || !type.IsPublic) - throw new InvalidOperationException($"Type '{type}' must be a public interface in order to be able to build a projection."); - - foreach (var member in type.GetMembers()) - { - if (member.MemberType != MemberTypes.Method) - throw new InvalidOperationException($"A member {member.MemberType} was found in the interface '{type}': '{member.Name}'. Only methods are supported."); - - var parameters = (member as MethodInfo)!.GetParameters(); - if (parameters.Length > 1) - throw new InvalidOperationException($"The {member.Name} method with an invalid signature was detected in the interface '{type}'. Methods without parameters and methods with a single parameter are supported."); - } + if (member.MemberType != MemberTypes.Method) + throw new InvalidOperationException($"A member {member.MemberType} was found in the interface '{type}': '{member.Name}'. Only methods are supported."); + + var parameters = (member as MethodInfo)!.GetParameters(); + if (parameters.Length > 1) + throw new InvalidOperationException($"The {member.Name} method with an invalid signature was detected in the interface '{type}'. Methods without parameters and methods with a single parameter are supported."); } + } - /// Generate a type that implements the specified factory. - /// Factory type. - /// Works both for .NET Framework and .NET Core. - /// A type that can be used as an implementation type for in DI. - public static Type Generate(Type factoryType) - { - if (factoryType == null) - throw new ArgumentNullException(nameof(factoryType)); + /// Generate a type that implements the specified factory. + /// Factory type. + /// Works both for .NET Framework and .NET Core. + /// A type that can be used as an implementation type for in DI. + public static Type Generate(Type factoryType) + { + if (factoryType == null) + throw new ArgumentNullException(nameof(factoryType)); - var alreadyGeneratedType = _moduleBuilder.Assembly.DefinedTypes.FirstOrDefault(t => t.GetInterfaces().Contains(factoryType)); - if (alreadyGeneratedType != null) - return alreadyGeneratedType; + var alreadyGeneratedType = _moduleBuilder.Assembly.DefinedTypes.FirstOrDefault(t => t.GetInterfaces().Contains(factoryType)); + if (alreadyGeneratedType != null) + return alreadyGeneratedType; - AssertType(factoryType); + AssertType(factoryType); - var typeBuilder = _moduleBuilder - .DefineType($"{factoryType.Name.Replace('`', '_')}_DynamicFactory_{Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)}", TypeAttributes.NotPublic | TypeAttributes.Sealed, typeof(object), new[] { factoryType }) - .Generate_Ctor(out var providerField, out var bindingsField, out var optionsField); + var typeBuilder = _moduleBuilder + .DefineType($"{factoryType.Name.Replace('`', '_')}_DynamicFactory_{Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)}", TypeAttributes.NotPublic | TypeAttributes.Sealed, typeof(object), new[] { factoryType }) + .Generate_Ctor(out var providerField, out var bindingsField, out var optionsField); - foreach (var method in factoryType.GetMethods()) - typeBuilder.Generate_Factory_Method(method, providerField, bindingsField, optionsField); + foreach (var method in factoryType.GetMethods()) + typeBuilder.Generate_Factory_Method(method, providerField, bindingsField, optionsField); - return typeBuilder.CreateTypeInfo()!; - } + return typeBuilder.CreateTypeInfo()!; + } - private static TypeBuilder Generate_Ctor(this TypeBuilder typeBuilder, out FieldBuilder providerField, out FieldBuilder bindingsField, out FieldBuilder optionsField) - { - providerField = typeBuilder.DefineField("_provider", typeof(IServiceProvider), FieldAttributes.Private); - bindingsField = typeBuilder.DefineField("_bindings", typeof(List), FieldAttributes.Private); - optionsField = typeBuilder.DefineField("_options", typeof(ServiceProviderAdvancedOptions), FieldAttributes.Private); + private static TypeBuilder Generate_Ctor(this TypeBuilder typeBuilder, out FieldBuilder providerField, out FieldBuilder bindingsField, out FieldBuilder optionsField) + { + providerField = typeBuilder.DefineField("_provider", typeof(IServiceProvider), FieldAttributes.Private); + bindingsField = typeBuilder.DefineField("_bindings", typeof(List), FieldAttributes.Private); + optionsField = typeBuilder.DefineField("_options", typeof(ServiceProviderAdvancedOptions), FieldAttributes.Private); - var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IServiceProvider), typeof(IEnumerable), typeof(IOptions) }); + var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IServiceProvider), typeof(IEnumerable), typeof(IOptions) }); - var ctorIL = ctorBuilder.GetILGenerator(); + var ctorIL = ctorBuilder.GetILGenerator(); - ctorIL.Emit(OpCodes.Ldarg_0); // this - ctorIL.Emit(OpCodes.Ldarg_1); // provider arg - ctorIL.Emit(OpCodes.Stfld, providerField); + ctorIL.Emit(OpCodes.Ldarg_0); // this + ctorIL.Emit(OpCodes.Ldarg_1); // provider arg + ctorIL.Emit(OpCodes.Stfld, providerField); - ctorIL.Emit(OpCodes.Ldarg_0); // this - ctorIL.Emit(OpCodes.Ldarg_2); // bindings arg - ctorIL.Emit(OpCodes.Call, typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(typeof(NamedBinding))); - ctorIL.Emit(OpCodes.Stfld, bindingsField); + ctorIL.Emit(OpCodes.Ldarg_0); // this + ctorIL.Emit(OpCodes.Ldarg_2); // bindings arg + ctorIL.Emit(OpCodes.Call, typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(typeof(NamedBinding))); + ctorIL.Emit(OpCodes.Stfld, bindingsField); - ctorIL.Emit(OpCodes.Ldarg_0); // this - ctorIL.Emit(OpCodes.Ldarg_3); // options arg - ctorIL.Emit(OpCodes.Callvirt, typeof(IOptions).GetProperty("Value").GetGetMethod()); - ctorIL.Emit(OpCodes.Stfld, optionsField); + ctorIL.Emit(OpCodes.Ldarg_0); // this + ctorIL.Emit(OpCodes.Ldarg_3); // options arg + ctorIL.Emit(OpCodes.Callvirt, typeof(IOptions).GetProperty("Value").GetGetMethod()); + ctorIL.Emit(OpCodes.Stfld, optionsField); - ctorIL.Emit(OpCodes.Ret); + ctorIL.Emit(OpCodes.Ret); - return typeBuilder; - } + return typeBuilder; + } + + private static TypeBuilder Generate_Factory_Method(this TypeBuilder typeBuilder, MethodInfo method, FieldBuilder providerField, FieldBuilder bindingsField, FieldBuilder optionsField) + { + var methodBuilder = typeBuilder.DefineMethod( + method.Name, + MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, + method.ReturnType, + Type.EmptyTypes); - private static TypeBuilder Generate_Factory_Method(this TypeBuilder typeBuilder, MethodInfo method, FieldBuilder providerField, FieldBuilder bindingsField, FieldBuilder optionsField) + var parameter = method.GetParameters().FirstOrDefault(); + + var ilGenerator = methodBuilder.GetILGenerator(); + + if (parameter == null) { - var methodBuilder = typeBuilder.DefineMethod( - method.Name, - MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, - method.ReturnType, - Type.EmptyTypes); - - var parameter = method.GetParameters().FirstOrDefault(); - - var ilGenerator = methodBuilder.GetILGenerator(); - - if (parameter == null) - { - ilGenerator.Emit(OpCodes.Ldarg_0); // this - ilGenerator.Emit(OpCodes.Ldfld, providerField); - ilGenerator.Emit(OpCodes.Ldarg_0); // this - ilGenerator.Emit(OpCodes.Ldfld, optionsField); - ilGenerator.Emit(OpCodes.Call, typeof(Resolver).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(m => m.Name == nameof(Resolver.Resolve) && m.IsGenericMethod == true).MakeGenericMethod(method.ReturnType)); - ilGenerator.Emit(OpCodes.Ret); - } - else - { - methodBuilder.SetParameters(parameter.ParameterType); - - ilGenerator.Emit(OpCodes.Ldarg_0); // this - ilGenerator.Emit(OpCodes.Ldfld, providerField); - ilGenerator.Emit(OpCodes.Ldarg_1); // name arg - if (parameter.ParameterType.IsValueType) - ilGenerator.Emit(OpCodes.Box, parameter.ParameterType); - ilGenerator.Emit(OpCodes.Ldarg_0); // this - ilGenerator.Emit(OpCodes.Ldfld, bindingsField); - ilGenerator.Emit(OpCodes.Ldarg_0); // this - ilGenerator.Emit(OpCodes.Ldfld, optionsField); - ilGenerator.Emit(OpCodes.Call, typeof(Resolver).GetMethod(nameof(Resolver.ResolveByNamedBinding), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(method.ReturnType)); - ilGenerator.Emit(OpCodes.Ret); - } - - return typeBuilder; + ilGenerator.Emit(OpCodes.Ldarg_0); // this + ilGenerator.Emit(OpCodes.Ldfld, providerField); + ilGenerator.Emit(OpCodes.Ldarg_0); // this + ilGenerator.Emit(OpCodes.Ldfld, optionsField); + ilGenerator.Emit(OpCodes.Call, typeof(Resolver).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(m => m.Name == nameof(Resolver.Resolve) && m.IsGenericMethod == true).MakeGenericMethod(method.ReturnType)); + ilGenerator.Emit(OpCodes.Ret); } + else + { + methodBuilder.SetParameters(parameter.ParameterType); + + ilGenerator.Emit(OpCodes.Ldarg_0); // this + ilGenerator.Emit(OpCodes.Ldfld, providerField); + ilGenerator.Emit(OpCodes.Ldarg_1); // name arg + if (parameter.ParameterType.IsValueType) + ilGenerator.Emit(OpCodes.Box, parameter.ParameterType); + ilGenerator.Emit(OpCodes.Ldarg_0); // this + ilGenerator.Emit(OpCodes.Ldfld, bindingsField); + ilGenerator.Emit(OpCodes.Ldarg_0); // this + ilGenerator.Emit(OpCodes.Ldfld, optionsField); + ilGenerator.Emit(OpCodes.Call, typeof(Resolver).GetMethod(nameof(Resolver.ResolveByNamedBinding), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(method.ReturnType)); + ilGenerator.Emit(OpCodes.Ret); + } + + return typeBuilder; } } diff --git a/src/SteroidsDI/Factory/NamedBinding.cs b/src/SteroidsDI/Factory/NamedBinding.cs index c90bef7..222d9de 100644 --- a/src/SteroidsDI/Factory/NamedBinding.cs +++ b/src/SteroidsDI/Factory/NamedBinding.cs @@ -1,29 +1,27 @@ -using System; using System.Diagnostics; -namespace SteroidsDI +namespace SteroidsDI; + +/// +/// Named binding, binds the type to the type +/// in the required context. +/// +[DebuggerDisplay("{Name}: {ServiceType.Name} -> {ImplementationType.Name}")] +internal sealed class NamedBinding { - /// - /// Named binding, binds the type to the type - /// in the required context. - /// - [DebuggerDisplay("{Name}: {ServiceType.Name} -> {ImplementationType.Name}")] - internal sealed class NamedBinding + public NamedBinding(object name, Type serviceType, Type implementationType) { - public NamedBinding(object name, Type serviceType, Type implementationType) - { - Name = name; - ServiceType = serviceType; - ImplementationType = implementationType; - } + Name = name; + ServiceType = serviceType; + ImplementationType = implementationType; + } - /// Gets the name of the binding. An arbitrary object, not just a string. - public object Name { get; } + /// Gets the name of the binding. An arbitrary object, not just a string. + public object Name { get; } - /// Gets the service type. - public Type ServiceType { get; } + /// Gets the service type. + public Type ServiceType { get; } - /// Gets the service implementation type. - public Type ImplementationType { get; } - } + /// Gets the service implementation type. + public Type ImplementationType { get; } } diff --git a/src/SteroidsDI/GenericScopeProvider.cs b/src/SteroidsDI/GenericScopeProvider.cs index 24df7bb..7a0bc53 100644 --- a/src/SteroidsDI/GenericScopeProvider.cs +++ b/src/SteroidsDI/GenericScopeProvider.cs @@ -1,17 +1,15 @@ -using System; using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI +namespace SteroidsDI; + +/// A generic working with . +/// +/// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique +/// closed type, thereby providing its own storage, to which only it will have access. +/// +public sealed class GenericScopeProvider : IScopeProvider { - /// A generic working with . - /// - /// An arbitrary type that is used to create various static AsyncLocal fields. The caller may set unique - /// closed type, thereby providing its own storage, to which only it will have access. - /// - public sealed class GenericScopeProvider : IScopeProvider - { - /// - public IServiceProvider? GetScopedServiceProvider(IServiceProvider root) => (GenericScope.CurrentScope as IServiceScope)?.ServiceProvider; - } + /// + public IServiceProvider? GetScopedServiceProvider(IServiceProvider root) => (GenericScope.CurrentScope as IServiceScope)?.ServiceProvider; } diff --git a/src/SteroidsDI/MicrosoftScopeFactory.cs b/src/SteroidsDI/MicrosoftScopeFactory.cs index 553b45d..c11dd09 100644 --- a/src/SteroidsDI/MicrosoftScopeFactory.cs +++ b/src/SteroidsDI/MicrosoftScopeFactory.cs @@ -1,23 +1,21 @@ -using System; using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI -{ - /// Adapter from to . - public sealed class MicrosoftScopeFactory : IScopeFactory - { - private readonly IServiceScopeFactory _serviceScopeFactory; +namespace SteroidsDI; - /// Initializes a new instance of the . - /// Native MSDI factory for creating scopes. - public MicrosoftScopeFactory(IServiceScopeFactory serviceScopeFactory) - { - _serviceScopeFactory = serviceScopeFactory; - } +/// Adapter from to . +public sealed class MicrosoftScopeFactory : IScopeFactory +{ + private readonly IServiceScopeFactory _serviceScopeFactory; - /// Create scope. - /// Scope object which should be destroyed at the end of scope. - public IDisposable CreateScope() => _serviceScopeFactory.CreateScope(); + /// Initializes a new instance of the . + /// Native MSDI factory for creating scopes. + public MicrosoftScopeFactory(IServiceScopeFactory serviceScopeFactory) + { + _serviceScopeFactory = serviceScopeFactory; } + + /// Create scope. + /// Scope object which should be destroyed at the end of scope. + public IDisposable CreateScope() => _serviceScopeFactory.CreateScope(); } diff --git a/src/SteroidsDI/Resolver.cs b/src/SteroidsDI/Resolver.cs index a29eab5..8d4fbd0 100644 --- a/src/SteroidsDI/Resolver.cs +++ b/src/SteroidsDI/Resolver.cs @@ -1,74 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using SteroidsDI.Core; -namespace SteroidsDI +namespace SteroidsDI; + +internal static class Resolver { - internal static class Resolver + internal static TService ResolveByNamedBinding(this IServiceProvider provider, object name, IEnumerable bindings, ServiceProviderAdvancedOptions options) { - internal static TService ResolveByNamedBinding(this IServiceProvider provider, object name, IEnumerable bindings, ServiceProviderAdvancedOptions options) - { - var binding = bindings.Where(b => b.ServiceType == typeof(TService)).SingleOrDefault(b => b.Name.Equals(name)); - return binding == null - ? throw new InvalidOperationException($"Destination type not found for named binding '{name}' to type '{typeof(TService)}'. Verify that a named binding is specified in the DI container.") - : (TService)provider.Resolve(binding.ImplementationType, options); - } + var binding = bindings.Where(b => b.ServiceType == typeof(TService)).SingleOrDefault(b => b.Name.Equals(name)); + return binding == null + ? throw new InvalidOperationException($"Destination type not found for named binding '{name}' to type '{typeof(TService)}'. Verify that a named binding is specified in the DI container.") + : (TService)provider.Resolve(binding.ImplementationType, options); + } - internal static TService Resolve(this IServiceProvider provider, ServiceProviderAdvancedOptions options) => (TService)provider.Resolve(typeof(TService), options); + internal static TService Resolve(this IServiceProvider provider, ServiceProviderAdvancedOptions options) => (TService)provider.Resolve(typeof(TService), options); - internal static object Resolve(this IServiceProvider provider, Type type, ServiceProviderAdvancedOptions options) + internal static object Resolve(this IServiceProvider provider, Type type, ServiceProviderAdvancedOptions options) + { + // An application can have several entry points that initialize their scopes. + var scopeProviders = provider.GetRequiredService>(); + + // If at any given time there is one of the scopes, then we use it. + foreach (var scopeProvider in scopeProviders) { - // An application can have several entry points that initialize their scopes. - var scopeProviders = provider.GetRequiredService>(); + var scopedServiceProvider = scopeProvider.GetScopedServiceProvider(provider); - // If at any given time there is one of the scopes, then we use it. - foreach (var scopeProvider in scopeProviders) + // The first scope found is used. Generally speaking, a situation where several providers can return a non null scope at the same time is unlikely. + // Providers characterize some entry point of the application, which processes requests from the outside. It can be, for example, an HTTP server or + // a message broker listener (RabbitMQ, Kafka, etc). Such entry points, by the nature are isolated from each other and do not interfere. Theoretically, + // it’s possible (intentionally or by mistake) to create such a situation, when nevertheless more than 1 scope will exist simultaneously. In this case, + // the possibility of validation is added. + if (scopedServiceProvider != null) { - var scopedServiceProvider = scopeProvider.GetScopedServiceProvider(provider); - - // The first scope found is used. Generally speaking, a situation where several providers can return a non null scope at the same time is unlikely. - // Providers characterize some entry point of the application, which processes requests from the outside. It can be, for example, an HTTP server or - // a message broker listener (RabbitMQ, Kafka, etc). Such entry points, by the nature are isolated from each other and do not interfere. Theoretically, - // it’s possible (intentionally or by mistake) to create such a situation, when nevertheless more than 1 scope will exist simultaneously. In this case, - // the possibility of validation is added. - if (scopedServiceProvider != null) + if (options.ValidateParallelScopes) { - if (options.ValidateParallelScopes) + // The exception of scopeProvider and then adding it to the set is made in order not to call GetScopedServiceProvider() on it again, which + // theoretically, can be costly and/or cause side effects (in practice this should not be, but there are concerns). + var parallelScopeProviders = scopeProviders.Except(new[] { scopeProvider }).Where(p => p.GetScopedServiceProvider(provider) != null).ToList(); + if (parallelScopeProviders.Count > 0) { - // The exception of scopeProvider and then adding it to the set is made in order not to call GetScopedServiceProvider() on it again, which - // theoretically, can be costly and/or cause side effects (in practice this should not be, but there are concerns). - var parallelScopeProviders = scopeProviders.Except(new[] { scopeProvider }).Where(p => p.GetScopedServiceProvider(provider) != null).ToList(); - if (parallelScopeProviders.Count > 0) - { - parallelScopeProviders.Add(scopeProvider); - throw new InvalidOperationException($@"When '{nameof(ServiceProviderAdvancedOptions)}.{nameof(ServiceProviderAdvancedOptions.ValidateParallelScopes)}' option is turned on, the simultaneous existence of several scopes from different providers was detected. + parallelScopeProviders.Add(scopeProvider); + throw new InvalidOperationException($@"When '{nameof(ServiceProviderAdvancedOptions)}.{nameof(ServiceProviderAdvancedOptions.ValidateParallelScopes)}' option is turned on, the simultaneous existence of several scopes from different providers was detected. Scopes obtained from the following providers: {string.Join(", ", parallelScopeProviders.Select(p => p.ToString()))}"); - } } - - return scopedServiceProvider.GetRequiredService(type); } + + return scopedServiceProvider.GetRequiredService(type); } + } - // With this type, there can be several descriptors in the container (or none), in this case we check the latter. - // MSDI has such behavior of Last Win when resolving dependencies, do the same here. - bool scopeRequired = options.Services.LastOrDefault(s => s.ServiceType == type)?.Lifetime == ServiceLifetime.Scoped; - if (scopeRequired) - { - throw new InvalidOperationException($@"An error occurred while resolving the type '{type.Name}' + // With this type, there can be several descriptors in the container (or none), in this case we check the latter. + // MSDI has such behavior of Last Win when resolving dependencies, do the same here. + bool scopeRequired = options.Services.LastOrDefault(s => s.ServiceType == type)?.Lifetime == ServiceLifetime.Scoped; + if (scopeRequired) + { + throw new InvalidOperationException($@"An error occurred while resolving the type '{type.Name}' The type is declared as scoped within the context of the request, but an attempt to resolve the type is made outside the context of the request. An application can simultaneously have several entry points that initialize their request contexts. Be sure to add the required provider (IScopeProvider) to the container using the TryAddEnumerable method."); - } + } - // In the absence of scopes and the possibility of obtaining TService from the root provider, use it to resolve the dependency - return options.AllowRootProviderResolve - ? provider.GetRequiredService(type) - : throw new InvalidOperationException($@"The current scope is missing. Unable to get object of type '{type.Name}' from the root provider. + // In the absence of scopes and the possibility of obtaining TService from the root provider, use it to resolve the dependency + return options.AllowRootProviderResolve + ? provider.GetRequiredService(type) + : throw new InvalidOperationException($@"The current scope is missing. Unable to get object of type '{type.Name}' from the root provider. Be sure to add the required provider (IScopeProvider) to the container using the TryAddEnumerable method or a special method from your transport library. An object can be obtained from the root provider if it has a non-scoped lifetime and the parameter '{nameof(ServiceProviderAdvancedOptions)}.{nameof(ServiceProviderAdvancedOptions.AllowRootProviderResolve)}' = true."); - } } } diff --git a/src/SteroidsDI/ServiceProviderAdvancedOptions.cs b/src/SteroidsDI/ServiceProviderAdvancedOptions.cs index d303d5e..ddb94a3 100644 --- a/src/SteroidsDI/ServiceProviderAdvancedOptions.cs +++ b/src/SteroidsDI/ServiceProviderAdvancedOptions.cs @@ -1,26 +1,24 @@ -using System; using Microsoft.Extensions.DependencyInjection; -namespace SteroidsDI +namespace SteroidsDI; + +/// +/// Options to customize the behavior of when used in Func / Defer / Factory. +/// +public sealed class ServiceProviderAdvancedOptions { /// - /// Options to customize the behavior of when used in Func / Defer / Factory. + /// to validate the situation with the presence of parallel scopes from different providers. The situation is unlikely. /// - public sealed class ServiceProviderAdvancedOptions - { - /// - /// to validate the situation with the presence of parallel scopes from different providers. The situation is unlikely. - /// - public bool ValidateParallelScopes { get; set; } + public bool ValidateParallelScopes { get; set; } - /// - /// Allows resolving objects through the root provider if the current scope is missing. The object must have - /// lifetime different from scoped. Getting scoped objects through the root provider is ALWAYS FORBIDDEN. - /// Defaults to . - /// - public bool AllowRootProviderResolve { get; set; } + /// + /// Allows resolving objects through the root provider if the current scope is missing. The object must have + /// lifetime different from scoped. Getting scoped objects through the root provider is ALWAYS FORBIDDEN. + /// Defaults to . + /// + public bool AllowRootProviderResolve { get; set; } - /// For internal use only. - internal IServiceCollection? Services { get; set; } - } + /// For internal use only. + internal IServiceCollection? Services { get; set; } }