From 7be4f3f653871accd8e76a693c27e05a28e71cf3 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Mon, 10 Apr 2023 02:30:49 +0200 Subject: [PATCH 1/5] Added .net 4.8 samples for server and client Also updated .net 3.1 samples to .net 6 as it is EOL Only change to CorrelationId project is a minor nuget bump --- CorrelationId.sln | 50 +++-- .../3.1/MvcSampleTests/MvcSampleTests.csproj | 22 --- .../CorrelationIdMiddlewareWrapper.cs | 96 +++++++++ ...orrelationIdServiceCollectionExtensions.cs | 44 +++++ .../Net48CorrelationId.csproj | 69 +++++++ .../Properties/AssemblyInfo.cs | 35 ++++ .../Net48MvcSample/App_Start/WebApiConfig.cs | 23 +++ .../Controllers/CorrelationIdController.cs | 15 ++ .../CorrelationIdFakeDependencyInjection.cs | 36 ++++ samples/net48/Net48MvcSample/Global.asax | 1 + samples/net48/Net48MvcSample/Global.asax.cs | 13 ++ .../Net48MvcSample/Net48MvcSample.csproj | 187 ++++++++++++++++++ .../Net48MvcSample/Properties/AssemblyInfo.cs | 8 + samples/net48/Net48MvcSample/Web.Debug.config | 30 +++ .../net48/Net48MvcSample/Web.Release.config | 31 +++ samples/net48/Net48MvcSample/Web.config | 9 + .../Controllers/WeatherForecastController.cs | 0 .../DoNothingCorrelationIdProvider.cs | 0 .../{3.1 => net6}/MvcSample/MvcSample.csproj | 7 +- .../MvcSample/NoOpDelegatingHandler.cs | 0 samples/{3.1 => net6}/MvcSample/Program.cs | 0 .../MvcSample/Properties/launchSettings.json | 0 .../ServiceWhichUsesCorrelationContext.cs | 0 samples/{3.1 => net6}/MvcSample/Startup.cs | 0 .../MvcSample/WeatherForecast.cs | 0 .../MvcSample/appsettings.Development.json | 0 .../{3.1 => net6}/MvcSample/appsettings.json | 0 .../net6/MvcSampleTests/MvcSampleTests.csproj | 30 +++ ...ServiceWhichUsesCorrelationContextTests.cs | 0 src/CorrelationId/CorrelationId.csproj | 6 +- .../CorrelationId.Net48.Tests.csproj | 74 +++++++ .../HttpClientBuilderTests.cs | 79 ++++++++ .../HttpClientTest.cs | 37 ++++ .../Net48MvcSampleApiClient.cs | 21 ++ .../Properties/AssemblyInfo.cs | 8 + .../CorrelationId.Tests.csproj | 14 +- .../CorrelationIdMiddlewareTests.cs | 12 +- 37 files changed, 905 insertions(+), 52 deletions(-) delete mode 100644 samples/3.1/MvcSampleTests/MvcSampleTests.csproj create mode 100644 samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs create mode 100644 samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs create mode 100644 samples/net48/Net48CorrelationId/Net48CorrelationId.csproj create mode 100644 samples/net48/Net48CorrelationId/Properties/AssemblyInfo.cs create mode 100644 samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs create mode 100644 samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs create mode 100644 samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs create mode 100644 samples/net48/Net48MvcSample/Global.asax create mode 100644 samples/net48/Net48MvcSample/Global.asax.cs create mode 100644 samples/net48/Net48MvcSample/Net48MvcSample.csproj create mode 100644 samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs create mode 100644 samples/net48/Net48MvcSample/Web.Debug.config create mode 100644 samples/net48/Net48MvcSample/Web.Release.config create mode 100644 samples/net48/Net48MvcSample/Web.config rename samples/{3.1 => net6}/MvcSample/Controllers/WeatherForecastController.cs (100%) rename samples/{3.1 => net6}/MvcSample/DoNothingCorrelationIdProvider.cs (100%) rename samples/{3.1 => net6}/MvcSample/MvcSample.csproj (51%) rename samples/{3.1 => net6}/MvcSample/NoOpDelegatingHandler.cs (100%) rename samples/{3.1 => net6}/MvcSample/Program.cs (100%) rename samples/{3.1 => net6}/MvcSample/Properties/launchSettings.json (100%) rename samples/{3.1 => net6}/MvcSample/ServiceWhichUsesCorrelationContext.cs (100%) rename samples/{3.1 => net6}/MvcSample/Startup.cs (100%) rename samples/{3.1 => net6}/MvcSample/WeatherForecast.cs (100%) rename samples/{3.1 => net6}/MvcSample/appsettings.Development.json (100%) rename samples/{3.1 => net6}/MvcSample/appsettings.json (100%) create mode 100644 samples/net6/MvcSampleTests/MvcSampleTests.csproj rename samples/{3.1 => net6}/MvcSampleTests/ServiceWhichUsesCorrelationContextTests.cs (100%) create mode 100644 test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj create mode 100644 test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs create mode 100644 test/CorrelationId.Net48.Tests/HttpClientTest.cs create mode 100644 test/CorrelationId.Net48.Tests/Net48MvcSampleApiClient.cs create mode 100644 test/CorrelationId.Net48.Tests/Properties/AssemblyInfo.cs diff --git a/CorrelationId.sln b/CorrelationId.sln index abe232f..e76761d 100644 --- a/CorrelationId.sln +++ b/CorrelationId.sln @@ -26,11 +26,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5961B376 azure-pipelines.yml = azure-pipelines.yml EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.1", "3.1", "{E28C5481-A68F-44AF-983F-EA127E70621A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net6", "net6", "{E28C5481-A68F-44AF-983F-EA127E70621A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcSample", "samples\3.1\MvcSample\MvcSample.csproj", "{9393676B-BE08-44C9-B556-7BE31CFA5B86}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net48", "net48", "{0D3CBD88-90B9-4364-9DE4-CA1FCC9CB023}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcSampleTests", "samples\3.1\MvcSampleTests\MvcSampleTests.csproj", "{0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net48MvcSample", "samples\net48\Net48MvcSample\Net48MvcSample.csproj", "{DED5BFD9-29F6-4291-B863-1995C33D686B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CorrelationId.Net48.Tests", "test\CorrelationId.Net48.Tests\CorrelationId.Net48.Tests.csproj", "{A4137EAE-590B-476C-838F-A2D4A45AAE4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net48CorrelationId", "samples\net48\Net48CorrelationId\Net48CorrelationId.csproj", "{29F51512-04FE-40CA-826F-0DDAD9BF5750}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcSample", "samples\net6\MvcSample\MvcSample.csproj", "{52ED4758-2A42-415B-9423-51D84BDE1708}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcSampleTests", "samples\net6\MvcSampleTests\MvcSampleTests.csproj", "{CF0F76FD-9AEF-4616-A93E-23B62692FADA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -46,14 +54,26 @@ Global {41035337-9829-4A6A-8EA9-42CC94734CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {41035337-9829-4A6A-8EA9-42CC94734CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {41035337-9829-4A6A-8EA9-42CC94734CE6}.Release|Any CPU.Build.0 = Release|Any CPU - {9393676B-BE08-44C9-B556-7BE31CFA5B86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9393676B-BE08-44C9-B556-7BE31CFA5B86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9393676B-BE08-44C9-B556-7BE31CFA5B86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9393676B-BE08-44C9-B556-7BE31CFA5B86}.Release|Any CPU.Build.0 = Release|Any CPU - {0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F}.Release|Any CPU.Build.0 = Release|Any CPU + {DED5BFD9-29F6-4291-B863-1995C33D686B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DED5BFD9-29F6-4291-B863-1995C33D686B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DED5BFD9-29F6-4291-B863-1995C33D686B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DED5BFD9-29F6-4291-B863-1995C33D686B}.Release|Any CPU.Build.0 = Release|Any CPU + {A4137EAE-590B-476C-838F-A2D4A45AAE4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4137EAE-590B-476C-838F-A2D4A45AAE4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4137EAE-590B-476C-838F-A2D4A45AAE4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4137EAE-590B-476C-838F-A2D4A45AAE4A}.Release|Any CPU.Build.0 = Release|Any CPU + {29F51512-04FE-40CA-826F-0DDAD9BF5750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29F51512-04FE-40CA-826F-0DDAD9BF5750}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29F51512-04FE-40CA-826F-0DDAD9BF5750}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29F51512-04FE-40CA-826F-0DDAD9BF5750}.Release|Any CPU.Build.0 = Release|Any CPU + {52ED4758-2A42-415B-9423-51D84BDE1708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52ED4758-2A42-415B-9423-51D84BDE1708}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52ED4758-2A42-415B-9423-51D84BDE1708}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52ED4758-2A42-415B-9423-51D84BDE1708}.Release|Any CPU.Build.0 = Release|Any CPU + {CF0F76FD-9AEF-4616-A93E-23B62692FADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF0F76FD-9AEF-4616-A93E-23B62692FADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF0F76FD-9AEF-4616-A93E-23B62692FADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF0F76FD-9AEF-4616-A93E-23B62692FADA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -62,8 +82,12 @@ Global {865886FC-EBBE-49E1-8F49-FECD0408EF6E} = {9A14C73A-D8FC-40C3-81FC-D0687F43FEAF} {41035337-9829-4A6A-8EA9-42CC94734CE6} = {EAF27B74-0B27-4BEE-9F82-DD5812B21B17} {E28C5481-A68F-44AF-983F-EA127E70621A} = {34C0F65A-8BF2-40DA-B0E7-844930EE2A7B} - {9393676B-BE08-44C9-B556-7BE31CFA5B86} = {E28C5481-A68F-44AF-983F-EA127E70621A} - {0E2E2678-4DBB-4560-AF0E-86ECAF8D9B6F} = {E28C5481-A68F-44AF-983F-EA127E70621A} + {0D3CBD88-90B9-4364-9DE4-CA1FCC9CB023} = {34C0F65A-8BF2-40DA-B0E7-844930EE2A7B} + {DED5BFD9-29F6-4291-B863-1995C33D686B} = {0D3CBD88-90B9-4364-9DE4-CA1FCC9CB023} + {A4137EAE-590B-476C-838F-A2D4A45AAE4A} = {EAF27B74-0B27-4BEE-9F82-DD5812B21B17} + {29F51512-04FE-40CA-826F-0DDAD9BF5750} = {0D3CBD88-90B9-4364-9DE4-CA1FCC9CB023} + {52ED4758-2A42-415B-9423-51D84BDE1708} = {E28C5481-A68F-44AF-983F-EA127E70621A} + {CF0F76FD-9AEF-4616-A93E-23B62692FADA} = {E28C5481-A68F-44AF-983F-EA127E70621A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C0A37404-C50B-472E-9491-DDE2A4BDA882} diff --git a/samples/3.1/MvcSampleTests/MvcSampleTests.csproj b/samples/3.1/MvcSampleTests/MvcSampleTests.csproj deleted file mode 100644 index ea41b6a..0000000 --- a/samples/3.1/MvcSampleTests/MvcSampleTests.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - netcoreapp3.1 - - false - - - - - - - - - - - - - - - - diff --git a/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs b/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs new file mode 100644 index 0000000..9699ecf --- /dev/null +++ b/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs @@ -0,0 +1,96 @@ +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CorrelationId; +using CorrelationId.Abstractions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Net48CorrelationId +{ + /// + /// We don't have any middleware in .net framework so we wrap it in a HttpMessageHandler instead + /// + public class CorrelationIdMiddlewareWrapper : DelegatingHandler + { + private readonly CorrelationIdMiddleware _correlationIdMiddleware; + private readonly ICorrelationContextFactory _correlationContextFactory; + private readonly IOptions _correlationIdOptions; + + public CorrelationIdMiddlewareWrapper(CorrelationIdMiddleware correlationIdMiddleware, + ICorrelationContextFactory correlationContextFactory, IOptions correlationIdOptions) + { + _correlationIdMiddleware = correlationIdMiddleware; + _correlationContextFactory = correlationContextFactory; + _correlationIdOptions = correlationIdOptions; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var httpContext = GetHttpContext(); + await _correlationIdMiddleware.Invoke(httpContext, _correlationContextFactory); + + var correlationIds = + httpContext.Request.Headers[_correlationIdOptions.Value.RequestHeader]; + + foreach (var correlationId in correlationIds) + { + request.Headers.Add(_correlationIdOptions.Value.RequestHeader, correlationId); + } + + var response = await base.SendAsync(request, cancellationToken); + + if (!_correlationIdOptions.Value.IncludeInResponse) + { + return response; + } + + response.Headers.TryGetValues(_correlationIdOptions.Value.ResponseHeader, out var existingCorrelationIdResponseHeaders); + var existingCorrelationIdResponseHeadersList = existingCorrelationIdResponseHeaders?.ToList(); + + foreach (var correlationId in correlationIds) + { + // CorrelationId might be added both on server and client part + if (existingCorrelationIdResponseHeadersList != null && + existingCorrelationIdResponseHeadersList.Any(x => x == correlationId)) + { + continue; + } + + response.Headers.Add(_correlationIdOptions.Value.ResponseHeader, correlationId); + } + + return response; + } + + private HttpContext GetHttpContext() + { + var httpContext = new DefaultHttpContext(); + + if (System.Web.HttpContext.Current == null) + { + return httpContext; + } + + var correlationIds = + System.Web.HttpContext.Current.Request.Headers + .GetValues(_correlationIdOptions.Value.RequestHeader); + + if (correlationIds == null) + { + return httpContext; + } + + foreach (var correlationId in correlationIds) + { + httpContext.Request.Headers + .Add(_correlationIdOptions.Value.RequestHeader, correlationId); + } + + return httpContext; + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs b/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs new file mode 100644 index 0000000..9161af1 --- /dev/null +++ b/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using CorrelationId; +using CorrelationId.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Net48CorrelationId +{ + public static class CorrelationIdServiceCollectionExtensions + { + public static void UseCorrelationIdMiddleware(this IServiceCollection serviceCollection, IHttpClientBuilder httpClientBuilder) + { + serviceCollection.UseCorrelationIdMiddleware(); + httpClientBuilder.AddHttpMessageHandler(); + } + + public static void UseCorrelationIdMiddleware(this IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(x => + { + return new CorrelationIdMiddleware( + next => + { + var correlationContextAccessor = x.GetService(); + // copied from CorrelationId.HttpClient.CorrelationIdHandler + if (!string.IsNullOrEmpty(correlationContextAccessor?.CorrelationContext?.CorrelationId) && + !next.Request.Headers.ContainsKey(correlationContextAccessor.CorrelationContext.Header)) + { + next.Request.Headers.Add(correlationContextAccessor.CorrelationContext.Header, + correlationContextAccessor.CorrelationContext.CorrelationId); + } + + return Task.CompletedTask; + }, + x.GetService>(), + x.GetService>(), + x.GetService()); + }); + + serviceCollection.AddTransient(); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48CorrelationId/Net48CorrelationId.csproj b/samples/net48/Net48CorrelationId/Net48CorrelationId.csproj new file mode 100644 index 0000000..3b2cf3b --- /dev/null +++ b/samples/net48/Net48CorrelationId/Net48CorrelationId.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {29F51512-04FE-40CA-826F-0DDAD9BF5750} + Library + Properties + Net48CorrelationId + Net48CorrelationId + v4.8 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {865886fc-ebbe-49e1-8f49-fecd0408ef6e} + CorrelationId + + + + + + + + + diff --git a/samples/net48/Net48CorrelationId/Properties/AssemblyInfo.cs b/samples/net48/Net48CorrelationId/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d8e5af4 --- /dev/null +++ b/samples/net48/Net48CorrelationId/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Net48CorrelationId")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Net48CorrelationId")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29F51512-04FE-40CA-826F-0DDAD9BF5750")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs b/samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..788325c --- /dev/null +++ b/samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs @@ -0,0 +1,23 @@ +using System.Web.Http; + +namespace Net48MvcSample +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Web API configuration and services + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + + config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = + new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); + } + }} \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs new file mode 100644 index 0000000..c3d7978 --- /dev/null +++ b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs @@ -0,0 +1,15 @@ +using System.Web; +using System.Web.Http; +using CorrelationId; + +namespace Net48MvcSample.Controllers +{ + public class CorrelationIdController : ApiController + { + public string Get() + { + var correlationId = HttpContext.Current.Request.Headers["X-Correlation-Id"]; + return correlationId; + } + } +} diff --git a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs new file mode 100644 index 0000000..7ce116f --- /dev/null +++ b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs @@ -0,0 +1,36 @@ +using System.Web.Http; +using CorrelationId; +using CorrelationId.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Net48CorrelationId; + +namespace Net48MvcSample +{ + public static class CorrelationIdFakeDependencyInjection + { + public static void Register(HttpConfiguration config) + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddDefaultCorrelationId(options => + { + options.AddToLoggingScope = true; + options.IgnoreRequestHeader = false; + options.IncludeInResponse = true; + options.RequestHeader = "X-Correlation-Id"; + options.ResponseHeader = "X-Correlation-Id"; + options.UpdateTraceIdentifier = false; + }); + + serviceCollection.UseCorrelationIdMiddleware(); + + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + serviceCollection.AddSingleton(_ => loggerFactory.CreateLogger()); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var correlationIdMiddlewareWrapper = serviceProvider.GetService(); + + config.MessageHandlers.Add(correlationIdMiddlewareWrapper); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Global.asax b/samples/net48/Net48MvcSample/Global.asax new file mode 100644 index 0000000..5d98dde --- /dev/null +++ b/samples/net48/Net48MvcSample/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Net48MvcSample.MvcApplication" Language="C#" %> \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Global.asax.cs b/samples/net48/Net48MvcSample/Global.asax.cs new file mode 100644 index 0000000..9d08d0d --- /dev/null +++ b/samples/net48/Net48MvcSample/Global.asax.cs @@ -0,0 +1,13 @@ +using System.Web.Http; + +namespace Net48MvcSample +{ + public class MvcApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + GlobalConfiguration.Configure(CorrelationIdFakeDependencyInjection.Register); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Net48MvcSample.csproj b/samples/net48/Net48MvcSample/Net48MvcSample.csproj new file mode 100644 index 0000000..1574fd2 --- /dev/null +++ b/samples/net48/Net48MvcSample/Net48MvcSample.csproj @@ -0,0 +1,187 @@ + + + + + + Debug + AnyCPU + 2.0 + {DED5BFD9-29F6-4291-B863-1995C33D686B} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Net48MvcSample + Net48MvcSample + v4.8 + false + true + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + True + ../../..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ../../..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll + + + True + ../../..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + True + ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + True + ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + True + ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + True + ../../..\packages\WebGrease.1.6.0\lib\WebGrease.dll + + + True + ../../..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll + + + + + ../../..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + + + + + Global.asax + + + + + + + + Web.config + + + Web.config + + + + + + + + {865886fc-ebbe-49e1-8f49-fecd0408ef6e} + CorrelationId + + + {29f51512-04fe-40ca-826f-0ddad9bf5750} + Net48CorrelationId + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + + + True + True + 5000 + / + http://localhost:31488/ + False + False + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs b/samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e05c321 --- /dev/null +++ b/samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Net48MvcSample")] +[assembly: AssemblyProduct("Net48MvcSample")] +[assembly: Guid("DED5BFD9-29F6-4291-B863-1995C33D686B")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Web.Debug.config b/samples/net48/Net48MvcSample/Web.Debug.config new file mode 100644 index 0000000..d7712aa --- /dev/null +++ b/samples/net48/Net48MvcSample/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/samples/net48/Net48MvcSample/Web.Release.config b/samples/net48/Net48MvcSample/Web.Release.config new file mode 100644 index 0000000..28a4d5f --- /dev/null +++ b/samples/net48/Net48MvcSample/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/samples/net48/Net48MvcSample/Web.config b/samples/net48/Net48MvcSample/Web.config new file mode 100644 index 0000000..28c2777 --- /dev/null +++ b/samples/net48/Net48MvcSample/Web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/3.1/MvcSample/Controllers/WeatherForecastController.cs b/samples/net6/MvcSample/Controllers/WeatherForecastController.cs similarity index 100% rename from samples/3.1/MvcSample/Controllers/WeatherForecastController.cs rename to samples/net6/MvcSample/Controllers/WeatherForecastController.cs diff --git a/samples/3.1/MvcSample/DoNothingCorrelationIdProvider.cs b/samples/net6/MvcSample/DoNothingCorrelationIdProvider.cs similarity index 100% rename from samples/3.1/MvcSample/DoNothingCorrelationIdProvider.cs rename to samples/net6/MvcSample/DoNothingCorrelationIdProvider.cs diff --git a/samples/3.1/MvcSample/MvcSample.csproj b/samples/net6/MvcSample/MvcSample.csproj similarity index 51% rename from samples/3.1/MvcSample/MvcSample.csproj rename to samples/net6/MvcSample/MvcSample.csproj index 02bea63..a1a6b5d 100644 --- a/samples/3.1/MvcSample/MvcSample.csproj +++ b/samples/net6/MvcSample/MvcSample.csproj @@ -1,11 +1,16 @@ - netcoreapp3.1 + net6.0 + default + + + + diff --git a/samples/3.1/MvcSample/NoOpDelegatingHandler.cs b/samples/net6/MvcSample/NoOpDelegatingHandler.cs similarity index 100% rename from samples/3.1/MvcSample/NoOpDelegatingHandler.cs rename to samples/net6/MvcSample/NoOpDelegatingHandler.cs diff --git a/samples/3.1/MvcSample/Program.cs b/samples/net6/MvcSample/Program.cs similarity index 100% rename from samples/3.1/MvcSample/Program.cs rename to samples/net6/MvcSample/Program.cs diff --git a/samples/3.1/MvcSample/Properties/launchSettings.json b/samples/net6/MvcSample/Properties/launchSettings.json similarity index 100% rename from samples/3.1/MvcSample/Properties/launchSettings.json rename to samples/net6/MvcSample/Properties/launchSettings.json diff --git a/samples/3.1/MvcSample/ServiceWhichUsesCorrelationContext.cs b/samples/net6/MvcSample/ServiceWhichUsesCorrelationContext.cs similarity index 100% rename from samples/3.1/MvcSample/ServiceWhichUsesCorrelationContext.cs rename to samples/net6/MvcSample/ServiceWhichUsesCorrelationContext.cs diff --git a/samples/3.1/MvcSample/Startup.cs b/samples/net6/MvcSample/Startup.cs similarity index 100% rename from samples/3.1/MvcSample/Startup.cs rename to samples/net6/MvcSample/Startup.cs diff --git a/samples/3.1/MvcSample/WeatherForecast.cs b/samples/net6/MvcSample/WeatherForecast.cs similarity index 100% rename from samples/3.1/MvcSample/WeatherForecast.cs rename to samples/net6/MvcSample/WeatherForecast.cs diff --git a/samples/3.1/MvcSample/appsettings.Development.json b/samples/net6/MvcSample/appsettings.Development.json similarity index 100% rename from samples/3.1/MvcSample/appsettings.Development.json rename to samples/net6/MvcSample/appsettings.Development.json diff --git a/samples/3.1/MvcSample/appsettings.json b/samples/net6/MvcSample/appsettings.json similarity index 100% rename from samples/3.1/MvcSample/appsettings.json rename to samples/net6/MvcSample/appsettings.json diff --git a/samples/net6/MvcSampleTests/MvcSampleTests.csproj b/samples/net6/MvcSampleTests/MvcSampleTests.csproj new file mode 100644 index 0000000..54a09c1 --- /dev/null +++ b/samples/net6/MvcSampleTests/MvcSampleTests.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + + false + + default + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/samples/3.1/MvcSampleTests/ServiceWhichUsesCorrelationContextTests.cs b/samples/net6/MvcSampleTests/ServiceWhichUsesCorrelationContextTests.cs similarity index 100% rename from samples/3.1/MvcSampleTests/ServiceWhichUsesCorrelationContextTests.cs rename to samples/net6/MvcSampleTests/ServiceWhichUsesCorrelationContextTests.cs diff --git a/src/CorrelationId/CorrelationId.csproj b/src/CorrelationId/CorrelationId.csproj index 83bcedf..b697116 100644 --- a/src/CorrelationId/CorrelationId.csproj +++ b/src/CorrelationId/CorrelationId.csproj @@ -21,10 +21,8 @@ - - - - + + \ No newline at end of file diff --git a/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj new file mode 100644 index 0000000..9c28da5 --- /dev/null +++ b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {A4137EAE-590B-476C-838F-A2D4A45AAE4A} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + CorrelationId.Net48.Tests + CorrelationId.Net48.Tests + v4.8 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + {29f51512-04fe-40ca-826f-0ddad9bf5750} + Net48CorrelationId + + + {865886fc-ebbe-49e1-8f49-fecd0408ef6e} + CorrelationId + + + + + + + + diff --git a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs new file mode 100644 index 0000000..60e3795 --- /dev/null +++ b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CorrelationId.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Net48CorrelationId; +using Xunit; +using Xunit.Abstractions; + +namespace CorrelationId.Net48.Tests +{ + public class HttpClientBuilderTests + { + private readonly ITestOutputHelper _testOutputHelper; + private readonly Net48MvcSampleApiClient _net48MvcSampleApiClient; + + public HttpClientBuilderTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + var serviceCollection = new ServiceCollection(); + serviceCollection.AddDefaultCorrelationId(options => + { + options.AddToLoggingScope = true; + options.IgnoreRequestHeader = false; + options.IncludeInResponse = true; + options.RequestHeader = "X-Correlation-Id"; + options.ResponseHeader = "X-Correlation-Id"; + options.UpdateTraceIdentifier = false; + }); + + var httpClientBuilder = serviceCollection.AddHttpClient(); + + serviceCollection.UseCorrelationIdMiddleware(httpClientBuilder); + var serviceProvider = serviceCollection.BuildServiceProvider(); + _net48MvcSampleApiClient = serviceProvider.GetService(); + } + + [Fact] + public async Task CallTo_Net48Service_ShouldSetCorrelationId() + { + var response = await _net48MvcSampleApiClient.GetAsync(); + var responsePayload = response.Content.ReadAsStringAsync().Result; + + Assert.True(response.IsSuccessStatusCode, responsePayload); + + _testOutputHelper.WriteLine(string.Join(", ", responsePayload)); + + Assert.True(Guid.TryParse(responsePayload.Replace("\"", string.Empty), out _)); + + Assert.Single(response.Headers.GetValues("X-Correlation-Id")); + Assert.Equal(response.Headers.GetValues("X-Correlation-Id").Single(), responsePayload.Replace("\"", string.Empty)); + } + + [Fact] + public async Task CallTo_Net48Service_MultipleCalls_ShouldSetDifferentCorrelationId() + { + var responseTask = _net48MvcSampleApiClient.GetAsync(); + var response2Task = _net48MvcSampleApiClient.GetAsync(); + Task.WaitAll(responseTask, response2Task); + + var response = await responseTask; + var responsePayload = response.Content.ReadAsStringAsync().Result; + Assert.True(response.IsSuccessStatusCode, responsePayload); + + var response2 = await response2Task; + var response2Payload = response2.Content.ReadAsStringAsync().Result; + + Assert.True(response2.IsSuccessStatusCode, response2Payload); + + Assert.NotNull(response.Content); + Assert.True(Guid.TryParse(responsePayload.Replace("\"", string.Empty), out _)); + + Assert.NotNull(response2.Content); + Assert.True(Guid.TryParse(response2Payload.Replace("\"", string.Empty), out _)); + + Assert.NotEqual(response, response2); + } + } +} \ No newline at end of file diff --git a/test/CorrelationId.Net48.Tests/HttpClientTest.cs b/test/CorrelationId.Net48.Tests/HttpClientTest.cs new file mode 100644 index 0000000..8ba5f94 --- /dev/null +++ b/test/CorrelationId.Net48.Tests/HttpClientTest.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace CorrelationId.Net48.Tests +{ + public class HttpClientTest + { + private readonly ITestOutputHelper _testOutputHelper; + + public HttpClientTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task CallApi_ShouldReturnCorrelationId() + { + var client = new System.Net.Http.HttpClient(); + client.BaseAddress = new Uri("http://localhost:31488"); + + var response = await client.GetAsync("/api/CorrelationId"); + var responsePayload = response.Content.ReadAsStringAsync().Result; + + Assert.True(response.IsSuccessStatusCode, responsePayload); + Assert.Equal("null", responsePayload); + + Assert.Single(response.Headers.GetValues("X-Correlation-Id")); + Assert.True(Guid.TryParse(response.Headers.GetValues("X-Correlation-Id").Single(), out _)); + + _testOutputHelper.WriteLine("CorrelationId returned in header: " + + response.Headers.GetValues("X-Correlation-Id").Single()); + } + } +} \ No newline at end of file diff --git a/test/CorrelationId.Net48.Tests/Net48MvcSampleApiClient.cs b/test/CorrelationId.Net48.Tests/Net48MvcSampleApiClient.cs new file mode 100644 index 0000000..913c7c1 --- /dev/null +++ b/test/CorrelationId.Net48.Tests/Net48MvcSampleApiClient.cs @@ -0,0 +1,21 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace CorrelationId.Net48.Tests +{ + public class Net48MvcSampleApiClient + { + private System.Net.Http.HttpClient Client { get; } + public Net48MvcSampleApiClient(System.Net.Http.HttpClient client) + { + Client = client; + Client.BaseAddress = new Uri("http://localhost:31488"); + } + + public async Task GetAsync() + { + return await Client.GetAsync("/api/correlationId"); + } + } +} \ No newline at end of file diff --git a/test/CorrelationId.Net48.Tests/Properties/AssemblyInfo.cs b/test/CorrelationId.Net48.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8988d9c --- /dev/null +++ b/test/CorrelationId.Net48.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("CorrelationId.Net48.Tests")] +[assembly: AssemblyProduct("CorrelationId.Net48.Tests")] +[assembly: Guid("A4137EAE-590B-476C-838F-A2D4A45AAE4A")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/test/CorrelationId.Tests/CorrelationId.Tests.csproj b/test/CorrelationId.Tests/CorrelationId.Tests.csproj index 589d947..e868a79 100644 --- a/test/CorrelationId.Tests/CorrelationId.Tests.csproj +++ b/test/CorrelationId.Tests/CorrelationId.Tests.csproj @@ -1,18 +1,20 @@  - netcoreapp2.1 - 8 + net6.0 + default + Library - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs b/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs index 11180af..1433dec 100644 --- a/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs +++ b/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs @@ -448,7 +448,7 @@ public async Task CorrelationContextIncludesHeaderValue_WhichMatchesTheOriginalO { app.UseCorrelationId(); - app.Use(async (ctx, next) => + app.Use(async (HttpContext ctx, Func _) => { var accessor = ctx.RequestServices.GetService(); ctx.Response.StatusCode = StatusCodes.Status200OK; @@ -463,7 +463,7 @@ public async Task CorrelationContextIncludesHeaderValue_WhichMatchesTheOriginalO var body = await response.Content.ReadAsStringAsync(); - Assert.Equal(body, customHeader); + Assert.Equal(customHeader, body); } [Fact] @@ -485,7 +485,7 @@ public async Task TraceIdentifier_IsNotUpdated_WhenUpdateTraceIdentifierIsFalse( app.UseCorrelationId(); - app.Use((ctx, next) => + app.Use((HttpContext ctx, Func _) => { traceIdentifier = ctx.TraceIdentifier; return Task.CompletedTask; @@ -520,7 +520,7 @@ public async Task TraceIdentifier_IsNotUpdated_WhenUpdateTraceIdentifierIsTrue_B app.UseCorrelationId(); - app.Use((ctx, next) => + app.Use((HttpContext ctx, Func _) => { traceIdentifier = ctx.TraceIdentifier; return Task.CompletedTask; @@ -555,7 +555,7 @@ public async Task TraceIdentifier_IsNotUpdated_WhenUpdateTraceIdentifierIsTrue_A app.UseCorrelationId(); - app.Use((ctx, next) => + app.Use((HttpContext ctx, Func _) => { traceIdentifier = ctx.TraceIdentifier; return Task.CompletedTask; @@ -583,7 +583,7 @@ public async Task TraceIdentifier_IsUpdated_WhenUpdateTraceIdentifierIsTrue() { app.UseCorrelationId(); - app.Use((ctx, next) => + app.Use((HttpContext ctx, Func _) => { traceIdentifier = ctx.TraceIdentifier; return Task.CompletedTask; From eec2bedf8e4469b234244f15922dd9d2ec65a673 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Mon, 10 Apr 2023 03:06:26 +0200 Subject: [PATCH 2/5] Add debug printout --- .../CorrelationIdFakeDependencyInjection.cs | 6 +++++- .../CorrelationId.Net48.Tests.csproj | 1 + .../HttpClientBuilderTests.cs | 13 +++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs index 7ce116f..31404e0 100644 --- a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs +++ b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs @@ -24,7 +24,11 @@ public static void Register(HttpConfiguration config) serviceCollection.UseCorrelationIdMiddleware(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Debug); + }); serviceCollection.AddSingleton(_ => loggerFactory.CreateLogger()); var serviceProvider = serviceCollection.BuildServiceProvider(); diff --git a/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj index 9c28da5..cd96fb3 100644 --- a/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj +++ b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj @@ -61,6 +61,7 @@ + diff --git a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs index 60e3795..e8d8454 100644 --- a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs +++ b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using CorrelationId.DependencyInjection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Net48CorrelationId; using Xunit; using Xunit.Abstractions; @@ -11,12 +12,10 @@ namespace CorrelationId.Net48.Tests { public class HttpClientBuilderTests { - private readonly ITestOutputHelper _testOutputHelper; private readonly Net48MvcSampleApiClient _net48MvcSampleApiClient; public HttpClientBuilderTests(ITestOutputHelper testOutputHelper) { - _testOutputHelper = testOutputHelper; var serviceCollection = new ServiceCollection(); serviceCollection.AddDefaultCorrelationId(options => { @@ -31,6 +30,14 @@ public HttpClientBuilderTests(ITestOutputHelper testOutputHelper) var httpClientBuilder = serviceCollection.AddHttpClient(); serviceCollection.UseCorrelationIdMiddleware(httpClientBuilder); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddXUnit(testOutputHelper); + builder.SetMinimumLevel(LogLevel.Debug); + }); + serviceCollection.AddSingleton(_ => loggerFactory.CreateLogger()); + var serviceProvider = serviceCollection.BuildServiceProvider(); _net48MvcSampleApiClient = serviceProvider.GetService(); } @@ -43,8 +50,6 @@ public async Task CallTo_Net48Service_ShouldSetCorrelationId() Assert.True(response.IsSuccessStatusCode, responsePayload); - _testOutputHelper.WriteLine(string.Join(", ", responsePayload)); - Assert.True(Guid.TryParse(responsePayload.Replace("\"", string.Empty), out _)); Assert.Single(response.Headers.GetValues("X-Correlation-Id")); From a7d69bcd0e885d17abab3faf80ef621c345128ac Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Wed, 3 May 2023 12:43:45 +0200 Subject: [PATCH 3/5] Fix nugget references --- .../Controllers/CorrelationIdController.cs | 1 - .../CorrelationIdFakeDependencyInjection.cs | 1 + .../Net48MvcSample/Net48MvcSample.csproj | 59 +------------------ samples/net48/Net48MvcSample/Web.config | 46 ++++++++++++++- .../CorrelationId.Tests.csproj | 2 +- 5 files changed, 48 insertions(+), 61 deletions(-) diff --git a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs index c3d7978..996049b 100644 --- a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs +++ b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs @@ -1,6 +1,5 @@ using System.Web; using System.Web.Http; -using CorrelationId; namespace Net48MvcSample.Controllers { diff --git a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs index 31404e0..7930dc3 100644 --- a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs +++ b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs @@ -16,6 +16,7 @@ public static void Register(HttpConfiguration config) { options.AddToLoggingScope = true; options.IgnoreRequestHeader = false; + options.EnforceHeader = false; options.IncludeInResponse = true; options.RequestHeader = "X-Correlation-Id"; options.ResponseHeader = "X-Correlation-Id"; diff --git a/samples/net48/Net48MvcSample/Net48MvcSample.csproj b/samples/net48/Net48MvcSample/Net48MvcSample.csproj index 1574fd2..a65286d 100644 --- a/samples/net48/Net48MvcSample/Net48MvcSample.csproj +++ b/samples/net48/Net48MvcSample/Net48MvcSample.csproj @@ -1,6 +1,5 @@  - Debug @@ -65,50 +64,8 @@ - - - - - - True - ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - True - ../../..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ../../..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll - - - True - ../../..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - True - ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - True - ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - True - ../../..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - True - ../../..\packages\WebGrease.1.6.0\lib\WebGrease.dll - - - True - ../../..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll - - - - - ../../..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - + + @@ -172,16 +129,4 @@ - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Web.config b/samples/net48/Net48MvcSample/Web.config index 28c2777..d18ef8c 100644 --- a/samples/net48/Net48MvcSample/Web.config +++ b/samples/net48/Net48MvcSample/Web.config @@ -3,7 +3,49 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/CorrelationId.Tests/CorrelationId.Tests.csproj b/test/CorrelationId.Tests/CorrelationId.Tests.csproj index e868a79..fb3a4da 100644 --- a/test/CorrelationId.Tests/CorrelationId.Tests.csproj +++ b/test/CorrelationId.Tests/CorrelationId.Tests.csproj @@ -7,7 +7,7 @@ - + From ebdbc940eb232e3d144fd7b46622c62acf8ac731 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Wed, 3 May 2023 15:42:59 +0200 Subject: [PATCH 4/5] Make the samples behave the same way --- .../Controllers/CorrelationIdController.cs | 14 ++++-- .../Net48MvcSample/Net48MvcSample.csproj | 5 +-- samples/net48/Net48MvcSample/Web.config | 7 +-- .../Controllers/CorrelationIdController.cs | 16 +++++++ .../Controllers/WeatherForecastController.cs | 45 ------------------- .../MvcSample/Properties/launchSettings.json | 9 ++-- samples/net6/MvcSample/Startup.cs | 23 +++------- .../HttpClientBuilderTests.cs | 6 +-- .../HttpClientTest.cs | 6 +-- 9 files changed, 46 insertions(+), 85 deletions(-) create mode 100644 samples/net6/MvcSample/Controllers/CorrelationIdController.cs delete mode 100644 samples/net6/MvcSample/Controllers/WeatherForecastController.cs diff --git a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs index 996049b..c7a0f3c 100644 --- a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs +++ b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs @@ -1,14 +1,22 @@ -using System.Web; +using System.Net; +using System.Web; using System.Web.Http; +using System.Web.Http.Results; namespace Net48MvcSample.Controllers { public class CorrelationIdController : ApiController { - public string Get() + public IHttpActionResult Get() { var correlationId = HttpContext.Current.Request.Headers["X-Correlation-Id"]; - return correlationId; + + if (correlationId == null) + { + return new StatusCodeResult(HttpStatusCode.NoContent, Request); + } + + return Ok(correlationId); } } } diff --git a/samples/net48/Net48MvcSample/Net48MvcSample.csproj b/samples/net48/Net48MvcSample/Net48MvcSample.csproj index a65286d..3ca5919 100644 --- a/samples/net48/Net48MvcSample/Net48MvcSample.csproj +++ b/samples/net48/Net48MvcSample/Net48MvcSample.csproj @@ -86,9 +86,6 @@ Web.config - - - {865886fc-ebbe-49e1-8f49-fecd0408ef6e} @@ -102,6 +99,8 @@ + + 10.0 diff --git a/samples/net48/Net48MvcSample/Web.config b/samples/net48/Net48MvcSample/Web.config index d18ef8c..647a8a1 100644 --- a/samples/net48/Net48MvcSample/Web.config +++ b/samples/net48/Net48MvcSample/Web.config @@ -8,7 +8,6 @@ - @@ -32,16 +31,12 @@ - + - - - - diff --git a/samples/net6/MvcSample/Controllers/CorrelationIdController.cs b/samples/net6/MvcSample/Controllers/CorrelationIdController.cs new file mode 100644 index 0000000..beb0668 --- /dev/null +++ b/samples/net6/MvcSample/Controllers/CorrelationIdController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace MvcSample.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class CorrelationIdController : ControllerBase + { + [HttpGet] + public string Get() + { + var correlationId = Request.Headers["X-Correlation-Id"]; + return correlationId; + } + } +} diff --git a/samples/net6/MvcSample/Controllers/WeatherForecastController.cs b/samples/net6/MvcSample/Controllers/WeatherForecastController.cs deleted file mode 100644 index 3956ecd..0000000 --- a/samples/net6/MvcSample/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; - -namespace MvcSample.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; - - public WeatherForecastController(ILogger logger, IHttpClientFactory httpClientFactory) - { - _logger = logger; - _httpClientFactory = httpClientFactory; - } - - [HttpGet] - public IEnumerable Get() - { - var client = _httpClientFactory.CreateClient("MyClient"); // this client will attach the correlation ID header - - client.GetAsync("https://www.example.com"); - - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/samples/net6/MvcSample/Properties/launchSettings.json b/samples/net6/MvcSample/Properties/launchSettings.json index 24190e2..31b59bb 100644 --- a/samples/net6/MvcSample/Properties/launchSettings.json +++ b/samples/net6/MvcSample/Properties/launchSettings.json @@ -4,15 +4,14 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:53567", - "sslPort": 44328 + "applicationUrl": "http://localhost:53567" } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": false, - "launchUrl": "weatherforecast", + "launchUrl": "api/correlationid", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -20,8 +19,8 @@ "MvcSample": { "commandName": "Project", "launchBrowser": false, - "launchUrl": "weatherforecast", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "launchUrl": "api/correlationid", + "applicationUrl": "http://localhost:53567", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/samples/net6/MvcSample/Startup.cs b/samples/net6/MvcSample/Startup.cs index d176388..464e2e6 100644 --- a/samples/net6/MvcSample/Startup.cs +++ b/samples/net6/MvcSample/Startup.cs @@ -26,26 +26,20 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpClient("MyClient") .AddCorrelationIdForwarding() // add the handler to attach the correlation ID to outgoing requests for this named client .AddHttpMessageHandler(); - - // Example of adding default correlation ID (using the GUID generator) services - // As shown here, options can be configured via the configure delegate overload + services.AddDefaultCorrelationId(options => - { - options.CorrelationIdGenerator = () => "Foo"; + { options.AddToLoggingScope = true; - options.EnforceHeader = true; + options.EnforceHeader = false; options.IgnoreRequestHeader = false; options.IncludeInResponse = true; - options.RequestHeader = "My-Custom-Correlation-Id"; + options.RequestHeader = "X-Correlation-Id"; options.ResponseHeader = "X-Correlation-Id"; options.UpdateTraceIdentifier = false; }); - // Example of registering a custom correlation ID provider - //services.AddCorrelationId().WithCustomProvider(); - services.AddControllers(); - } + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -63,10 +57,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } -} +} \ No newline at end of file diff --git a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs index e8d8454..fbc1297 100644 --- a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs +++ b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs @@ -46,7 +46,7 @@ public HttpClientBuilderTests(ITestOutputHelper testOutputHelper) public async Task CallTo_Net48Service_ShouldSetCorrelationId() { var response = await _net48MvcSampleApiClient.GetAsync(); - var responsePayload = response.Content.ReadAsStringAsync().Result; + var responsePayload = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, responsePayload); @@ -64,11 +64,11 @@ public async Task CallTo_Net48Service_MultipleCalls_ShouldSetDifferentCorrelatio Task.WaitAll(responseTask, response2Task); var response = await responseTask; - var responsePayload = response.Content.ReadAsStringAsync().Result; + var responsePayload = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, responsePayload); var response2 = await response2Task; - var response2Payload = response2.Content.ReadAsStringAsync().Result; + var response2Payload = await response2.Content.ReadAsStringAsync(); Assert.True(response2.IsSuccessStatusCode, response2Payload); diff --git a/test/CorrelationId.Net48.Tests/HttpClientTest.cs b/test/CorrelationId.Net48.Tests/HttpClientTest.cs index 8ba5f94..596e4f5 100644 --- a/test/CorrelationId.Net48.Tests/HttpClientTest.cs +++ b/test/CorrelationId.Net48.Tests/HttpClientTest.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -22,10 +23,7 @@ public async Task CallApi_ShouldReturnCorrelationId() client.BaseAddress = new Uri("http://localhost:31488"); var response = await client.GetAsync("/api/CorrelationId"); - var responsePayload = response.Content.ReadAsStringAsync().Result; - - Assert.True(response.IsSuccessStatusCode, responsePayload); - Assert.Equal("null", responsePayload); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.Single(response.Headers.GetValues("X-Correlation-Id")); Assert.True(Guid.TryParse(response.Headers.GetValues("X-Correlation-Id").Single(), out _)); From 5a5ebc471baf7c3b496756b73dc1da57fb5105d5 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Sat, 6 May 2023 02:22:21 +0200 Subject: [PATCH 5/5] Fixed CorrelationIdMiddlewareWrapper for .net48 Request header is now set if a new is autogenerated Fixed the samples with nested HttpClients --- CorrelationId.sln | 2 +- .../CorrelationIdMiddlewareWrapper.cs | 84 ++++++++----- ...orrelationIdServiceCollectionExtensions.cs | 34 +---- .../Controllers/CorrelationIdController.cs | 22 ---- .../CorrelationIdFakeDependencyInjection.cs | 41 ------ samples/net48/Net48MvcSample/Global.asax | 1 - .../App_Start/WebApiConfig.cs | 2 +- .../Controllers/CorrelationIdController.cs | 51 ++++++++ .../Net48WebApiSample/DependencyInjection.cs | 84 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 12 ++ samples/net48/Net48WebApiSample/Global.asax | 1 + .../Global.asax.cs | 4 +- .../Net48WebApiSample.csproj} | 12 +- .../NoOpDelegatingHandler.cs | 32 +++++ .../Properties/AssemblyInfo.cs | 4 +- .../Web.Debug.config | 0 .../Web.Release.config | 0 .../Web.config | 0 .../Controllers/CorrelationIdController.cs | 41 +++++- src/CorrelationId/CorrelationIdMiddleware.cs | 5 + .../HttpClient/CorrelationIdHandler.cs | 31 ++++- .../CorrelationId.Net48.Tests.csproj | 1 + .../HttpClientBuilderTests.cs | 17 ++- .../HttpClientTest.cs | 2 +- .../CorrelationIdMiddlewareTests.cs | 119 +++++++++++++++++- 25 files changed, 442 insertions(+), 160 deletions(-) delete mode 100644 samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs delete mode 100644 samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs delete mode 100644 samples/net48/Net48MvcSample/Global.asax rename samples/net48/{Net48MvcSample => Net48WebApiSample}/App_Start/WebApiConfig.cs (96%) create mode 100644 samples/net48/Net48WebApiSample/Controllers/CorrelationIdController.cs create mode 100644 samples/net48/Net48WebApiSample/DependencyInjection.cs create mode 100644 samples/net48/Net48WebApiSample/Extensions/ServiceCollectionExtensions.cs create mode 100644 samples/net48/Net48WebApiSample/Global.asax rename samples/net48/{Net48MvcSample => Net48WebApiSample}/Global.asax.cs (66%) rename samples/net48/{Net48MvcSample/Net48MvcSample.csproj => Net48WebApiSample/Net48WebApiSample.csproj} (90%) create mode 100644 samples/net48/Net48WebApiSample/NoOpDelegatingHandler.cs rename samples/net48/{Net48MvcSample => Net48WebApiSample}/Properties/AssemblyInfo.cs (68%) rename samples/net48/{Net48MvcSample => Net48WebApiSample}/Web.Debug.config (100%) rename samples/net48/{Net48MvcSample => Net48WebApiSample}/Web.Release.config (100%) rename samples/net48/{Net48MvcSample => Net48WebApiSample}/Web.config (100%) diff --git a/CorrelationId.sln b/CorrelationId.sln index e76761d..096bd94 100644 --- a/CorrelationId.sln +++ b/CorrelationId.sln @@ -30,7 +30,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net6", "net6", "{E28C5481-A EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net48", "net48", "{0D3CBD88-90B9-4364-9DE4-CA1FCC9CB023}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net48MvcSample", "samples\net48\Net48MvcSample\Net48MvcSample.csproj", "{DED5BFD9-29F6-4291-B863-1995C33D686B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net48WebApiSample", "samples\net48\Net48WebApiSample\Net48WebApiSample.csproj", "{DED5BFD9-29F6-4291-B863-1995C33D686B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CorrelationId.Net48.Tests", "test\CorrelationId.Net48.Tests\CorrelationId.Net48.Tests.csproj", "{A4137EAE-590B-476C-838F-A2D4A45AAE4A}" EndProject diff --git a/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs b/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs index 9699ecf..3e1e9cd 100644 --- a/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs +++ b/samples/net48/Net48CorrelationId/CorrelationIdMiddlewareWrapper.cs @@ -1,11 +1,13 @@ -using System.Linq; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CorrelationId; using CorrelationId.Abstractions; -using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using AspnetHttpContext = Microsoft.AspNetCore.Http.HttpContext; +using AspnetDefaultHttpContext = Microsoft.AspNetCore.Http.DefaultHttpContext; namespace Net48CorrelationId { @@ -14,61 +16,81 @@ namespace Net48CorrelationId /// public class CorrelationIdMiddlewareWrapper : DelegatingHandler { - private readonly CorrelationIdMiddleware _correlationIdMiddleware; private readonly ICorrelationContextFactory _correlationContextFactory; private readonly IOptions _correlationIdOptions; - - public CorrelationIdMiddlewareWrapper(CorrelationIdMiddleware correlationIdMiddleware, - ICorrelationContextFactory correlationContextFactory, IOptions correlationIdOptions) + private readonly ILogger _correlationIdMiddlewareLoggerWrapper; + private readonly ILogger _correlationIdMiddlewareLogger; + private readonly ICorrelationIdProvider _correlationIdProvider; + private readonly ICorrelationContextAccessor _correlationContextAccessor; + + public CorrelationIdMiddlewareWrapper(ICorrelationContextFactory correlationContextFactory, + IOptions correlationIdOptions, + ILogger correlationIdMiddlewareLoggerWrapper, + // ReSharper disable once ContextualLoggerProblem + ILogger correlationIdMiddlewareLogger, + ICorrelationIdProvider correlationIdProvider, ICorrelationContextAccessor correlationContextAccessor) { - _correlationIdMiddleware = correlationIdMiddleware; _correlationContextFactory = correlationContextFactory; _correlationIdOptions = correlationIdOptions; + _correlationIdMiddlewareLoggerWrapper = correlationIdMiddlewareLoggerWrapper; + _correlationIdMiddlewareLogger = correlationIdMiddlewareLogger; + _correlationIdProvider = correlationIdProvider; + _correlationContextAccessor = correlationContextAccessor; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { - var httpContext = GetHttpContext(); - await _correlationIdMiddleware.Invoke(httpContext, _correlationContextFactory); - - var correlationIds = - httpContext.Request.Headers[_correlationIdOptions.Value.RequestHeader]; + HttpResponseMessage response = null; + var correlationIdMiddleware = new CorrelationIdMiddleware( + context => MiddlewareActionAsync(context, request, x => response = x), _correlationIdMiddlewareLogger, + _correlationIdOptions, _correlationIdProvider); + var aspnetHttpContext = GetAspnetHttpContext(); - foreach (var correlationId in correlationIds) + if (_correlationContextAccessor.CorrelationContext == null) + { + // we should just do this to setup the context if missing + await correlationIdMiddleware.Invoke(aspnetHttpContext, _correlationContextFactory); + } + else { - request.Headers.Add(_correlationIdOptions.Value.RequestHeader, correlationId); + await MiddlewareActionAsync(aspnetHttpContext, request, x => response = x); } - var response = await base.SendAsync(request, cancellationToken); + return response; + } - if (!_correlationIdOptions.Value.IncludeInResponse) + private async Task MiddlewareActionAsync(AspnetHttpContext aspnetHttpContext, + HttpRequestMessage request, Action responseSetter) + { + var correlationId = _correlationContextAccessor?.CorrelationContext?.CorrelationId; + if (!string.IsNullOrEmpty(correlationId) + && !request.Headers.Contains(_correlationContextAccessor.CorrelationContext.Header)) { - return response; + request.Headers.Add(_correlationContextAccessor.CorrelationContext.Header, + _correlationContextAccessor.CorrelationContext.CorrelationId); } - response.Headers.TryGetValues(_correlationIdOptions.Value.ResponseHeader, out var existingCorrelationIdResponseHeaders); - var existingCorrelationIdResponseHeadersList = existingCorrelationIdResponseHeaders?.ToList(); + var response = await base.SendAsync(request, aspnetHttpContext.RequestAborted); - foreach (var correlationId in correlationIds) + // context.Response.OnStarting is not executed on .net FW + if (_correlationIdOptions.Value.IncludeInResponse && + !string.IsNullOrEmpty(correlationId) && + !response.Headers.Contains(_correlationIdOptions.Value.ResponseHeader)) { - // CorrelationId might be added both on server and client part - if (existingCorrelationIdResponseHeadersList != null && - existingCorrelationIdResponseHeadersList.Any(x => x == correlationId)) - { - continue; - } - + _correlationIdMiddlewareLoggerWrapper.LogDebug( + "Writing correlation ID response header {ResponseHeader} with value {CorrelationId}", + _correlationIdOptions.Value.ResponseHeader, correlationId); response.Headers.Add(_correlationIdOptions.Value.ResponseHeader, correlationId); } - - return response; + + responseSetter.Invoke(response); } - private HttpContext GetHttpContext() + private AspnetHttpContext GetAspnetHttpContext() { - var httpContext = new DefaultHttpContext(); + var httpContext = new AspnetDefaultHttpContext(); if (System.Web.HttpContext.Current == null) { diff --git a/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs b/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs index 9161af1..65ddffd 100644 --- a/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs +++ b/samples/net48/Net48CorrelationId/CorrelationIdServiceCollectionExtensions.cs @@ -1,44 +1,12 @@ -using System.Threading.Tasks; -using CorrelationId; -using CorrelationId.Abstractions; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Net48CorrelationId { public static class CorrelationIdServiceCollectionExtensions { - public static void UseCorrelationIdMiddleware(this IServiceCollection serviceCollection, IHttpClientBuilder httpClientBuilder) - { - serviceCollection.UseCorrelationIdMiddleware(); - httpClientBuilder.AddHttpMessageHandler(); - } - public static void UseCorrelationIdMiddleware(this IServiceCollection serviceCollection) { - serviceCollection.AddTransient(x => - { - return new CorrelationIdMiddleware( - next => - { - var correlationContextAccessor = x.GetService(); - // copied from CorrelationId.HttpClient.CorrelationIdHandler - if (!string.IsNullOrEmpty(correlationContextAccessor?.CorrelationContext?.CorrelationId) && - !next.Request.Headers.ContainsKey(correlationContextAccessor.CorrelationContext.Header)) - { - next.Request.Headers.Add(correlationContextAccessor.CorrelationContext.Header, - correlationContextAccessor.CorrelationContext.CorrelationId); - } - - return Task.CompletedTask; - }, - x.GetService>(), - x.GetService>(), - x.GetService()); - }); - - serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); } } } \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs b/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs deleted file mode 100644 index c7a0f3c..0000000 --- a/samples/net48/Net48MvcSample/Controllers/CorrelationIdController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Net; -using System.Web; -using System.Web.Http; -using System.Web.Http.Results; - -namespace Net48MvcSample.Controllers -{ - public class CorrelationIdController : ApiController - { - public IHttpActionResult Get() - { - var correlationId = HttpContext.Current.Request.Headers["X-Correlation-Id"]; - - if (correlationId == null) - { - return new StatusCodeResult(HttpStatusCode.NoContent, Request); - } - - return Ok(correlationId); - } - } -} diff --git a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs b/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs deleted file mode 100644 index 7930dc3..0000000 --- a/samples/net48/Net48MvcSample/CorrelationIdFakeDependencyInjection.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Web.Http; -using CorrelationId; -using CorrelationId.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Net48CorrelationId; - -namespace Net48MvcSample -{ - public static class CorrelationIdFakeDependencyInjection - { - public static void Register(HttpConfiguration config) - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddDefaultCorrelationId(options => - { - options.AddToLoggingScope = true; - options.IgnoreRequestHeader = false; - options.EnforceHeader = false; - options.IncludeInResponse = true; - options.RequestHeader = "X-Correlation-Id"; - options.ResponseHeader = "X-Correlation-Id"; - options.UpdateTraceIdentifier = false; - }); - - serviceCollection.UseCorrelationIdMiddleware(); - - var loggerFactory = LoggerFactory.Create(builder => - { - builder.AddConsole(); - builder.SetMinimumLevel(LogLevel.Debug); - }); - serviceCollection.AddSingleton(_ => loggerFactory.CreateLogger()); - - var serviceProvider = serviceCollection.BuildServiceProvider(); - var correlationIdMiddlewareWrapper = serviceProvider.GetService(); - - config.MessageHandlers.Add(correlationIdMiddlewareWrapper); - } - } -} \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Global.asax b/samples/net48/Net48MvcSample/Global.asax deleted file mode 100644 index 5d98dde..0000000 --- a/samples/net48/Net48MvcSample/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Net48MvcSample.MvcApplication" Language="C#" %> \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs b/samples/net48/Net48WebApiSample/App_Start/WebApiConfig.cs similarity index 96% rename from samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs rename to samples/net48/Net48WebApiSample/App_Start/WebApiConfig.cs index 788325c..ae75069 100644 --- a/samples/net48/Net48MvcSample/App_Start/WebApiConfig.cs +++ b/samples/net48/Net48WebApiSample/App_Start/WebApiConfig.cs @@ -1,6 +1,6 @@ using System.Web.Http; -namespace Net48MvcSample +namespace Net48WebApiSample { public static class WebApiConfig { diff --git a/samples/net48/Net48WebApiSample/Controllers/CorrelationIdController.cs b/samples/net48/Net48WebApiSample/Controllers/CorrelationIdController.cs new file mode 100644 index 0000000..a25eb84 --- /dev/null +++ b/samples/net48/Net48WebApiSample/Controllers/CorrelationIdController.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Results; +using CorrelationId; +using Microsoft.Extensions.Options; + +namespace Net48WebApiSample.Controllers +{ + public class CorrelationIdController : ApiController + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOptions _correlationIdOptions; + + public CorrelationIdController(IHttpClientFactory httpClientFactory, + IOptions correlationIdOptions) + { + _httpClientFactory = httpClientFactory; + _correlationIdOptions = correlationIdOptions; + } + + public async Task Get() + { + Request.Headers.TryGetValues(_correlationIdOptions.Value.RequestHeader, out var correlationIds); + var correlationId = correlationIds.SingleOrDefault(); + + if (correlationId == null) + { + return new StatusCodeResult(HttpStatusCode.NoContent, Request); + } + + var client = + _httpClientFactory.CreateClient("MyClient"); // this client will attach the correlation ID header + + var innerResponse = await client.GetAsync("https://www.example.com"); + + innerResponse.Headers.TryGetValues(_correlationIdOptions.Value.RequestHeader, + out var innerResponseCorrelationIds); + var innerResponseCorrelationId = innerResponseCorrelationIds.SingleOrDefault(); + + if (innerResponseCorrelationId != correlationId) + { + return Conflict(); + } + + return Ok(correlationId); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48WebApiSample/DependencyInjection.cs b/samples/net48/Net48WebApiSample/DependencyInjection.cs new file mode 100644 index 0000000..58b45be --- /dev/null +++ b/samples/net48/Net48WebApiSample/DependencyInjection.cs @@ -0,0 +1,84 @@ +using System; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Dispatcher; +using CorrelationId.DependencyInjection; +using CorrelationId.HttpClient; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Net48CorrelationId; +using Net48WebApiSample.Extensions; + +namespace Net48WebApiSample +{ + public static class DependencyInjection + { + public static void Register(HttpConfiguration config) + { + var serviceCollection = new ServiceCollection(); + ConfigureServices(serviceCollection); + + var serviceProvider = serviceCollection.BuildServiceProvider(new ServiceProviderOptions + { + // Prefer to keep validation on at all times + ValidateOnBuild = true, + ValidateScopes = true + }); + + GlobalConfiguration.Configuration.Services.Replace( + typeof(IHttpControllerActivator), + new MsDiHttpControllerActivator(serviceProvider)); + + var correlationIdMiddlewareWrapper = serviceProvider.GetService(); + config.MessageHandlers.Add(correlationIdMiddlewareWrapper); + } + + private static void ConfigureServices(IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + + serviceCollection.AddHttpClient("MyClient") + .AddCorrelationIdForwarding() // add the handler to attach the correlation ID to outgoing requests for this named client + .AddHttpMessageHandler(); + + serviceCollection.AddDefaultCorrelationId(options => + { + options.AddToLoggingScope = true; + options.IgnoreRequestHeader = false; + options.EnforceHeader = false; + options.IncludeInResponse = true; + options.RequestHeader = "X-Correlation-Id"; + options.ResponseHeader = "X-Correlation-Id"; + options.UpdateTraceIdentifier = false; + }); + + serviceCollection.UseCorrelationIdMiddleware(); + + serviceCollection.AddControllers(); + + serviceCollection.AddLogging(loggerFactory => + { + loggerFactory.AddConsole(); + loggerFactory.SetMinimumLevel(LogLevel.Debug); + }); + } + } + + public class MsDiHttpControllerActivator : IHttpControllerActivator + { + private readonly ServiceProvider _provider; + + public MsDiHttpControllerActivator(ServiceProvider provider) + { + _provider = provider; + } + + public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor descriptor, Type type) + { + var scope = _provider.CreateScope(); + request.RegisterForDispose(scope); + return (IHttpController)scope.ServiceProvider.GetRequiredService(type); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48WebApiSample/Extensions/ServiceCollectionExtensions.cs b/samples/net48/Net48WebApiSample/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..57436fb --- /dev/null +++ b/samples/net48/Net48WebApiSample/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Net48WebApiSample.Extensions +{ + public static class ServiceCollectionExtensions + { + public static void AddControllers(this IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + } + } +} \ No newline at end of file diff --git a/samples/net48/Net48WebApiSample/Global.asax b/samples/net48/Net48WebApiSample/Global.asax new file mode 100644 index 0000000..ede897c --- /dev/null +++ b/samples/net48/Net48WebApiSample/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Net48WebApiSample.MvcApplication" Language="C#" %> \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Global.asax.cs b/samples/net48/Net48WebApiSample/Global.asax.cs similarity index 66% rename from samples/net48/Net48MvcSample/Global.asax.cs rename to samples/net48/Net48WebApiSample/Global.asax.cs index 9d08d0d..dc13a92 100644 --- a/samples/net48/Net48MvcSample/Global.asax.cs +++ b/samples/net48/Net48WebApiSample/Global.asax.cs @@ -1,13 +1,13 @@ using System.Web.Http; -namespace Net48MvcSample +namespace Net48WebApiSample { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); - GlobalConfiguration.Configure(CorrelationIdFakeDependencyInjection.Register); + GlobalConfiguration.Configure(DependencyInjection.Register); } } } \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Net48MvcSample.csproj b/samples/net48/Net48WebApiSample/Net48WebApiSample.csproj similarity index 90% rename from samples/net48/Net48MvcSample/Net48MvcSample.csproj rename to samples/net48/Net48WebApiSample/Net48WebApiSample.csproj index 3ca5919..a174786 100644 --- a/samples/net48/Net48MvcSample/Net48MvcSample.csproj +++ b/samples/net48/Net48WebApiSample/Net48WebApiSample.csproj @@ -9,8 +9,8 @@ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} Library Properties - Net48MvcSample - Net48MvcSample + Net48WebApiSample + Net48WebApiSample v4.8 false true @@ -42,6 +42,9 @@ + + ..\..\..\..\..\Users\miha39\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\7.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + @@ -70,10 +73,12 @@ - + + Global.asax + @@ -98,6 +103,7 @@ + diff --git a/samples/net48/Net48WebApiSample/NoOpDelegatingHandler.cs b/samples/net48/Net48WebApiSample/NoOpDelegatingHandler.cs new file mode 100644 index 0000000..1d2791b --- /dev/null +++ b/samples/net48/Net48WebApiSample/NoOpDelegatingHandler.cs @@ -0,0 +1,32 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Net48WebApiSample +{ + public class NoOpDelegatingHandler : DelegatingHandler + { + private readonly ILogger _logger; + + public NoOpDelegatingHandler(ILogger logger) => _logger = logger; + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.Headers.TryGetValues("X-Correlation-Id", out var headerEnumerable)) + { + _logger.LogInformation("Request has the following correlation ID header {CorrelationId}.", headerEnumerable.FirstOrDefault()); + } + else + { + _logger.LogInformation("Request does not have a correlation ID header."); + } + + var response = new HttpResponseMessage(HttpStatusCode.OK); + + return Task.FromResult(response); + } + } +} diff --git a/samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs b/samples/net48/Net48WebApiSample/Properties/AssemblyInfo.cs similarity index 68% rename from samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs rename to samples/net48/Net48WebApiSample/Properties/AssemblyInfo.cs index e05c321..66c9f0b 100644 --- a/samples/net48/Net48MvcSample/Properties/AssemblyInfo.cs +++ b/samples/net48/Net48WebApiSample/Properties/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("Net48MvcSample")] -[assembly: AssemblyProduct("Net48MvcSample")] +[assembly: AssemblyTitle("Net48WebApiSample")] +[assembly: AssemblyProduct("Net48WebApiSample")] [assembly: Guid("DED5BFD9-29F6-4291-B863-1995C33D686B")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/samples/net48/Net48MvcSample/Web.Debug.config b/samples/net48/Net48WebApiSample/Web.Debug.config similarity index 100% rename from samples/net48/Net48MvcSample/Web.Debug.config rename to samples/net48/Net48WebApiSample/Web.Debug.config diff --git a/samples/net48/Net48MvcSample/Web.Release.config b/samples/net48/Net48WebApiSample/Web.Release.config similarity index 100% rename from samples/net48/Net48MvcSample/Web.Release.config rename to samples/net48/Net48WebApiSample/Web.Release.config diff --git a/samples/net48/Net48MvcSample/Web.config b/samples/net48/Net48WebApiSample/Web.config similarity index 100% rename from samples/net48/Net48MvcSample/Web.config rename to samples/net48/Net48WebApiSample/Web.config diff --git a/samples/net6/MvcSample/Controllers/CorrelationIdController.cs b/samples/net6/MvcSample/Controllers/CorrelationIdController.cs index beb0668..7ac0857 100644 --- a/samples/net6/MvcSample/Controllers/CorrelationIdController.cs +++ b/samples/net6/MvcSample/Controllers/CorrelationIdController.cs @@ -1,4 +1,10 @@ -using Microsoft.AspNetCore.Mvc; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using CorrelationId; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace MvcSample.Controllers { @@ -6,11 +12,36 @@ namespace MvcSample.Controllers [Route("api/[controller]")] public class CorrelationIdController : ControllerBase { + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOptions _correlationIdOptions; + + public CorrelationIdController(IHttpClientFactory httpClientFactory, + IOptions correlationIdOptions) + { + _httpClientFactory = httpClientFactory; + _correlationIdOptions = correlationIdOptions; + } + [HttpGet] - public string Get() + public async Task Get() { - var correlationId = Request.Headers["X-Correlation-Id"]; - return correlationId; + var correlationId = Request.Headers[_correlationIdOptions.Value.RequestHeader].SingleOrDefault(); + + var client = + _httpClientFactory.CreateClient("MyClient"); // this client will attach the correlation ID header + + var innerResponse = await client.GetAsync("https://www.example.com"); + + innerResponse.Headers.TryGetValues(_correlationIdOptions.Value.RequestHeader, + out var innerResponseCorrelationIds); + var innerResponseCorrelationId = innerResponseCorrelationIds?.SingleOrDefault(); + + if (innerResponseCorrelationId != correlationId) + { + return Results.Conflict(); + } + + return Results.Ok(correlationId); } } -} +} \ No newline at end of file diff --git a/src/CorrelationId/CorrelationIdMiddleware.cs b/src/CorrelationId/CorrelationIdMiddleware.cs index 1912cde..dd20235 100644 --- a/src/CorrelationId/CorrelationIdMiddleware.cs +++ b/src/CorrelationId/CorrelationIdMiddleware.cs @@ -81,6 +81,11 @@ public async Task Invoke(HttpContext context, ICorrelationContextFactory correla if (_options.IgnoreRequestHeader || RequiresGenerationOfCorrelationId(hasCorrelationIdHeader, cid)) { correlationId = GenerateCorrelationId(context); + if (!string.IsNullOrEmpty(correlationId) + && !context.Request.Headers.ContainsKey(_options.RequestHeader)) + { + context.Request.Headers.Add(_options.RequestHeader, correlationId); + } } if (!string.IsNullOrEmpty(correlationId) && _options.UpdateTraceIdentifier) diff --git a/src/CorrelationId/HttpClient/CorrelationIdHandler.cs b/src/CorrelationId/HttpClient/CorrelationIdHandler.cs index 5732b6e..ea6fe49 100644 --- a/src/CorrelationId/HttpClient/CorrelationIdHandler.cs +++ b/src/CorrelationId/HttpClient/CorrelationIdHandler.cs @@ -2,29 +2,48 @@ using CorrelationId.Abstractions; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; namespace CorrelationId.HttpClient { /// /// A which adds the correlation ID header from the onto outgoing HTTP requests. + /// And on its response if configured. /// internal sealed class CorrelationIdHandler : DelegatingHandler { private readonly ICorrelationContextAccessor _correlationContextAccessor; - - public CorrelationIdHandler(ICorrelationContextAccessor correlationContextAccessor) => _correlationContextAccessor = correlationContextAccessor; + private readonly IOptions _correlationIdOptions; + + public CorrelationIdHandler(ICorrelationContextAccessor correlationContextAccessor, + IOptions correlationIdOptions) + { + _correlationContextAccessor = correlationContextAccessor; + _correlationIdOptions = correlationIdOptions; + } /// - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - string correlationId = _correlationContextAccessor?.CorrelationContext?.CorrelationId; + var correlationId = _correlationContextAccessor?.CorrelationContext?.CorrelationId; if (!string.IsNullOrEmpty(correlationId) && !request.Headers.Contains(_correlationContextAccessor.CorrelationContext.Header)) { - request.Headers.Add(_correlationContextAccessor.CorrelationContext.Header, _correlationContextAccessor.CorrelationContext.CorrelationId); + request.Headers.Add(_correlationContextAccessor.CorrelationContext.Header, + _correlationContextAccessor.CorrelationContext.CorrelationId); + } + + var response = await base.SendAsync(request, cancellationToken); + + if (_correlationIdOptions.Value.IncludeInResponse && + !string.IsNullOrEmpty(correlationId) + && !response.Headers.Contains(_correlationIdOptions.Value.ResponseHeader)) + { + response.Headers.Add(_correlationIdOptions.Value.ResponseHeader, + _correlationContextAccessor.CorrelationContext.CorrelationId); } - return base.SendAsync(request, cancellationToken); + return response; } } } diff --git a/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj index cd96fb3..649dfd2 100644 --- a/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj +++ b/test/CorrelationId.Net48.Tests/CorrelationId.Net48.Tests.csproj @@ -62,6 +62,7 @@ + diff --git a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs index fbc1297..fd4c3ff 100644 --- a/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs +++ b/test/CorrelationId.Net48.Tests/HttpClientBuilderTests.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Threading.Tasks; using CorrelationId.DependencyInjection; +using CorrelationId.HttpClient; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Net48CorrelationId; using Xunit; using Xunit.Abstractions; @@ -26,17 +26,16 @@ public HttpClientBuilderTests(ITestOutputHelper testOutputHelper) options.ResponseHeader = "X-Correlation-Id"; options.UpdateTraceIdentifier = false; }); - - var httpClientBuilder = serviceCollection.AddHttpClient(); - serviceCollection.UseCorrelationIdMiddleware(httpClientBuilder); - - var loggerFactory = LoggerFactory.Create(builder => + serviceCollection + .AddHttpClient() + .AddCorrelationIdForwarding(); + + serviceCollection.AddLogging(loggingBuilder => { - builder.AddXUnit(testOutputHelper); - builder.SetMinimumLevel(LogLevel.Debug); + loggingBuilder.AddXUnit(testOutputHelper); + loggingBuilder.SetMinimumLevel(LogLevel.Debug); }); - serviceCollection.AddSingleton(_ => loggerFactory.CreateLogger()); var serviceProvider = serviceCollection.BuildServiceProvider(); _net48MvcSampleApiClient = serviceProvider.GetService(); diff --git a/test/CorrelationId.Net48.Tests/HttpClientTest.cs b/test/CorrelationId.Net48.Tests/HttpClientTest.cs index 596e4f5..1d3087c 100644 --- a/test/CorrelationId.Net48.Tests/HttpClientTest.cs +++ b/test/CorrelationId.Net48.Tests/HttpClientTest.cs @@ -23,7 +23,7 @@ public async Task CallApi_ShouldReturnCorrelationId() client.BaseAddress = new Uri("http://localhost:31488"); var response = await client.GetAsync("/api/CorrelationId"); - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Single(response.Headers.GetValues("X-Correlation-Id")); Assert.True(Guid.TryParse(response.Headers.GetValues("X-Correlation-Id").Single(), out _)); diff --git a/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs b/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs index 1433dec..1a0523d 100644 --- a/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs +++ b/test/CorrelationId.Tests/CorrelationIdMiddlewareTests.cs @@ -14,6 +14,9 @@ using System.Net; using CorrelationId.Abstractions; using CorrelationId.DependencyInjection; +using CorrelationId.HttpClient; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace CorrelationId.Tests { @@ -115,7 +118,7 @@ public async Task IgnoresRequestHeader_WhenOptionIsTrue() { var builder = new WebHostBuilder() .Configure(app => app.UseCorrelationId()) - .ConfigureServices(sc => sc.AddDefaultCorrelationId(options => { options.IgnoreRequestHeader = true; options.IncludeInResponse = true; }));; + .ConfigureServices(sc => sc.AddDefaultCorrelationId(options => { options.IgnoreRequestHeader = true; options.IncludeInResponse = true; })); using var server = new TestServer(builder); @@ -134,7 +137,7 @@ public async Task DoesNotIgnoresRequestHeader_WhenOptionIsFalse() { var builder = new WebHostBuilder() .Configure(app => app.UseCorrelationId()) - .ConfigureServices(sc => sc.AddDefaultCorrelationId(options => { options.IgnoreRequestHeader = false; options.IncludeInResponse = true; })); ; + .ConfigureServices(sc => sc.AddDefaultCorrelationId(options => { options.IgnoreRequestHeader = false; options.IncludeInResponse = true; })); using var server = new TestServer(builder); @@ -257,7 +260,58 @@ public async Task CorrelationId_SetToCorrelationIdFromRequestHeader() Assert.Single(header, expectedHeaderValue); } + + [Fact] + public async Task CorrelationId_UpdatesRequestWithtGeneratedCorrelationIdWhenNotGiven() + { + var expectedHeaderName = new CorrelationIdOptions().RequestHeader; + StringValues excectedRequestValue; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseCorrelationId(); + app.Use((HttpContext ctx, Func _) => + { + ctx.Request.Headers.TryGetValue(expectedHeaderName, out excectedRequestValue); + return Task.CompletedTask; + }); + }) + + .ConfigureServices(sc => sc.AddDefaultCorrelationId()); + + using var server = new TestServer(builder); + + var request = new HttpRequestMessage(); + + await server.CreateClient().SendAsync(request); + + Assert.Single(excectedRequestValue); + Assert.True(Guid.TryParse(excectedRequestValue.Single(), out _)); + } + + [Fact] + public async Task CorrelationId_ReturnsGeneratedCorrelationIdWhenNotGiven() + { + var expectedHeaderName = new CorrelationIdOptions().RequestHeader; + var builder = new WebHostBuilder() + .Configure(app => app.UseCorrelationId()) + .ConfigureServices(sc => sc.AddDefaultCorrelationId()); + + using var server = new TestServer(builder); + + var request = new HttpRequestMessage(); + + var response = await server.CreateClient().SendAsync(request); + + var header = response.Headers.GetValues(expectedHeaderName) + .ToList(); + + Assert.Single(header); + Assert.True(Guid.TryParse(header.Single(), out _)); + } + [Fact] public async Task CorrelationId_SetToGuid_RegisteredWithAddDefaultCorrelationId() { @@ -600,6 +654,67 @@ public async Task TraceIdentifier_IsUpdated_WhenUpdateTraceIdentifierIsTrue() Assert.Equal(correlationId, traceIdentifier); } + + [Fact] + public async Task NestedHttpClient_ShouldReturnSameCorrelationId() + { + var expectedHeaderName = new CorrelationIdOptions().RequestHeader; + const string correlationId = "123456"; + + var innerBuilder = new WebHostBuilder() + .Configure(app => + { + app.UseCorrelationId(); + app.Use((HttpContext _, Func _) => Task.CompletedTask); + }) + .ConfigureServices(sc => sc.AddDefaultCorrelationId()); + + using var innerServer = new TestServer(innerBuilder); + var innerHandler = innerServer.CreateHandler(); + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseCorrelationId(); + app.Use((HttpContext ctx, Func _) => + Task.Run(async () => + { + var httpClientFactory = ctx.RequestServices.GetService(); + var client = httpClientFactory + .CreateClient("MyClient"); // this client will attach the correlation ID header + + var innerResponse = await client.GetAsync("https://www.example.com"); + + var innerResponseCorrelationId = + innerResponse.Headers.GetValues(new CorrelationIdOptions().RequestHeader).Single(); + + Assert.Equal(correlationId, innerResponseCorrelationId); + })); + }) + .ConfigureServices(serviceCollection => + { + serviceCollection.AddLogging(logging => + { + logging.AddDebug(); + logging.SetMinimumLevel(LogLevel.Trace); + }); + serviceCollection.AddDefaultCorrelationId(); + serviceCollection.AddHttpClient("MyClient") + .ConfigurePrimaryHttpMessageHandler(_ => innerHandler).AddCorrelationIdForwarding(); + }); + + using var server = new TestServer(builder); + + var request = new HttpRequestMessage(); + request.Headers.Add(expectedHeaderName, correlationId); + + var response = await server.CreateClient().SendAsync(request); + + var header = response.Headers.GetValues(expectedHeaderName); + + Assert.Single(header, correlationId); + + } private class SingletonClass {