Skip to content

Commit d7a0331

Browse files
authored
Merge pull request #649 from Azure/rossgrambo/merging-main-to-preview
rossgrambo - merging main to preview
2 parents d7f5939 + 8fdade8 commit d7a0331

16 files changed

+280
-66
lines changed

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
67
using System;
78
using System.Collections.Generic;
89
using System.Security;
@@ -99,11 +100,11 @@ public static IServiceCollection AddAzureAppConfiguration(this IServiceCollectio
99100
if (!_isProviderDisabled)
100101
{
101102
services.AddLogging();
102-
services.AddSingleton<IConfigurationRefresherProvider, AzureAppConfigurationRefresherProvider>();
103+
services.TryAddSingleton<IConfigurationRefresherProvider, AzureAppConfigurationRefresherProvider>();
103104
}
104105
else
105106
{
106-
services.AddSingleton<IConfigurationRefresherProvider, EmptyConfigurationRefresherProvider>();
107+
services.TryAddSingleton<IConfigurationRefresherProvider, EmptyConfigurationRefresherProvider>();
107108
}
108109

109110
return services;

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ internal IEnumerable<IKeyValueAdapter> Adapters
142142
internal bool IsKeyVaultRefreshConfigured { get; private set; } = false;
143143

144144
/// <summary>
145-
/// Indicates all types of feature filters used by the application.
145+
/// Indicates all feature flag features used by the application.
146146
/// </summary>
147147
internal FeatureFlagTracing FeatureFlagTracing { get; set; } = new FeatureFlagTracing();
148148

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,9 +597,21 @@ private async Task<Dictionary<string, string>> PrepareData(Dictionary<string, Co
597597
// Reset old feature flag tracing in order to track the information present in the current response from server.
598598
_options.FeatureFlagTracing.ResetFeatureFlagTracing();
599599

600+
// Reset old request tracing values for content type
601+
if (_requestTracingEnabled && _requestTracingOptions != null)
602+
{
603+
_requestTracingOptions.ResetAiConfigurationTracing();
604+
}
605+
600606
foreach (KeyValuePair<string, ConfigurationSetting> kvp in data)
601607
{
602608
IEnumerable<KeyValuePair<string, string>> keyValuePairs = null;
609+
610+
if (_requestTracingEnabled && _requestTracingOptions != null)
611+
{
612+
_requestTracingOptions.UpdateAiConfigurationTracing(kvp.Value.ContentType);
613+
}
614+
603615
keyValuePairs = await ProcessAdapters(kvp.Value, cancellationToken).ConfigureAwait(false);
604616

605617
foreach (KeyValuePair<string, string> kv in keyValuePairs)
@@ -636,7 +648,7 @@ private async Task LoadAsync(bool ignoreFailures, CancellationToken cancellation
636648
{
637649
IEnumerable<ConfigurationClient> clients = _configClientManager.GetClients();
638650

639-
if (_requestTracingOptions != null)
651+
if (_requestTracingEnabled && _requestTracingOptions != null)
640652
{
641653
_requestTracingOptions.ReplicaCount = clients.Count() - 1;
642654
}

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,38 @@ internal class AzureAppConfigurationRefresherProvider : IConfigurationRefresherP
1313
{
1414
private static readonly PropertyInfo _propertyInfo = typeof(ChainedConfigurationProvider).GetProperty("Configuration", BindingFlags.Public | BindingFlags.Instance);
1515

16-
public IEnumerable<IConfigurationRefresher> Refreshers { get; }
16+
private readonly IConfiguration _configuration;
17+
private readonly ILoggerFactory _loggerFactory;
18+
private IEnumerable<IConfigurationRefresher> _refreshers;
19+
private bool _rediscoveredRefreshers = false;
1720

18-
public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILoggerFactory _loggerFactory)
21+
public IEnumerable<IConfigurationRefresher> Refreshers
1922
{
20-
var configurationRoot = configuration as IConfigurationRoot;
23+
get
24+
{
25+
// Ensure latest refreshers are discovered if the configuration has changed since the constructor was called
26+
if (!_rediscoveredRefreshers)
27+
{
28+
_refreshers = DiscoverRefreshers();
29+
30+
_rediscoveredRefreshers = true;
31+
}
32+
33+
return _refreshers;
34+
}
35+
}
36+
37+
public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILoggerFactory loggerFactory)
38+
{
39+
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
40+
_loggerFactory = loggerFactory;
41+
42+
_refreshers = DiscoverRefreshers();
43+
}
44+
45+
private IEnumerable<IConfigurationRefresher> DiscoverRefreshers()
46+
{
47+
var configurationRoot = _configuration as IConfigurationRoot;
2148
var refreshers = new List<IConfigurationRefresher>();
2249

2350
FindRefreshers(configurationRoot, _loggerFactory, refreshers);
@@ -27,7 +54,7 @@ public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILog
2754
throw new InvalidOperationException("Unable to access the Azure App Configuration provider. Please ensure that it has been configured correctly.");
2855
}
2956

30-
Refreshers = refreshers;
57+
return refreshers;
3158
}
3259

3360
private void FindRefreshers(IConfigurationRoot configurationRoot, ILoggerFactory loggerFactory, List<IConfigurationRefresher> refreshers)

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
using Azure;
55
using Azure.Data.AppConfiguration;
66
using Azure.Security.KeyVault.Secrets;
7+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
8+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
79
using System;
810
using System.Collections.Generic;
911
using System.Linq;
12+
using System.Net.Mime;
1013
using System.Text.Json;
1114
using System.Threading;
1215
using System.Threading.Tasks;
@@ -72,8 +75,15 @@ KeyVaultReferenceException CreateKeyVaultReferenceException(string message, Conf
7275

7376
public bool CanProcess(ConfigurationSetting setting)
7477
{
75-
string contentType = setting?.ContentType?.Split(';')[0].Trim();
76-
return string.Equals(contentType, KeyVaultConstants.ContentType);
78+
if (setting == null ||
79+
string.IsNullOrWhiteSpace(setting.Value) ||
80+
string.IsNullOrWhiteSpace(setting.ContentType))
81+
{
82+
return false;
83+
}
84+
85+
return setting.ContentType.TryParseContentType(out ContentType contentType)
86+
&& contentType.IsKeyVaultReference();
7787
}
7888

7989
public void OnChangeDetected(ConfigurationSetting setting = null)

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSettingPageExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Azure.Data.AppConfiguration;
2-
using Azure;
1+
using Azure;
2+
using Azure.Data.AppConfiguration;
33
using System.Collections.Generic;
44

55
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RequestTracingConstants.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ internal class RequestTracingConstants
3131
public const string ReplicaCountKey = "ReplicaCount";
3232
public const string FeaturesKey = "Features";
3333
public const string LoadBalancingEnabledTag = "LB";
34+
public const string AIConfigurationTag = "AI";
35+
public const string AIChatCompletionConfigurationTag = "AICC";
36+
3437
public const string SignalRUsedTag = "SignalR";
3538
public const string FailoverRequestTag = "Failover";
3639
public const string PushRefreshTag = "PushRefresh";
@@ -54,5 +57,8 @@ internal class RequestTracingConstants
5457
public const string SignalRAssemblyName = "Microsoft.AspNetCore.SignalR";
5558

5659
public const string Delimiter = "+";
60+
61+
public const string AIMimeProfile = "https://azconfig.io/mime-profiles/ai";
62+
public const string AIChatCompletionMimeProfile = "https://azconfig.io/mime-profiles/ai/chat-completion";
5763
}
5864
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using System.Linq;
5+
using System;
6+
using System.Net.Mime;
7+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault;
8+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
9+
using System.Collections.Generic;
10+
11+
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
12+
{
13+
internal static class ContentTypeExtensions
14+
{
15+
public static bool IsAi(this ContentType contentType)
16+
{
17+
return contentType != null &&
18+
contentType.IsJson() &&
19+
!contentType.IsFeatureFlag() &&
20+
!contentType.IsKeyVaultReference() &&
21+
contentType.Parameters.ContainsKey("profile") &&
22+
!string.IsNullOrEmpty(contentType.Parameters["profile"]) &&
23+
contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIMimeProfile);
24+
}
25+
26+
public static bool IsAiChatCompletion(this ContentType contentType)
27+
{
28+
return contentType != null &&
29+
contentType.IsJson() &&
30+
!contentType.IsFeatureFlag() &&
31+
!contentType.IsKeyVaultReference() &&
32+
contentType.Parameters.ContainsKey("profile") &&
33+
!string.IsNullOrEmpty(contentType.Parameters["profile"]) &&
34+
contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIChatCompletionMimeProfile);
35+
}
36+
37+
public static bool IsJson(this ContentType contentType)
38+
{
39+
if (contentType == null)
40+
{
41+
return false;
42+
}
43+
44+
string acceptedMainType = "application";
45+
string acceptedSubType = "json";
46+
47+
ReadOnlySpan<char> mediaTypeSpan = contentType.MediaType.AsSpan();
48+
49+
// Since contentType has been validated using System.Net.Mime.ContentType,
50+
// mediaType will always have exactly 2 parts after splitting on '/'
51+
int slashIndex = mediaTypeSpan.IndexOf('/');
52+
53+
if (mediaTypeSpan.Slice(0, slashIndex).Equals(acceptedMainType.AsSpan(), StringComparison.OrdinalIgnoreCase))
54+
{
55+
ReadOnlySpan<char> subTypeSpan = mediaTypeSpan.Slice(slashIndex + 1);
56+
57+
while (!subTypeSpan.IsEmpty)
58+
{
59+
int plusIndex = subTypeSpan.IndexOf('+');
60+
61+
ReadOnlySpan<char> currentSubType = plusIndex == -1 ? subTypeSpan : subTypeSpan.Slice(0, plusIndex);
62+
63+
if (currentSubType.Equals(acceptedSubType.AsSpan(), StringComparison.OrdinalIgnoreCase))
64+
{
65+
return true;
66+
}
67+
68+
subTypeSpan = plusIndex == -1 ? ReadOnlySpan<char>.Empty : subTypeSpan.Slice(plusIndex + 1);
69+
}
70+
}
71+
72+
return false;
73+
}
74+
75+
public static bool IsFeatureFlag(this ContentType contentType)
76+
{
77+
return contentType.MediaType.Equals(FeatureManagementConstants.ContentType);
78+
}
79+
80+
public static bool IsKeyVaultReference(this ContentType contentType)
81+
{
82+
return contentType.MediaType.Equals(KeyVaultConstants.ContentType);
83+
}
84+
}
85+
}

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/StringExtensions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,39 @@
22
// Licensed under the MIT license.
33
//
44
using System;
5+
using System.Net.Mime;
56

67
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
78
{
89
internal static class StringExtensions
910
{
11+
public static bool TryParseContentType(this string contentTypeString, out ContentType contentType)
12+
{
13+
contentType = null;
14+
15+
if (string.IsNullOrWhiteSpace(contentTypeString))
16+
{
17+
return false;
18+
}
19+
20+
try
21+
{
22+
contentType = new ContentType(contentTypeString.Trim());
23+
24+
return true;
25+
}
26+
catch (FormatException)
27+
{
28+
return false;
29+
}
30+
catch (IndexOutOfRangeException)
31+
{
32+
// Bug in System.Net.Mime.ContentType throws this if contentType is "xyz/"
33+
// https://github.com/dotnet/runtime/issues/39337
34+
return false;
35+
}
36+
}
37+
1038
public static string NormalizeNull(this string s)
1139
{
1240
return s == LabelFilter.Null ? null : s;

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.Diagnostics;
99
using System.Linq;
10+
using System.Net.Mime;
1011
using System.Security.Cryptography;
1112
using System.Text;
1213
using System.Text.Json;
@@ -46,10 +47,20 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura
4647

4748
public bool CanProcess(ConfigurationSetting setting)
4849
{
49-
string contentType = setting?.ContentType?.Split(';')[0].Trim();
50+
if (setting == null ||
51+
string.IsNullOrWhiteSpace(setting.Value) ||
52+
string.IsNullOrWhiteSpace(setting.ContentType))
53+
{
54+
return false;
55+
}
56+
57+
if (setting.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker))
58+
{
59+
return true;
60+
}
5061

51-
return string.Equals(contentType, FeatureManagementConstants.ContentType) ||
52-
setting.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker);
62+
return setting.ContentType.TryParseContentType(out ContentType contentType) &&
63+
contentType.IsFeatureFlag();
5364
}
5465

5566
public bool NeedsRefresh()

0 commit comments

Comments
 (0)