Skip to content

Commit

Permalink
#1518 Create building the IServiceCollection (#1986)
Browse files Browse the repository at this point in the history
* fix: create building the servicecollection

* fix: handle default configuration

* chore: extension methods invocation

* Update src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs

* Code review by @raman-m

* No warnings for CS0618

* Given developer forgot to add environment, then?... Ups! Your Duck, duck!
  Hunting for `new FileInfo("")` bug... Done!

* Coverage 100%

---------

Co-authored-by: Adrien HUPOND <adrien.hupond@csgroup.eu>
Co-authored-by: Raman Maksimchuk <dotnet044@gmail.com>
  • Loading branch information
3 people authored Mar 21, 2024
1 parent d6eefc8 commit ded4d7e
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,16 @@ private static IConfigurationBuilder ApplyMergeOcelotJsonOption(IConfigurationBu

private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env,
FileConfiguration fileConfiguration = null, string primaryFile = null, string globalFile = null, string environmentFile = null)
{
environmentFile ??= env?.EnvironmentName != null ? string.Format(EnvironmentConfigFile, env.EnvironmentName) : string.Empty;
{
var envName = string.IsNullOrEmpty(env?.EnvironmentName) ? "Development" : env.EnvironmentName;
environmentFile ??= string.Format(EnvironmentConfigFile, envName);
var reg = SubConfigRegex();
var environmentFileInfo = new FileInfo(environmentFile);
var files = new DirectoryInfo(folder)
.EnumerateFiles()
.Where(fi => reg.IsMatch(fi.Name) && fi.Name != environmentFileInfo.Name && fi.FullName != environmentFileInfo.FullName)
.Where(fi => reg.IsMatch(fi.Name) &&
!fi.Name.Equals(environmentFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
!fi.FullName.Equals(environmentFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
.ToArray();

fileConfiguration ??= new FileConfiguration();
Expand Down
34 changes: 28 additions & 6 deletions src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;

namespace Ocelot.DependencyInjection;
Expand All @@ -8,7 +10,8 @@ public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds default ASP.NET services and Ocelot application services.<br/>
/// Creates default <see cref="IConfiguration"/> object via the <see cref="ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection)"/> extension-method.
/// Creates default <see cref="IConfiguration"/> from the current service descriptors.
/// If the configuration is not registered, it will try to read ocelot configuration from current working directory.
/// </summary>
/// <remarks>
/// Remarks for default ASP.NET services being injected see in docs of the <see cref="OcelotBuilder.AddDefaultAspNetServices(IMvcCoreBuilder, Assembly)"/> method.
Expand All @@ -17,15 +20,15 @@ public static class ServiceCollectionExtensions
/// <returns>An <see cref="IOcelotBuilder"/> object.</returns>
public static IOcelotBuilder AddOcelot(this IServiceCollection services)
{
var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
var configuration = services.FindConfiguration(null);
return new OcelotBuilder(services, configuration);
}

/// <summary>
/// Adds default ASP.NET services and Ocelot application services with configuration.
/// </summary>
/// <remarks>
/// Remarks for default ASP.NET services being injected see in docs of the <see cref="OcelotBuilder.AddDefaultAspNetServices(IMvcCoreBuilder, Assembly)"/> method.
/// Remarks for default ASP.NET services will be injected, see docs of the <see cref="OcelotBuilder.AddDefaultAspNetServices(IMvcCoreBuilder, Assembly)"/> method.
/// </remarks>
/// <param name="services">Current services collection.</param>
/// <param name="configuration">Current web app configuration.</param>
Expand All @@ -37,17 +40,19 @@ public static IOcelotBuilder AddOcelot(this IServiceCollection services, IConfig

/// <summary>
/// Adds Ocelot application services and custom ASP.NET services with custom builder.<br/>
/// Creates default <see cref="IConfiguration"/> object via the <see cref="ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection)"/> extension-method.
/// Creates default <see cref="IConfiguration"/> from the current service descriptors.
/// If the configuration is not registered, it will try to read ocelot configuration from current working directory.
/// </summary>
/// <remarks>
/// Warning! To understand which ASP.NET services should be injected/removed by custom builder, see docs of the <see cref="OcelotBuilder.AddDefaultAspNetServices(IMvcCoreBuilder, Assembly)"/> method.
/// </remarks>
/// <param name="services">Current services collection.</param>
/// <param name="customBuilder">Current custom builder for ASP.NET MVC pipeline.</param>
/// <returns>An <see cref="IOcelotBuilder"/> object.</returns>
[Obsolete("Use AddOcelotUsingBuilder() overloaded version with the 'IConfiguration configuration' parameter.")]
public static IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder)
{
var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
var configuration = services.FindConfiguration(null);
return new OcelotBuilder(services, configuration, customBuilder);
}

Expand All @@ -63,6 +68,23 @@ public static IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection servi
/// <returns>An <see cref="IOcelotBuilder"/> object.</returns>
public static IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder)
{
configuration ??= services.FindConfiguration(null);
return new OcelotBuilder(services, configuration, customBuilder);
}

private static IConfiguration DefaultConfiguration(IWebHostEnvironment env)
=> new ConfigurationBuilder().AddOcelot(env).Build();

private static IConfiguration FindConfiguration(this IServiceCollection services, IWebHostEnvironment env)
{
var descriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IConfiguration));
if (descriptor == null)
{
return DefaultConfiguration(env);
}

var provider = new ServiceCollection().Add(descriptor).BuildServiceProvider();
var configuration = provider.GetService<IConfiguration>();
return configuration ?? DefaultConfiguration(env);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,15 @@ protected override string EnvironmentName()
public void Should_add_base_url_to_config()
{
// Arrange
#pragma warning disable CS0618
_configuration = new ConfigurationBuilder()
.AddOcelotBaseUrl("test")
.Build();
#pragma warning restore CS0618

// Act
var actual = _configuration.GetValue("BaseUrl", string.Empty);

// Assert
actual.ShouldBe("test");

}

[Fact]
Expand Down Expand Up @@ -121,6 +118,26 @@ public void Should_merge_files_to_memory()
ThenTheConfigsAreMergedAndAddedInApplicationConfiguration(false);
TheOcelotPrimaryConfigFileExists(false);
}

[Fact]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
public void Should_merge_files_with_null_environment()
{
// Arrange
_environmentConfigFileName = null; // Ups!
const IWebHostEnvironment NullEnvironment = null; // Wow!
GivenMultipleConfigurationFiles(TestID, false);

// Act
_configRoot = new ConfigurationBuilder()
.AddOcelot(TestID, NullEnvironment, MergeOcelotJson.ToMemory, _primaryConfigFileName, _globalConfigFileName, _environmentConfigFileName, false, false)
.Build();

// Assert
ThenTheConfigsAreMergedAndAddedInApplicationConfiguration(false);
TheOcelotPrimaryConfigFileExists(false);
}

private void GivenCombinedFileConfigurationObject()
{
Expand Down
45 changes: 37 additions & 8 deletions test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Ocelot.Configuration.Setter;
using Ocelot.DependencyInjection;
using Ocelot.Infrastructure;
Expand All @@ -30,9 +29,9 @@ namespace Ocelot.UnitTests.DependencyInjection
{
public class OcelotBuilderTests
{
private readonly IConfiguration _configRoot;
private readonly IServiceCollection _services;
private IServiceProvider _serviceProvider;
private readonly IConfiguration _configRoot;
private IOcelotBuilder _ocelotBuilder;
private Exception _ex;

Expand Down Expand Up @@ -273,11 +272,31 @@ private void CstorShouldUseDefaultBuilderToInitMvcCoreBuilder()
}

[Fact]
public void Should_use_custom_mvc_builder()
public void Should_use_custom_mvc_builder_no_configuration()
{
this.Given(x => x.WhenISetupOcelotServicesWithCustomMvcBuider())
.Then(x => CstorShouldUseCustomBuilderToInitMvcCoreBuilder())
.BDDfy();
// Arrange, Act
WhenISetupOcelotServicesWithCustomMvcBuider();

// Assert
CstorShouldUseCustomBuilderToInitMvcCoreBuilder();
ShouldFindConfiguration();
}

[Theory]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
[InlineData(false)]
[InlineData(true)]
public void Should_use_custom_mvc_builder_with_configuration(bool hasConfig)
{
// Arrange, Act
WhenISetupOcelotServicesWithCustomMvcBuider(
hasConfig ? _configRoot : null,
true);

// Assert
CstorShouldUseCustomBuilderToInitMvcCoreBuilder();
ShouldFindConfiguration();
}

private bool _fakeCustomBuilderCalled;
Expand All @@ -293,12 +312,14 @@ private IMvcCoreBuilder FakeCustomBuilder(IMvcCoreBuilder builder, Assembly asse
});
}

private void WhenISetupOcelotServicesWithCustomMvcBuider()
private void WhenISetupOcelotServicesWithCustomMvcBuider(IConfiguration configuration = null, bool useConfigParam = false)
{
_fakeCustomBuilderCalled = false;
try
{
_ocelotBuilder = _services.AddOcelotUsingBuilder(FakeCustomBuilder);
_ocelotBuilder = !useConfigParam
? _services.AddOcelotUsingBuilder(FakeCustomBuilder)
: _services.AddOcelotUsingBuilder(configuration, FakeCustomBuilder);
}
catch (Exception e)
{
Expand Down Expand Up @@ -326,6 +347,14 @@ private void CstorShouldUseCustomBuilderToInitMvcCoreBuilder()
.ShouldNotBeNull().ShouldBeOfType<ConfigureNamedOptions<JsonOptions>>();
}

private void ShouldFindConfiguration()
{
_ocelotBuilder.ShouldNotBeNull();
var actual = _ocelotBuilder.Configuration.ShouldNotBeNull();
actual.Equals(_configRoot).ShouldBeTrue(); // check references equality
actual.ShouldBe(_configRoot);
}

private void AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Ocelot.DependencyInjection;
using System.Reflection;
using Extensions = Ocelot.DependencyInjection.ServiceCollectionExtensions;

namespace Ocelot.UnitTests.DependencyInjection;

public class ServiceCollectionExtensionsTests
{
[Fact]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
public void AddOcelot_NoConfiguration_DefaultConfiguration()
{
// Arrange
var services = new ServiceCollection();

// Act
var ocelot = services.AddOcelot();

// Assert
ocelot.ShouldNotBeNull()
.Configuration.ShouldNotBeNull();
}

[Theory]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
[InlineData(false)]
[InlineData(true)]
public void FindConfiguration_HasDescriptor_HappyPath(bool hasConfig)
{
// Arrange
IConfiguration config = hasConfig ? new ConfigurationBuilder().Build() : null;
var descriptor = new ServiceDescriptor(typeof(IConfiguration), (p) => config, ServiceLifetime.Transient);
var services = new ServiceCollection().Add(descriptor);
IWebHostEnvironment env = null;

// Act
var method = typeof(Extensions).GetMethod("FindConfiguration", BindingFlags.NonPublic | BindingFlags.Static);
var actual = (IConfiguration)method.Invoke(null, [services, env]);

// Assert
actual.ShouldNotBeNull();
if (hasConfig)
{
actual.Equals(config).ShouldBeTrue();
}
}

[Fact]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
public void AddOcelotUsingBuilder_NoConfigurationParam_ShouldFindConfiguration()
{
// Arrange
var services = new ServiceCollection();
var config = new ConfigurationBuilder().Build();
services.AddSingleton<IConfiguration>(config);

// Act
var ocelot = services.AddOcelotUsingBuilder(null, CustomBuilder);

// Assert
AssertConfiguration(ocelot, config);
}

[Theory]
[Trait("PR", "1986")]
[Trait("Issue", "1518")]
[InlineData(false)]
[InlineData(true)]
public void AddOcelotUsingBuilder_WithConfigurationParam_ShouldFindConfiguration(bool shouldFind)
{
// Arrange
var services = new ServiceCollection();
var config = new ConfigurationBuilder().Build();
if (shouldFind)
{
services.AddSingleton<IConfiguration>(config);
}

// Act
var ocelot = services.AddOcelotUsingBuilder(shouldFind ? null : config, CustomBuilder);

// Assert
AssertConfiguration(ocelot, config);
}

private void AssertConfiguration(IOcelotBuilder ocelot, IConfiguration config)
{
ocelot.ShouldNotBeNull();
var actual = ocelot.Configuration.ShouldNotBeNull();
actual.Equals(config).ShouldBeTrue(); // check references equality
actual.ShouldBe(config);
Assert.Equal(1, _count);
}

private int _count;

private IMvcCoreBuilder CustomBuilder(IMvcCoreBuilder builder, Assembly assembly)
{
_count++;
return builder;
}
}
2 changes: 1 addition & 1 deletion test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;CS0618</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
Expand Down

0 comments on commit ded4d7e

Please sign in to comment.