HeaderPropagation: propagate incoming request headers to outgoing HTTP requests (#7921)
* Ported HeaderPropagation from aspnet/Extensions * Introduced Middleware * Refactored middleware logic * Refactored builder extensions * Copyright notice * Test for friendly exception on Builder * Fixed header name selection when no output name specified * Set comparer for the dictionary of headers * Refactored configuration as Dictionary * Renamed state objects * renamed OutboundHeaderName in configuration * Changed DefaultValuesGenerator to ValueFactory * Missing docs * Removed AlwaysAdd and added tests for null entry in configuration * Improved docs * Update src/Middleware/HeaderPropagation/src/DependencyInjection/HeaderPropagationExtensions.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * Moved dependency injection extensions * DI: reused ServiceCollection extension in the HttpClientBuilder one * Moved service registration * Update src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * more docs * Improved docs * Update src/Middleware/HeaderPropagation/src/HeaderPropagationValues.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * Fixed build * Update eng/SharedFramework.Local.props Co-Authored-By: alefranz <alessio@franceschelli.me> * Updated tests for null config * Reversed condition on HeaderPropagationMessageHandler as suggested * Added docs for HeaderPropagationMessageHandler * Changed proj to ship package to NuGet
This commit is contained in:
parent
f6130e8430
commit
f28cf2bbc8
|
|
@ -73,6 +73,7 @@
|
|||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.Abstractions" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.Abstractions\src\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.Abstractions\ref\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.EntityFrameworkCore\src\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.EntityFrameworkCore\ref\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics\src\Microsoft.AspNetCore.Diagnostics.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics\ref\Microsoft.AspNetCore.Diagnostics.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.HeaderPropagation" ProjectPath="$(RepositoryRoot)src\Middleware\HeaderPropagation\src\Microsoft.AspNetCore.HeaderPropagation.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HeaderPropagation\ref\Microsoft.AspNetCore.HeaderPropagation.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" ProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks.EntityFrameworkCore\src\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks.EntityFrameworkCore\ref\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" ProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks\src\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks\ref\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.HostFiltering" ProjectPath="$(RepositoryRoot)src\Middleware\HostFiltering\src\Microsoft.AspNetCore.HostFiltering.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HostFiltering\ref\Microsoft.AspNetCore.HostFiltering.csproj" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.HeaderPropagation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public static class HeaderPropagationApplicationBuilderExtensions
|
||||
{
|
||||
private static readonly string _unableToFindServices = string.Format(
|
||||
"Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to 'ConfigureServices(...)' in the application startup code.",
|
||||
nameof(IServiceCollection),
|
||||
nameof(HeaderPropagationServiceCollectionExtensions.AddHeaderPropagation));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a middleware that collect headers to be propagated to a <see cref="HttpClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
|
||||
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
|
||||
public static IApplicationBuilder UseHeaderPropagation(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
if (app.ApplicationServices.GetService<HeaderPropagationValues>() == null)
|
||||
{
|
||||
throw new InvalidOperationException(_unableToFindServices);
|
||||
}
|
||||
|
||||
return app.UseMiddleware<HeaderPropagationMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.HeaderPropagation;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class HeaderPropagationHttpClientBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHttpClientBuilder"/> to add the message handler to.</param>
|
||||
/// <returns>The <see cref="IHttpClientBuilder"/> so that additional calls can be chained.</returns>
|
||||
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.Services.AddHeaderPropagation();
|
||||
|
||||
builder.AddHttpMessageHandler<HeaderPropagationMessageHandler>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.HeaderPropagation;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class HeaderPropagationServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds services required for propagating headers to a <see cref="HttpClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddHeaderPropagation(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.TryAddSingleton<HeaderPropagationValues>();
|
||||
services.TryAddTransient<HeaderPropagationMessageHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds services required for propagating headers to a <see cref="HttpClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
|
||||
/// <param name="configureOptions">A delegate used to configure the <see cref="HeaderPropagationOptions"/>.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddHeaderPropagation(this IServiceCollection services, Action<HeaderPropagationOptions> configureOptions)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (configureOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configureOptions));
|
||||
}
|
||||
|
||||
services.Configure(configureOptions);
|
||||
services.AddHeaderPropagation();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// Define the configuration of a header for the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the header to be used by the <see cref="HeaderPropagationMessageHandler"/> for the
|
||||
/// outbound http requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="ValueFactory"/> is present, the value of the header in the outbound calls will be the one
|
||||
/// returned by the factory or, if the factory returns an empty value, the header will be omitted.
|
||||
/// Otherwise, it will be the value of the header in the incoming request named as the key of this entry in
|
||||
/// <see cref="HeaderPropagationOptions.Headers"/> or, if missing or empty, the value specified in
|
||||
/// <see cref="DefaultValue"/> or, if the <see cref="DefaultValue"/> is empty, it will not be
|
||||
/// added to the outbound calls.
|
||||
/// </remarks>
|
||||
public string OutboundHeaderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default value to be used when the header in the incoming request is missing or empty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is ignored when <see cref="ValueFactory"/> is set.
|
||||
/// When it is <see cref="StringValues.Empty"/> it has no effect and, if the header is missing or empty in the
|
||||
/// incoming request, it will not be added to outbound calls.
|
||||
/// </remarks>
|
||||
public StringValues DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value factory to be used.
|
||||
/// It gets as input the inbound header name for this entry as defined in
|
||||
/// <see cref="HeaderPropagationOptions.Headers"/> and the <see cref="HttpContext"/> of the current request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When present, the factory is the only method used to set the value.
|
||||
/// The factory should return <see cref="StringValues.Empty"/> to not add the header.
|
||||
/// When not present, the value will be taken from the header in the incoming request named as the key of this
|
||||
/// entry in <see cref="HeaderPropagationOptions.Headers"/> or, if missing or empty, it will be the values
|
||||
/// specified in <see cref="DefaultValue"/> or, if the <see cref="DefaultValue"/> is empty, the header will not
|
||||
/// be added to the outbound calls.
|
||||
/// Please note the factory is called only once per incoming request and the same value will be used by all the
|
||||
/// outbound calls.
|
||||
/// </remarks>
|
||||
public Func<string, HttpContext, StringValues> ValueFactory { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// A message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request.
|
||||
/// </summary>
|
||||
public class HeaderPropagationMessageHandler : DelegatingHandler
|
||||
{
|
||||
private readonly HeaderPropagationValues _values;
|
||||
private readonly HeaderPropagationOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The options that define which headers are propagated.</param>
|
||||
/// <param name="values">The values of the headers to be propagated populated by the
|
||||
/// <see cref="HeaderPropagationMiddleware"/>.</param>
|
||||
public HeaderPropagationMessageHandler(IOptions<HeaderPropagationOptions> options, HeaderPropagationValues values)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
|
||||
_values = values ?? throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation, after adding
|
||||
/// the propagated headers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If an header with the same name is already present in the request, even if empty, the corresponding
|
||||
/// propagated header will not be added.
|
||||
/// </remarks>
|
||||
/// <param name="request">The HTTP request message to send to the server.</param>
|
||||
/// <param name="cancellationToken">A cancellation token to cancel operation.</param>
|
||||
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach ((var headerName, var entry) in _options.Headers)
|
||||
{
|
||||
var outputName = string.IsNullOrEmpty(entry?.OutboundHeaderName) ? headerName : entry.OutboundHeaderName;
|
||||
|
||||
if (!request.Headers.Contains(outputName) &&
|
||||
_values.Headers.TryGetValue(headerName, out var values) &&
|
||||
!StringValues.IsNullOrEmpty(values))
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation(outputName, (string[])values);
|
||||
}
|
||||
}
|
||||
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// A Middleware for propagating headers to a <see cref="HttpClient"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly HeaderPropagationOptions _options;
|
||||
private readonly HeaderPropagationValues _values;
|
||||
|
||||
public HeaderPropagationMiddleware(RequestDelegate next, IOptions<HeaderPropagationOptions> options, HeaderPropagationValues values)
|
||||
{
|
||||
_next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
_options = options.Value;
|
||||
|
||||
_values = values ?? throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
foreach ((var headerName, var entry) in _options.Headers)
|
||||
{
|
||||
var values = GetValues(headerName, entry, context);
|
||||
|
||||
if (!StringValues.IsNullOrEmpty(values))
|
||||
{
|
||||
_values.Headers.TryAdd(headerName, values);
|
||||
}
|
||||
}
|
||||
|
||||
return _next.Invoke(context);
|
||||
}
|
||||
|
||||
private static StringValues GetValues(string headerName, HeaderPropagationEntry entry, HttpContext context)
|
||||
{
|
||||
if (entry?.ValueFactory != null)
|
||||
{
|
||||
return entry.ValueFactory(headerName, context);
|
||||
}
|
||||
|
||||
if (context.Request.Headers.TryGetValue(headerName, out var values)
|
||||
&& !StringValues.IsNullOrEmpty(values))
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
return entry != null ? entry.DefaultValue : StringValues.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides configuration for the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the headers to be collected by the <see cref="HeaderPropagationMiddleware"/>
|
||||
/// and to be propagated by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
public IDictionary<string, HeaderPropagationEntry> Headers { get; set; } = new Dictionary<string, HeaderPropagationEntry>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the headers values for the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationValues
|
||||
{
|
||||
private readonly static AsyncLocal<Dictionary<string, StringValues>> _headers = new AsyncLocal<Dictionary<string, StringValues>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the headers values collected by the <see cref="HeaderPropagationMiddleware"/> from the current request that can be propagated.
|
||||
/// </summary>
|
||||
public IDictionary<string, StringValues> Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _headers.Value ?? (_headers.Value = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests</Description>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<IsShippingPackage>true</IsShippingPackage>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;httpclient</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.AspNetCore.HeaderPropagation.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Http" />
|
||||
<Reference Include="Microsoft.Extensions.Http" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
||||
{
|
||||
public class HeaderPropagationIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task HeaderInRequest_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var handler = new SimpleHandler();
|
||||
var builder = CreateBuilder(c =>
|
||||
c.Headers.Add("in", new HeaderPropagationEntry
|
||||
{
|
||||
OutboundHeaderName = "out",
|
||||
}),
|
||||
handler);
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(handler.Headers.Contains("out"));
|
||||
Assert.Equal(new[] { "test" }, handler.Headers.GetValues("out"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_UseHeaderPropagation_Without_AddHeaderPropagation_Throws()
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseHeaderPropagation();
|
||||
});
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new TestServer(builder));
|
||||
Assert.Equal(
|
||||
"Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddHeaderPropagation' inside the call to 'ConfigureServices(...)' in the application startup code.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
private IWebHostBuilder CreateBuilder(Action<HeaderPropagationOptions> configure, HttpMessageHandler primaryHandler)
|
||||
{
|
||||
return new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseHeaderPropagation();
|
||||
app.UseMiddleware<SimpleMiddleware>();
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
|
||||
.ConfigureHttpMessageHandlerBuilder(b =>
|
||||
{
|
||||
b.PrimaryHandler = primaryHandler;
|
||||
})
|
||||
.AddHeaderPropagation();
|
||||
services.AddHeaderPropagation(configure);
|
||||
});
|
||||
}
|
||||
|
||||
private class SimpleHandler : DelegatingHandler
|
||||
{
|
||||
public HttpHeaders Headers { get; private set; }
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
Headers = request.Headers;
|
||||
return Task.FromResult(new HttpResponseMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleMiddleware
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public SimpleMiddleware(RequestDelegate next, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public Task InvokeAsync(HttpContext _)
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("example.com");
|
||||
return client.GetAsync("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
||||
{
|
||||
public class HeaderPropagationMessageHandlerTest
|
||||
{
|
||||
public HeaderPropagationMessageHandlerTest()
|
||||
{
|
||||
Handler = new SimpleHandler();
|
||||
|
||||
State = new HeaderPropagationValues();
|
||||
Configuration = new HeaderPropagationOptions();
|
||||
|
||||
var headerPropagationMessageHandler =
|
||||
new HeaderPropagationMessageHandler(Options.Create(Configuration), State)
|
||||
{
|
||||
InnerHandler = Handler
|
||||
};
|
||||
|
||||
Client = new HttpClient(headerPropagationMessageHandler)
|
||||
{
|
||||
BaseAddress = new Uri("http://example.com")
|
||||
};
|
||||
}
|
||||
|
||||
private SimpleHandler Handler { get; }
|
||||
public HeaderPropagationValues State { get; set; }
|
||||
public HeaderPropagationOptions Configuration { get; set; }
|
||||
public HttpClient Client { get; set; }
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry { OutboundHeaderName = "out" });
|
||||
State.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("out"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("out"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_NoOutputName_UseInputName()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry());
|
||||
State.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("in"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("in"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoHeaderInState_DoesNotAddIt()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("inout", new HeaderPropagationEntry());
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(Handler.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_NotInOptions_DoesNotAddIt()
|
||||
{
|
||||
// Arrange
|
||||
State.Headers.Add("inout", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(Handler.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleHeadersInState_AddsAll()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("inout", new HeaderPropagationEntry());
|
||||
Configuration.Headers.Add("another", new HeaderPropagationEntry());
|
||||
State.Headers.Add("inout", "test");
|
||||
State.Headers.Add("another", "test2");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("inout"));
|
||||
Assert.True(Handler.Headers.Contains("another"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("inout"));
|
||||
Assert.Equal(new[] { "test2" }, Handler.Headers.GetValues("another"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public async Task HeaderEmptyInState_DoNotAddIt(string headerValue)
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("inout", new HeaderPropagationEntry());
|
||||
State.Headers.Add("inout", headerValue);
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.False(Handler.Headers.Contains("inout"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", new[] { "" })]
|
||||
[InlineData(null, new[] { "" })]
|
||||
[InlineData("42", new[] { "42" })]
|
||||
public async Task HeaderInState_HeaderAlreadyInOutgoingRequest(string outgoingValue,
|
||||
string[] expectedValues)
|
||||
{
|
||||
// Arrange
|
||||
State.Headers.Add("inout", "test");
|
||||
Configuration.Headers.Add("inout", new HeaderPropagationEntry());
|
||||
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Add("inout", outgoingValue);
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("inout"));
|
||||
Assert.Equal(expectedValues, Handler.Headers.GetValues("inout"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullEntryInConfiguration_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", null);
|
||||
State.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("in"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("in"));
|
||||
}
|
||||
|
||||
private class SimpleHandler : DelegatingHandler
|
||||
{
|
||||
public HttpHeaders Headers { get; private set; }
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Headers = request.Headers;
|
||||
return Task.FromResult(new HttpResponseMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
||||
{
|
||||
public class HeaderPropagationMiddlewareTest
|
||||
{
|
||||
public HeaderPropagationMiddlewareTest()
|
||||
{
|
||||
Context = new DefaultHttpContext();
|
||||
Next = ctx => Task.CompletedTask;
|
||||
Configuration = new HeaderPropagationOptions();
|
||||
State = new HeaderPropagationValues();
|
||||
Middleware = new HeaderPropagationMiddleware(Next,
|
||||
new OptionsWrapper<HeaderPropagationOptions>(Configuration),
|
||||
State);
|
||||
}
|
||||
|
||||
public DefaultHttpContext Context { get; set; }
|
||||
public RequestDelegate Next { get; set; }
|
||||
public HeaderPropagationOptions Configuration { get; set; }
|
||||
public HeaderPropagationValues State { get; set; }
|
||||
public HeaderPropagationMiddleware Middleware { get; set; }
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInRequest_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry());
|
||||
Context.Request.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal(new[] { "test" }, State.Headers["in"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoHeaderInRequest_DoesNotAddIt()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry());
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(State.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInRequest_NotInOptions_DoesNotAddIt()
|
||||
{
|
||||
// Arrange
|
||||
Context.Request.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(State.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleHeadersInRequest_AddAllHeaders()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry());
|
||||
Configuration.Headers.Add("another", new HeaderPropagationEntry());
|
||||
Context.Request.Headers.Add("in", "test");
|
||||
Context.Request.Headers.Add("another", "test2");
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal(new[] { "test" }, State.Headers["in"]);
|
||||
Assert.Contains("another", State.Headers.Keys);
|
||||
Assert.Equal(new[] { "test2" }, State.Headers["another"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public async Task HeaderEmptyInRequest_DoesNotAddIt(string headerValue)
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry());
|
||||
Context.Request.Headers.Add("in", headerValue);
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("in", State.Headers.Keys);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] { "default" }, new[] { "default" })]
|
||||
[InlineData(new[] { "default", "other" }, new[] { "default", "other" })]
|
||||
public async Task NoHeaderInRequest_AddsDefaultValue(string[] defaultValues,
|
||||
string[] expectedValues)
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry { DefaultValue = defaultValues });
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal(expectedValues, State.Headers["in"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] { "default" }, new[] { "default" })]
|
||||
[InlineData(new[] { "default", "other" }, new[] { "default", "other" })]
|
||||
public async Task UsesValueFactory(string[] factoryValues,
|
||||
string[] expectedValues)
|
||||
{
|
||||
// Arrange
|
||||
string receivedName = null;
|
||||
HttpContext receivedContext = null;
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry
|
||||
{
|
||||
DefaultValue = "no",
|
||||
ValueFactory = (name, ctx) =>
|
||||
{
|
||||
receivedName = name;
|
||||
receivedContext = ctx;
|
||||
return factoryValues;
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal(expectedValues, State.Headers["in"]);
|
||||
Assert.Equal("in", receivedName);
|
||||
Assert.Same(Context, receivedContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreferValueFactory_OverDefaultValuesAndRequestHeader()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry
|
||||
{
|
||||
DefaultValue = "no",
|
||||
ValueFactory = (name, ctx) => "test"
|
||||
});
|
||||
Context.Request.Headers.Add("in", "no");
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal("test", State.Headers["in"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyValuesFromValueFactory_DoesNotAddIt()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", new HeaderPropagationEntry
|
||||
{
|
||||
ValueFactory = (name, ctx) => StringValues.Empty
|
||||
});
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("in", State.Headers.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullEntryInConfiguration_HeaderInRequest_AddsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", null);
|
||||
Context.Request.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("in", State.Headers.Keys);
|
||||
Assert.Equal(new[] { "test" }, State.Headers["in"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullEntryInConfiguration_NoHeaderInRequest_DoesNotAddHeader()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", null);
|
||||
|
||||
// Act
|
||||
await Middleware.Invoke(Context);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("in", State.Headers.Keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<Reference Include="Microsoft.AspNetCore.TestHost" />
|
||||
<Reference Include="Microsoft.AspNetCore.HeaderPropagation" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -269,6 +269,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaSer
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices.Tests", "NodeServices\test\Microsoft.AspNetCore.NodeServices.Tests.csproj", "{B04E9CB6-0D1C-4C21-B626-89B6926A491F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HeaderPropagation", "HeaderPropagation", "{0437D207-864E-429C-92B4-9D08D290188C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HeaderPropagation", "HeaderPropagation\src\Microsoft.AspNetCore.HeaderPropagation.csproj", "{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HeaderPropagation.Tests", "HeaderPropagation\test\Microsoft.AspNetCore.HeaderPropagation.Tests.csproj", "{7E18FA09-5E08-4E41-836F-25C94B60C608}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8CDBD9C6-96D8-4987-AFCD-D248FBC7F02D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -1479,6 +1487,30 @@ Global
|
|||
{B04E9CB6-0D1C-4C21-B626-89B6926A491F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B04E9CB6-0D1C-4C21-B626-89B6926A491F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B04E9CB6-0D1C-4C21-B626-89B6926A491F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -1595,6 +1627,9 @@ Global
|
|||
{D9D02772-1D53-45C3-B2CC-888F9978958C} = {D6FA4ABE-E685-4EDD-8B06-D8777E76B472}
|
||||
{5D5B7E54-9323-498A-8983-E9BDFA3B2D07} = {D6FA4ABE-E685-4EDD-8B06-D8777E76B472}
|
||||
{B04E9CB6-0D1C-4C21-B626-89B6926A491F} = {17B409B3-7EC6-49D8-847E-CFAA319E01B5}
|
||||
{D66BD4A3-DA19-413B-8FC5-4BCCFB03E084} = {0437D207-864E-429C-92B4-9D08D290188C}
|
||||
{7E18FA09-5E08-4E41-836F-25C94B60C608} = {8CDBD9C6-96D8-4987-AFCD-D248FBC7F02D}
|
||||
{8CDBD9C6-96D8-4987-AFCD-D248FBC7F02D} = {0437D207-864E-429C-92B4-9D08D290188C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA}
|
||||
|
|
|
|||
Loading…
Reference in New Issue