diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 18e089a77..6497cad7d 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -62,10 +62,10 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder .Where(fi => reg.IsMatch(fi.Name) && fi.Name != excludeConfigName) .ToArray(); - dynamic fileConfiguration = new ExpandoObject(); - fileConfiguration.GlobalConfiguration = new ExpandoObject(); - fileConfiguration.Aggregates = new List(); - fileConfiguration.ReRoutes = new List(); + dynamic fileConfiguration = new JObject(); + fileConfiguration.GlobalConfiguration = new JObject(); + fileConfiguration.Aggregates = new JArray(); + fileConfiguration.ReRoutes = new JArray(); foreach (var file in files) { @@ -75,8 +75,7 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder } var lines = File.ReadAllText(file.FullName); - - dynamic config = JsonConvert.DeserializeObject(lines); + dynamic config = JToken.Parse(lines); if (file.Name.Equals(GlobalConfigFile, StringComparison.OrdinalIgnoreCase)) { @@ -106,25 +105,22 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder return builder.AddJsonFile(PrimaryConfigFile, false, false); } - private static void TryAddSection(ExpandoObject mergedConfig, ExpandoObject config, string sectionName) + private static void TryAddSection(JToken mergedConfig, JToken config, string sectionName) { - var configAsDict = config as IDictionary; - var mergedConfigAsDict = mergedConfig as IDictionary; - if (configAsDict.ContainsKey(sectionName) && mergedConfigAsDict.ContainsKey(sectionName)) + var mergedConfigSection = mergedConfig[sectionName]; + var configSection = config[sectionName]; + + if (configSection != null) { - var mergedSectionAsExpando = mergedConfigAsDict[sectionName] as ExpandoObject; - if (mergedSectionAsExpando != null) + if (configSection is JObject) { - mergedConfigAsDict[sectionName] = configAsDict[sectionName]; + mergedConfig[sectionName] = configSection; } - else + else if (configSection is JArray) { - var mergedSectionAsList = mergedConfigAsDict[sectionName] as List; - var sectionAsList = configAsDict[sectionName] as List; - - mergedSectionAsList.AddRange(sectionAsList); + (mergedConfigSection as JArray).Merge(configSection); } - } - } + } + } } } diff --git a/test/Ocelot.AcceptanceTests/ConfigurationMergeTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationMergeTests.cs new file mode 100644 index 000000000..2a24cb6f7 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ConfigurationMergeTests.cs @@ -0,0 +1,94 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Ocelot.Configuration.File; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationMergeTests + { + private Steps _steps; + private IWebHostBuilder _webHostBuilder; + private TestServer _ocelotServer; + private HttpClient _ocelotClient; + + public ConfigurationMergeTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_merge_reroutes_custom_properties() + { + this.Given(x => GivenOcelotIsRunningWithMultipleConfigs()) + .And(x => ThenConfigContentShouldBeMerged()) + .BDDfy(); + } + + private void GivenOcelotIsRunningWithMultipleConfigs() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddOcelot("MergeConfiguration", hostingContext.HostingEnvironment); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + private void ThenConfigContentShouldBeMerged() + { + var mergedConfigFileName = "ocelot.json"; + File.Exists(mergedConfigFileName).ShouldBeTrue(); + var lines = File.ReadAllText(mergedConfigFileName); + var config = JObject.Parse(lines); + + config[nameof(FileConfiguration.ReRoutes)].ShouldNotBeNull(); + config[nameof(FileConfiguration.ReRoutes)].Children().Count().ShouldBe(3); + + var routeWithCustomProperty = config[nameof(FileConfiguration.ReRoutes)].Children().SingleOrDefault(c => c["CustomStrategyProperty"] != null); + routeWithCustomProperty.ShouldNotBeNull(); + var customProperty = routeWithCustomProperty["CustomStrategyProperty"]; + customProperty["GET"].ShouldNotBeNull(); + customProperty["GET"].Children().Count().ShouldBe(1); + customProperty["GET"].Children().FirstOrDefault().ShouldBe("SomeCustomStrategyMethodA"); + customProperty["POST"].ShouldNotBeNull(); + customProperty["POST"].Children().Count().ShouldBe(1); + customProperty["POST"].Children().FirstOrDefault().ShouldBe("SomeCustomStrategyMethodB"); + + var routeWithCustomProperty2 = config[nameof(FileConfiguration.ReRoutes)].Children().SingleOrDefault(c => c["somethingmore"] != null); + routeWithCustomProperty2.ShouldNotBeNull(); + routeWithCustomProperty2["somethingmore"].ShouldBe("something"); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.global.json b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.global.json new file mode 100644 index 000000000..cf4c03f2b --- /dev/null +++ b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.global.json @@ -0,0 +1,20 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/{global}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 54001 + } + ], + "UpstreamPathTemplate": "/{global}", + "UpstreamHttpMethod": [ "Get" ], + "somethingmore": "something" + } + ], + "GlobalConfiguration": { + "BaseUrl": "http://api.test.com" + } +} diff --git a/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.xservices.json b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.xservices.json new file mode 100644 index 000000000..d98453f78 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.xservices.json @@ -0,0 +1,27 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/xservice.svc/serviceMethodA/byNumber/{number}", + "UpstreamPathTemplate": "/gw/serviceMethodA/byNumber/{number}", + "UpstreamHttpMethod": [ + "Get", + "Post" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "host", + "Port": 80 + } + ], + "CustomStrategyProperty": { + "GET": [ + "SomeCustomStrategyMethodA" + ], + "POST": [ + "SomeCustomStrategyMethodB" + ] + } + } + ] +} diff --git a/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.yservices.json b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.yservices.json new file mode 100644 index 000000000..86ed135af --- /dev/null +++ b/test/Ocelot.AcceptanceTests/MergeConfiguration/ocelot.yservices.json @@ -0,0 +1,17 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/b/{action}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 54001 + } + ], + "UpstreamPathTemplate": "/b/{action}", + "UpstreamHttpMethod": [ "Get" ], + "MyCustomProperty": "bbb" + } + ] +} diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index b998eba40..7ed77278a 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -18,6 +18,22 @@ True 1591 + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + +