• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ThreeMammals / Ocelot / 28713620273

04 Jul 2026 05:12PM UTC coverage: 96.935% (-0.07%) from 97.004%
28713620273

Pull #1183

github

web-flow
Merge e566a4648 into c5ee51014
Pull Request #1183: #651 Merge custom JSON properties across multiple `ocelot.X.json` configuration files using Newtonsoft's `JToken` merge functionality

6611 of 6820 relevant lines covered (96.94%)

2029.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.11
src/DependencyInjection/ConfigurationBuilderExtensions.cs
1
using Microsoft.AspNetCore.Hosting;
2
using Microsoft.Extensions.Configuration;
3
using Microsoft.Extensions.Configuration.Memory;
4
using Newtonsoft.Json;
5
using Newtonsoft.Json.Linq;
6
using Ocelot.Configuration.File;
7
using Ocelot.Infrastructure;
8

9
namespace Ocelot.DependencyInjection;
10

11
/// <summary>
12
/// Defines extension-methods for the <see cref="IConfigurationBuilder"/> interface.
13
/// </summary>
14
public static partial class ConfigurationBuilderExtensions
15
{
16
    public const string PrimaryConfigFile = "ocelot.json";
17
    public const string GlobalConfigFile = "ocelot.global.json";
18
    public const string EnvironmentConfigFile = "ocelot.{0}.json";
19

20
    [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")]
21
    public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl)
22
    {
1✔
23
        var memorySource = new MemoryConfigurationSource
1✔
24
        {
1✔
25
            InitialData = new List<KeyValuePair<string, string>>
1✔
26
            {
1✔
27
                new("BaseUrl", baseUrl),
1✔
28
            },
1✔
29
        };
1✔
30

31
        return builder.Add(memorySource);
1✔
32
    }
1✔
33

34
    /// <summary>
35
    /// Adds Ocelot configuration by environment, reading the required files from the default path.
36
    /// </summary>
37
    /// <param name="builder">Configuration builder to extend.</param>
38
    /// <param name="env">Web hosting environment object.</param>
39
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
40
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env)
41
        => builder.AddOcelot(".", env);
2✔
42

43
    /// <summary>
44
    /// Adds Ocelot configuration by environment, reading the required files from the specified folder.
45
    /// </summary>
46
    /// <param name="builder">Configuration builder to extend.</param>
47
    /// <param name="folder">Folder to read files from.</param>
48
    /// <param name="env">Web hosting environment object.</param>
49
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
50
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env)
51
        => builder.AddOcelot(folder, env, MergeOcelotJson.ToFile);
2✔
52

53
    /// <summary>
54
    /// Adds Ocelot configuration by environment and merge option, reading the required files from the current default folder.
55
    /// </summary>
56
    /// <remarks>Use optional arguments for injections and overridings.</remarks>
57
    /// <param name="builder">Configuration builder to extend.</param>
58
    /// <param name="env">Web hosting environment object.</param>
59
    /// <param name="mergeTo">Option to merge files to.</param>
60
    /// <param name="primaryConfigFile">Primary config file.</param>
61
    /// <param name="globalConfigFile">Global config file.</param>
62
    /// <param name="environmentConfigFile">Environment config file.</param>
63
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
64
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
65
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
66
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
67
        string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
68
        => builder.AddOcelot(".", env, mergeTo, primaryConfigFile, globalConfigFile, environmentConfigFile, optional, reloadOnChange);
3✔
69

70
    /// <summary>
71
    /// Adds Ocelot configuration by environment and merge option, reading the required files from the specified folder.
72
    /// </summary>
73
    /// <remarks>Use optional arguments for injections and overridings.</remarks>
74
    /// <param name="builder">Configuration builder to extend.</param>
75
    /// <param name="folder">Folder to read files from.</param>
76
    /// <param name="env">Web hosting environment object.</param>
77
    /// <param name="mergeTo">Option to merge files to.</param>
78
    /// <param name="primaryConfigFile">Primary config file.</param>
79
    /// <param name="globalConfigFile">Global config file.</param>
80
    /// <param name="environmentConfigFile">Environment config file.</param>
81
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
82
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
83
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
84
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
85
        string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
86
    {
11✔
87
        var json = GetMergedOcelotJson(folder, env, null, primaryConfigFile, globalConfigFile, environmentConfigFile);
11✔
88
        primaryConfigFile ??= Path.Join(folder, PrimaryConfigFile); // if not specified, merge & write back to the same folder
11✔
89
        return ApplyMergeOcelotJsonOption(builder, mergeTo, json, primaryConfigFile, optional, reloadOnChange);
11✔
90
    }
11✔
91

92
    private static IConfigurationBuilder ApplyMergeOcelotJsonOption(IConfigurationBuilder builder, MergeOcelotJson mergeTo, string json,
93
        string primaryConfigFile, bool? optional, bool? reloadOnChange)
94
    {
17✔
95
        return mergeTo == MergeOcelotJson.ToMemory ? 
17✔
96
            builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) : 
17✔
97
            AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange);
17✔
98
    }
17✔
99

100
    [GeneratedRegex(@"^ocelot\.(.*?)\.json$", RegexOptions.IgnoreCase | RegexOptions.Singleline, RegexGlobal.DefaultMatchTimeoutMilliseconds, "en-US")]
101
    private static partial Regex SubConfigRegex();
102

103
    public static string GetMergedOcelotJson(string folder, IWebHostEnvironment env,
104
        FileConfiguration fileConfiguration = null, string primaryFile = null, string globalFile = null, string environmentFile = null)
105
    {
18✔
106
        // All versions of overloaded AddOcelot methods call this GetMergedOcelotJson one, so we improve Regex performance by cache increasing.
107
        // Developers can adjust the RegexGlobal value BEFORE calling AddOcelot
108
        // Developers can adjust the Regex.CacheSize value AFTER calling AddOcelot
109
        Regex.CacheSize = RegexGlobal.RegexCacheSize;
18✔
110

111
        var envName = string.IsNullOrEmpty(env?.EnvironmentName) ? "Development" : env.EnvironmentName;
18✔
112
        environmentFile ??= Path.Join(folder, string.Format(EnvironmentConfigFile, envName));
18✔
113
        var reg = SubConfigRegex();
18✔
114
        var environmentFileInfo = new FileInfo(environmentFile);
18✔
115
        var files = new DirectoryInfo(folder)
18✔
116
            .EnumerateFiles()
18✔
117
            .Where(fi => reg.IsMatch(fi.Name) &&
18✔
118
                !fi.Name.Equals(environmentFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
18✔
119
                !fi.FullName.Equals(environmentFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
18✔
120
            .ToArray();
18✔
121

122
        fileConfiguration ??= new FileConfiguration();
18✔
123
        dynamic fcMerged = JObject.FromObject(fileConfiguration);
18✔
124
        fcMerged.GlobalConfiguration ??= new JObject();
18✔
125
        fcMerged.Aggregates ??= new JArray();
18✔
126
        fcMerged.Routes ??= new JArray();
18✔
127

128
        primaryFile ??= Path.Join(folder, PrimaryConfigFile);
18✔
129
        globalFile ??= Path.Join(folder, GlobalConfigFile);
18✔
130
        var primaryFileInfo = new FileInfo(primaryFile);
18✔
131
        var globalFileInfo = new FileInfo(globalFile);
18✔
132
        foreach (var file in files)
180✔
133
        {
63✔
134
            if (files.Length > 1 &&
63✔
135
                file.Name.Equals(primaryFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
63✔
136
                file.FullName.Equals(primaryFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
63✔
137
            {
1✔
138
                continue;
1✔
139
            }
140

141
            var lines = File.ReadAllText(file.FullName);
62✔
142
            dynamic config = JToken.Parse(lines);
62✔
143
            bool isGlobal = file.Name.Equals(globalFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
62✔
144
                file.FullName.Equals(globalFileInfo.FullName, StringComparison.OrdinalIgnoreCase);
62✔
145
            MergeConfig(fcMerged, config, isGlobal);
62✔
146
        }
62✔
147

148
        return ((JObject)fcMerged).ToString();
18✔
149
    }
18✔
150

151
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, JObject fileConfiguration)
152
        => SerializeToFile(builder, fileConfiguration);
×
153

154
    /// <summary>
155
    /// Adds Ocelot configuration by ready configuration object and writes JSON to the primary configuration file.<br/>
156
    /// Finally, adds JSON file as configuration provider.
157
    /// </summary>
158
    /// <remarks>Use optional arguments for injections and overridings.</remarks>
159
    /// <param name="builder">Configuration builder to extend.</param>
160
    /// <param name="fileConfiguration">File configuration to add as JSON provider.</param>
161
    /// <param name="primaryConfigFile">Primary config file.</param>
162
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
163
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
164
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
165
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration,
166
        string primaryConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
167
        => SerializeToFile(builder, fileConfiguration);
1✔
168

169
    private static IConfigurationBuilder SerializeToFile(IConfigurationBuilder builder, object fileConfiguration, bool? optional = null, bool? reloadOnChange = null)
170
    {
1✔
171
        var json = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
1✔
172
        return AddOcelotJsonFile(builder, json, PrimaryConfigFile, optional, reloadOnChange);
1✔
173
    }
1✔
174

175
    /// <summary>
176
    /// Adds Ocelot configuration by ready configuration object, environment and merge option, reading the required files from the current default folder.
177
    /// </summary>
178
    /// <param name="builder">Configuration builder to extend.</param>
179
    /// <param name="fileConfiguration">File configuration to add as JSON provider.</param>
180
    /// <param name="env">Web hosting environment object.</param>
181
    /// <param name="mergeTo">Option to merge files to.</param>
182
    /// <param name="primaryConfigFile">Primary config file.</param>
183
    /// <param name="globalConfigFile">Global config file.</param>
184
    /// <param name="environmentConfigFile">Environment config file.</param>
185
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
186
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
187
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
188
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration, IWebHostEnvironment env, MergeOcelotJson mergeTo,
189
        string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
190
    {
6✔
191
        var json = GetMergedOcelotJson(".", env, fileConfiguration, primaryConfigFile, globalConfigFile, environmentConfigFile);
6✔
192
        return ApplyMergeOcelotJsonOption(builder, mergeTo, json, primaryConfigFile, optional, reloadOnChange);
6✔
193
    }
6✔
194

195
    /// <summary>
196
    /// Adds Ocelot primary configuration file (aka ocelot.json).<br/>
197
    /// Writes JSON to the file.<br/>
198
    /// Adds the file as a JSON configuration provider via the <see cref="JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder, string, bool, bool)"/> extension.
199
    /// </summary>
200
    /// <remarks>Use optional arguments for injections and overridings.</remarks>
201
    /// <param name="builder">The builder to extend.</param>
202
    /// <param name="json">JSON data of the Ocelot configuration.</param>
203
    /// <param name="primaryFile">Primary config file.</param>
204
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
205
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
206
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
207
    private static IConfigurationBuilder AddOcelotJsonFile(IConfigurationBuilder builder, string json,
208
        string primaryFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
209
    {
14✔
210
        var primary = primaryFile ?? PrimaryConfigFile;
14✔
211
        File.WriteAllText(primary, json);
14✔
212
        return builder.AddJsonFile(primary, optional ?? false, reloadOnChange ?? false);
14✔
213
    }
14✔
214

215
    /// <summary>
216
    /// Adds Ocelot primary configuration file (aka ocelot.json) in read-only mode.
217
    /// <para>Adds the file as a JSON configuration provider via the <see cref="JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder, string, bool, bool)"/> extension.</para>
218
    /// </summary>
219
    /// <remarks>Use optional arguments for injections and overridings.</remarks>
220
    /// <param name="builder">The builder to extend.</param>
221
    /// <param name="primaryFile">Primary config file path.</param>
222
    /// <param name="optional">The 2nd argument of the AddJsonFile.</param>
223
    /// <param name="reloadOnChange">The 3rd argument of the AddJsonFile.</param>
224
    /// <returns>An <see cref="IConfigurationBuilder"/> object.</returns>
225
    public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder,
226
        string primaryFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
227
        => builder.AddJsonFile(primaryFile ?? PrimaryConfigFile, optional ?? false, reloadOnChange ?? false);
8✔
228

229
    private static void MergeConfig(JToken to, JToken from, bool isGlobal)
230
    {
62✔
231
        if (isGlobal)
62✔
232
            MergeConfigSection(to, from, nameof(FileConfiguration.GlobalConfiguration));
10✔
233

234
        MergeConfigSection(to, from, nameof(FileConfiguration.Aggregates));
62✔
235
        MergeConfigSection(to, from, nameof(FileConfiguration.Routes));
62✔
236
        MergeConfigSection(to, from, nameof(FileConfiguration.DynamicRoutes));
62✔
237
    }
62✔
238

239
    private static void MergeConfigSection(JToken to, JToken from, string sectionName)
240
    {
196✔
241
        var destination = to.GetSection(sectionName);
196✔
242
        var source = from.GetSection(sectionName);
196✔
243
        if (source == null || destination == null)
196✔
244
            return;
66✔
245

246
        if (source is JObject)
130✔
247
            to.SetSection(sectionName, source);
10✔
248
        else if (source is JArray)
120✔
249
            (destination as JArray).Merge(source);
120✔
250
    }
196✔
251

252
    public static JToken GetSection(this JToken token, string name)
253
    {
392✔
254
        var obj = token as JObject;
392✔
255
        return obj?.Properties()
392✔
256
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
392✔
257
            ?.Value;
392✔
258
    }
392✔
259

260
    public static void SetSection(this JToken to, string name, JToken value)
261
    {
10✔
262
        JObject obj = to as JObject;
10✔
263
        var prop = obj?.Properties()
10✔
264
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
10✔
265
        if (prop != null)
10✔
266
            prop.Value = value;
10✔
267
        else
268
            obj.Add(name, value);
×
269
    }
10✔
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc