diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 65730d67ff5d..4d2ccde96296 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Web; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -262,14 +263,14 @@ private Activity StartActivity(HttpContext httpContext, out bool hasDiagnosticLi // We expect baggage to be empty by default // Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items) string[] baggage = headers.GetCommaSeparatedValues(HeaderNames.CorrelationContext); - if (baggage.Length > 0) + + // AddBaggage adds items at the beginning of the list, so we need to add them in reverse to keep the same order as the client + // An order could be important if baggage has two items with the same key (that is allowed by the contract) + for (var i = baggage.Length - 1; i >= 0; i--) { - foreach (var item in baggage) + if (NameValueHeaderValue.TryParse(baggage[i], out var baggageItem)) { - if (NameValueHeaderValue.TryParse(item, out var baggageItem)) - { - activity.AddBaggage(baggageItem.Name.ToString(), baggageItem.Value.ToString()); - } + activity.AddBaggage(baggageItem.Name.ToString(), HttpUtility.UrlDecode(baggageItem.Value.ToString())); } } } diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 385424fa6758..419a50052b09 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -345,6 +345,42 @@ public void ActivityParentIdAndBaggeReadFromHeaders() Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value2"); } + [Fact] + public void ActivityBaggagePreservesItemsOrder() + { + var diagnosticListener = new DiagnosticListener("DummySource"); + var hostingApplication = CreateApplication(out var features, diagnosticListener: diagnosticListener); + + diagnosticListener.Subscribe(new CallbackDiagnosticListener(pair => { }), + s => + { + if (s.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn")) + { + return true; + } + return false; + }); + + features.Set(new HttpRequestFeature() + { + Headers = new HeaderDictionary() + { + {"Request-Id", "ParentId1"}, + {"Correlation-Context", "Key1=value1, Key2=value2, Key1=value3"} // duplicated keys allowed by the contract + } + }); + hostingApplication.CreateContext(features); + Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName); + + var expectedBaggage = new [] + { + KeyValuePair.Create("Key1","value1"), + KeyValuePair.Create("Key2","value2"), + KeyValuePair.Create("Key1","value3") + }; + + Assert.Equal(expectedBaggage, Activity.Current.Baggage); + } [Fact] public void ActivityTraceParentAndTraceStateFromHeaders()