Merge remote-tracking branch 'origin/v14/dev' into v14/dev
This commit is contained in:
@@ -43,7 +43,7 @@ public class SetStatusRedirectUrlManagementController : RedirectUrlManagementCon
|
||||
// For now I'm not gonna change this to limit breaking, but it's weird to have a "disabled" switch,
|
||||
// since you're essentially negating the boolean from the get go,
|
||||
// it's much easier to reason with enabled = false == disabled.
|
||||
_configManipulator.SaveDisableRedirectUrlTracking(!enable);
|
||||
await _configManipulator.SaveDisableRedirectUrlTrackingAsync(!enable);
|
||||
|
||||
// Taken from the existing implementation in RedirectUrlManagementController
|
||||
// TODO this is ridiculous, but we need to ensure the configuration is reloaded, before this request is ended.
|
||||
|
||||
@@ -2,13 +2,81 @@ namespace Umbraco.Cms.Core.Configuration;
|
||||
|
||||
public interface IConfigManipulator
|
||||
{
|
||||
[Obsolete("Use RemoveConnectionStringAsync instead, scheduled for removal in V16.")]
|
||||
void RemoveConnectionString();
|
||||
|
||||
[Obsolete("Use SaveConnectionStringAsync instead, scheduled for removal in V16.")]
|
||||
void SaveConnectionString(string connectionString, string? providerName);
|
||||
|
||||
[Obsolete("Use SaveConfigValueAsync instead, scheduled for removal in V16.")]
|
||||
void SaveConfigValue(string itemPath, object value);
|
||||
|
||||
[Obsolete("Use SaveDisableRedirectUrlTrackingAsync instead, scheduled for removal in V16.")]
|
||||
void SaveDisableRedirectUrlTracking(bool disable);
|
||||
|
||||
[Obsolete("Use SetGlobalIdAsync instead, scheduled for removal in V16.")]
|
||||
void SetGlobalId(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the connection string from the configuration file
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RemoveConnectionStringAsync()
|
||||
{
|
||||
RemoveConnectionString();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the connection string to the configuration file
|
||||
/// </summary>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <param name="providerName"></param>
|
||||
/// <returns></returns>
|
||||
Task SaveConnectionStringAsync(string connectionString, string? providerName)
|
||||
{
|
||||
SaveConnectionString(connectionString, providerName);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a value in the configuration file.
|
||||
/// <remarks>Will only update an existing key in the configuration file, if it does not exists nothing is saved</remarks>
|
||||
/// </summary>
|
||||
/// <param name="itemPath">Path to update, uses : as the separator.</param>
|
||||
/// <param name="value">The new value.</param>
|
||||
/// <returns></returns>
|
||||
Task SaveConfigValueAsync(string itemPath, object value)
|
||||
{
|
||||
SaveConfigValue(itemPath, value);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the disableRedirectUrlTracking value in the configuration file.
|
||||
/// <remarks>
|
||||
/// Will create the node if it does not already exist.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="disable">The value to save.</param>
|
||||
/// <returns></returns>
|
||||
Task SaveDisableRedirectUrlTrackingAsync(bool disable)
|
||||
{
|
||||
SaveDisableRedirectUrlTracking(disable);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the global id in the configuration file.
|
||||
/// <remarks>
|
||||
/// Will create the node if it does not already exist.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="id">The ID to save.</param>
|
||||
/// <returns></returns>
|
||||
Task SetGlobalIdAsync(string id)
|
||||
{
|
||||
SetGlobalId(id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ internal class SiteIdentifierService : ISiteIdentifierService
|
||||
|
||||
try
|
||||
{
|
||||
_configManipulator.SetGlobalId(createdGuid.ToString());
|
||||
_configManipulator.SetGlobalIdAsync(createdGuid.ToString()).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,329 +1,300 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration
|
||||
namespace Umbraco.Cms.Infrastructure.Configuration;
|
||||
|
||||
internal class JsonConfigManipulator : IConfigManipulator
|
||||
{
|
||||
public class JsonConfigManipulator : IConfigManipulator
|
||||
private const string ConnectionStringObjectName = "ConnectionStrings";
|
||||
private const string UmbracoConnectionStringPath = $"{ConnectionStringObjectName}:{Constants.System.UmbracoConnectionName}";
|
||||
private const string UmbracoConnectionStringProviderNamePath = UmbracoConnectionStringPath + ConnectionStrings.ProviderNamePostfix;
|
||||
private const string CmsObjectPath = "Umbraco:CMS";
|
||||
private const string GlobalIdPath = $"{CmsObjectPath}:Global:Id";
|
||||
private const string DisableRedirectUrlTrackingPath = $"{CmsObjectPath}:WebRouting:DisableRedirectUrlTracking";
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<JsonConfigManipulator> _logger;
|
||||
private readonly SemaphoreSlim _lock = new(1, 1);
|
||||
|
||||
public JsonConfigManipulator(IConfiguration configuration, ILogger<JsonConfigManipulator> logger)
|
||||
{
|
||||
private const string UmbracoConnectionStringPath = $"ConnectionStrings:{Cms.Core.Constants.System.UmbracoConnectionName}";
|
||||
private const string UmbracoConnectionStringProviderNamePath = UmbracoConnectionStringPath + ConnectionStrings.ProviderNamePostfix;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<JsonConfigManipulator> _logger;
|
||||
private readonly object _locker = new object();
|
||||
public void RemoveConnectionString()
|
||||
=> RemoveConnectionStringAsync().GetAwaiter().GetResult();
|
||||
|
||||
public JsonConfigManipulator(IConfiguration configuration, ILogger<JsonConfigManipulator> logger)
|
||||
/// <inheritdoc />
|
||||
public async Task RemoveConnectionStringAsync()
|
||||
{
|
||||
JsonConfigurationProvider? provider = GetJsonConfigurationProvider(UmbracoConnectionStringPath);
|
||||
|
||||
JsonNode? jsonNode = await GetJsonNodeAsync(provider);
|
||||
|
||||
if (jsonNode is null)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_logger.LogWarning("Failed to remove connection string from JSON configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public string UmbracoConnectionPath { get; } = UmbracoConnectionStringPath;
|
||||
RemoveJsonNode(jsonNode, UmbracoConnectionStringPath);
|
||||
RemoveJsonNode(jsonNode, UmbracoConnectionStringProviderNamePath);
|
||||
|
||||
public void RemoveConnectionString()
|
||||
await SaveJsonAsync(provider, jsonNode);
|
||||
}
|
||||
|
||||
public void SaveConnectionString(string connectionString, string? providerName)
|
||||
=> SaveConnectionStringAsync(connectionString, providerName).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveConnectionStringAsync(string connectionString, string? providerName)
|
||||
{
|
||||
JsonConfigurationProvider? provider = GetJsonConfigurationProvider();
|
||||
JsonNode? node = await GetJsonNodeAsync(provider);
|
||||
|
||||
if (node is null)
|
||||
{
|
||||
// Remove keys from JSON
|
||||
var provider = GetJsonConfigurationProvider(UmbracoConnectionStringPath);
|
||||
|
||||
var json = GetJson(provider);
|
||||
if (json is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to remove connection string from JSON configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveJsonKey(json, UmbracoConnectionStringPath);
|
||||
RemoveJsonKey(json, UmbracoConnectionStringProviderNamePath);
|
||||
|
||||
SaveJson(provider, json);
|
||||
_logger.LogWarning("Was unable to load the configuration file to save the connection string");
|
||||
return;
|
||||
}
|
||||
|
||||
public void SaveConnectionString(string connectionString, string? providerName)
|
||||
CreateOrUpdateJsonNode(node, UmbracoConnectionStringPath, connectionString);
|
||||
if (providerName is not null)
|
||||
{
|
||||
// Save keys to JSON
|
||||
var provider = GetJsonConfigurationProvider();
|
||||
|
||||
var json = GetJson(provider);
|
||||
if (json is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to save connection string in JSON configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
var item = GetConnectionItem(connectionString, providerName);
|
||||
if (item is not null)
|
||||
{
|
||||
json.Merge(item, new JsonMergeSettings());
|
||||
}
|
||||
|
||||
SaveJson(provider, json);
|
||||
CreateOrUpdateJsonNode(node, UmbracoConnectionStringProviderNamePath, providerName);
|
||||
}
|
||||
|
||||
public void SaveConfigValue(string key, object value)
|
||||
await SaveJsonAsync(provider, node);
|
||||
}
|
||||
|
||||
public void SaveConfigValue(string key, object value)
|
||||
=> SaveConfigValueAsync(key, value).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveConfigValueAsync(string itemPath, object value)
|
||||
{
|
||||
JsonConfigurationProvider? provider = GetJsonConfigurationProvider();
|
||||
JsonNode? node = await GetJsonNodeAsync(provider);
|
||||
|
||||
if (node is null)
|
||||
{
|
||||
// Save key to JSON
|
||||
var provider = GetJsonConfigurationProvider();
|
||||
|
||||
var json = GetJson(provider);
|
||||
if (json is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to save configuration key \"{Key}\" in JSON configuration.", key);
|
||||
return;
|
||||
}
|
||||
|
||||
JToken? token = json;
|
||||
foreach (var propertyName in key.Split(new[] { ':' }))
|
||||
{
|
||||
if (token is null)
|
||||
break;
|
||||
token = CaseSelectPropertyValues(token, propertyName);
|
||||
}
|
||||
|
||||
if (token is null)
|
||||
return;
|
||||
|
||||
var writer = new JTokenWriter();
|
||||
writer.WriteValue(value);
|
||||
|
||||
if (writer.Token is not null)
|
||||
{
|
||||
token.Replace(writer.Token);
|
||||
}
|
||||
|
||||
SaveJson(provider, json);
|
||||
_logger.LogWarning("Failed to save configuration key \"{Key}\" in JSON configuration", itemPath);
|
||||
return;
|
||||
}
|
||||
|
||||
public void SaveDisableRedirectUrlTracking(bool disable)
|
||||
JsonNode? propertyNode = node;
|
||||
foreach (var propertyName in itemPath.Split(':'))
|
||||
{
|
||||
// Save key to JSON
|
||||
var provider = GetJsonConfigurationProvider();
|
||||
|
||||
var json = GetJson(provider);
|
||||
if (json is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to save enabled/disabled state for redirect URL tracking in JSON configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
var item = GetDisableRedirectUrlItem(disable);
|
||||
if (item is not null)
|
||||
{
|
||||
json.Merge(item, new JsonMergeSettings());
|
||||
}
|
||||
|
||||
SaveJson(provider, json);
|
||||
propertyNode = FindChildNode(propertyNode, propertyName);
|
||||
}
|
||||
|
||||
public void SetGlobalId(string id)
|
||||
if (propertyNode is null)
|
||||
{
|
||||
// Save key to JSON
|
||||
var provider = GetJsonConfigurationProvider();
|
||||
|
||||
var json = GetJson(provider);
|
||||
if (json is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to save global identifier in JSON configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
var item = GetGlobalIdItem(id);
|
||||
if (item is not null)
|
||||
{
|
||||
json.Merge(item, new JsonMergeSettings());
|
||||
}
|
||||
|
||||
SaveJson(provider, json);
|
||||
return;
|
||||
}
|
||||
|
||||
private object? GetGlobalIdItem(string id)
|
||||
propertyNode.ReplaceWith(value);
|
||||
await SaveJsonAsync(provider, node);
|
||||
}
|
||||
|
||||
public void SaveDisableRedirectUrlTracking(bool disable)
|
||||
=> SaveDisableRedirectUrlTrackingAsync(disable).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveDisableRedirectUrlTrackingAsync(bool disable)
|
||||
=> await CreateOrUpdateConfigValueAsync(DisableRedirectUrlTrackingPath, disable);
|
||||
|
||||
public void SetGlobalId(string id)
|
||||
=> SetGlobalIdAsync(id).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetGlobalIdAsync(string id)
|
||||
=> await CreateOrUpdateConfigValueAsync(GlobalIdPath, id);
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates a config value at the specified path.
|
||||
/// <remarks>This causes a rewrite of the configuration file.</remarks>
|
||||
/// </summary>
|
||||
/// <param name="itemPath">Path to update, uses : as the separator.</param>
|
||||
/// <param name="value">The value of the node.</param>
|
||||
private async Task CreateOrUpdateConfigValueAsync(string itemPath, object value)
|
||||
{
|
||||
JsonConfigurationProvider? provider = GetJsonConfigurationProvider();
|
||||
JsonNode? node = await GetJsonNodeAsync(provider);
|
||||
|
||||
if (node is null)
|
||||
{
|
||||
JTokenWriter writer = new JTokenWriter();
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Umbraco");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("CMS");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Global");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Id");
|
||||
writer.WriteValue(id);
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
|
||||
return writer.Token;
|
||||
_logger.LogWarning("Failed to save configuration key \"{Key}\" in JSON configuration", itemPath);
|
||||
return;
|
||||
}
|
||||
|
||||
private JToken? GetDisableRedirectUrlItem(bool value)
|
||||
CreateOrUpdateJsonNode(node, itemPath, value);
|
||||
await SaveJsonAsync(provider, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates or creates a json node at the specified path.
|
||||
/// <remarks>
|
||||
/// Will also create any missing nodes in the path.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="node">Node to create or update.</param>
|
||||
/// <param name="itemPath">Path to create or update, uses : as the separator.</param>
|
||||
/// <param name="value">The value of the node.</param>
|
||||
private static void CreateOrUpdateJsonNode(JsonNode node, string itemPath, object value)
|
||||
{
|
||||
// This is required because System.Text.Json has no merge function, and doesn't support patch
|
||||
// this is a problem because we don't know if the key(s) exists yet, so we can't simply update it,
|
||||
// we may have to create one ore more json objects.
|
||||
|
||||
// First we find the inner most child that already exists.
|
||||
var propertyNames = itemPath.Split(':');
|
||||
JsonNode propertyNode = node;
|
||||
var index = 0;
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
JTokenWriter writer = new JTokenWriter();
|
||||
JsonNode? found = FindChildNode(propertyNode, propertyName);
|
||||
if (found is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Umbraco");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("CMS");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("WebRouting");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("DisableRedirectUrlTracking");
|
||||
writer.WriteValue(value);
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
|
||||
return writer.Token;
|
||||
propertyNode = found;
|
||||
index++;
|
||||
}
|
||||
|
||||
private JToken? GetConnectionItem(string connectionString, string? providerName)
|
||||
|
||||
// We can now use the index to go through the remaining keys, creating them as we go.
|
||||
while (index < propertyNames.Length)
|
||||
{
|
||||
JTokenWriter writer = new JTokenWriter();
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("ConnectionStrings");
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName(Constants.System.UmbracoConnectionName);
|
||||
writer.WriteValue(connectionString);
|
||||
|
||||
if (!string.IsNullOrEmpty(providerName))
|
||||
{
|
||||
writer.WritePropertyName(Constants.System.UmbracoConnectionName + ConnectionStrings.ProviderNamePostfix);
|
||||
writer.WriteValue(providerName);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndObject();
|
||||
|
||||
return writer.Token;
|
||||
var propertyName = propertyNames[index];
|
||||
var newNode = new JsonObject();
|
||||
propertyNode.AsObject()[propertyName] = newNode;
|
||||
propertyNode = newNode;
|
||||
index++;
|
||||
}
|
||||
|
||||
private static void RemoveJsonKey(JObject? json, string key)
|
||||
{
|
||||
JToken? token = json;
|
||||
foreach (var propertyName in key.Split(new[] { ':' }))
|
||||
{
|
||||
token = CaseSelectPropertyValues(token, propertyName);
|
||||
}
|
||||
// System.Text.Json doesn't like just setting an Object as a value, so instead we first create the node,
|
||||
// and then replace the value
|
||||
propertyNode.ReplaceWith(value);
|
||||
}
|
||||
|
||||
token?.Parent?.Remove();
|
||||
private static void RemoveJsonNode(JsonNode node, string key)
|
||||
{
|
||||
JsonNode? propertyNode = node;
|
||||
foreach (var propertyName in key.Split(':'))
|
||||
{
|
||||
propertyNode = FindChildNode(propertyNode, propertyName);
|
||||
}
|
||||
|
||||
private void SaveJson(JsonConfigurationProvider? provider, JObject? json)
|
||||
propertyNode?.Parent?.AsObject().Remove(propertyNode.GetPropertyName());
|
||||
}
|
||||
|
||||
private async Task SaveJsonAsync(JsonConfigurationProvider? provider, JsonNode jsonNode)
|
||||
{
|
||||
if (provider?.Source.FileProvider is not PhysicalFileProvider physicalFileProvider)
|
||||
{
|
||||
if (provider is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider)
|
||||
{
|
||||
var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path!);
|
||||
|
||||
try
|
||||
{
|
||||
using (var sw = new StreamWriter(jsonFilePath, false))
|
||||
using (var jsonTextWriter = new JsonTextWriter(sw)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
})
|
||||
{
|
||||
json?.WriteTo(jsonTextWriter);
|
||||
}
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "JSON configuration could not be written: {path}", jsonFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private JObject? GetJson(JsonConfigurationProvider? provider)
|
||||
await _lock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if (provider is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (provider.Source.FileProvider is not PhysicalFileProvider physicalFileProvider)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path!);
|
||||
|
||||
try
|
||||
{
|
||||
var serializer = new JsonSerializer();
|
||||
using var sr = new StreamReader(jsonFilePath);
|
||||
using var jsonTextReader = new JsonTextReader(sr);
|
||||
return serializer.Deserialize<JObject>(jsonTextReader);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "JSON configuration could not be read: {path}", jsonFilePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path!);
|
||||
await using var jsonConfigStream = new FileStream(jsonFilePath, FileMode.Create);
|
||||
await using var writer = new Utf8JsonWriter(jsonConfigStream, new JsonWriterOptions { Indented = true });
|
||||
jsonNode.WriteTo(writer);
|
||||
}
|
||||
|
||||
private JsonConfigurationProvider? GetJsonConfigurationProvider(string? requiredKey = null)
|
||||
finally
|
||||
{
|
||||
if (_configuration is IConfigurationRoot configurationRoot)
|
||||
{
|
||||
foreach (var provider in configurationRoot.Providers)
|
||||
{
|
||||
if (provider is JsonConfigurationProvider jsonConfigurationProvider &&
|
||||
(requiredKey is null || provider.TryGet(requiredKey, out _)))
|
||||
{
|
||||
return jsonConfigurationProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the property value when case insensative
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is required because keys are case insensative in IConfiguration.
|
||||
/// JObject[..] do not support case insensative and JObject.Property(...) do not return a new JObject.
|
||||
/// </remarks>
|
||||
private static JToken? CaseSelectPropertyValues(JToken? token, string name)
|
||||
{
|
||||
if (token is JObject obj)
|
||||
{
|
||||
foreach (var property in obj.Properties())
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
return property.Value;
|
||||
}
|
||||
|
||||
if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return property.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
_lock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<JsonNode?> GetJsonNodeAsync(JsonConfigurationProvider? provider)
|
||||
{
|
||||
if (provider is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await _lock.WaitAsync();
|
||||
if (provider.Source.FileProvider is not PhysicalFileProvider physicalFileProvider)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path!);
|
||||
|
||||
try
|
||||
{
|
||||
using var streamReader = new StreamReader(jsonFilePath);
|
||||
return await JsonNode.ParseAsync(streamReader.BaseStream);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "JSON configuration could not be read: {Path}", jsonFilePath);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private JsonConfigurationProvider? GetJsonConfigurationProvider(string? requiredKey = null)
|
||||
{
|
||||
if (_configuration is not IConfigurationRoot configurationRoot)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (IConfigurationProvider provider in configurationRoot.Providers)
|
||||
{
|
||||
if (provider is JsonConfigurationProvider jsonConfigurationProvider &&
|
||||
(requiredKey is null || provider.TryGet(requiredKey, out _)))
|
||||
{
|
||||
return jsonConfigurationProvider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the immediate child with the specified name, in a case insensitive manner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is required since keys are case insensitive in IConfiguration.
|
||||
/// But not in JsonNode.
|
||||
/// </remarks>
|
||||
/// <param name="node">The node to search.</param>
|
||||
/// <param name="key">The key to search for.</param>
|
||||
/// <returns>The found node, null if no match is found.</returns>
|
||||
private static JsonNode? FindChildNode(JsonNode? node, string key)
|
||||
{
|
||||
if (node is null)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, JsonNode?> property in node.AsObject())
|
||||
{
|
||||
if (property.Key.Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return property.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ using Umbraco.Cms.Core.Templates;
|
||||
using Umbraco.Cms.Core.Trees;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
using Umbraco.Cms.Infrastructure.Configuration;
|
||||
using Umbraco.Cms.Infrastructure.DeliveryApi;
|
||||
using Umbraco.Cms.Infrastructure.DistributedLocking;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
|
||||
});
|
||||
|
||||
// Update configuration and wait for change
|
||||
_configManipulator.SaveConnectionString(connectionString, providerName);
|
||||
_configManipulator.SaveConnectionStringAsync(connectionString, providerName).GetAwaiter().GetResult();
|
||||
if (!isChanged.WaitOne(10_000))
|
||||
{
|
||||
throw new InstallException("Didn't retrieve updated connection string within 10 seconds, try manual configuration instead.");
|
||||
|
||||
@@ -55,13 +55,13 @@ public class SiteIdentifierServiceTests
|
||||
|
||||
if (shouldCreate)
|
||||
{
|
||||
configManipulatorMock.Verify(x => x.SetGlobalId(It.IsAny<string>()), Times.Once);
|
||||
configManipulatorMock.Verify(x => x.SetGlobalIdAsync(It.IsAny<string>()), Times.Once);
|
||||
Assert.AreNotEqual(Guid.Empty, identifier);
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
configManipulatorMock.Verify(x => x.SetGlobalId(It.IsAny<string>()), Times.Never());
|
||||
configManipulatorMock.Verify(x => x.SetGlobalIdAsync(It.IsAny<string>()), Times.Never());
|
||||
Assert.AreEqual(guidString.ToLower(), identifier.ToString());
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user