Skip to content

Add header propagation functionality #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

sw-joelmut
Copy link
Collaborator

@sw-joelmut sw-joelmut commented Mar 21, 2025

Description

This PR adds header propagation functionality, allowing to choose which headers to transition from incoming to outgoing requests.
This implementation uses an Http middleware, a context to share values, and the options to choose which headers to propagate.
Additionally, it will also work with hosted services, as it registers the new request headers into AsyncLocal for the current context by passing the headers via ActivityWithClaims.

How to use it

Most of the propagation configuration is done internally, so, a way to use this feature is adding the UseHeaderPropagation method call to the application builder as follows in the Program.cs file.

var app = builder.Build();
app.UseHeaderPropagation();

How it knows which headers to propagate

The header propagation functionality exposes a way to set which headers to propagate by using C# Attributes. Whenever a class uses the [HeaderPropagation] attribute, it will be automatically loaded into the header propagation context.
This functionality provides a collection to Add, Override, Append, and Propagate incoming request headers o outgoing ones.

Here is an example:

[HeaderPropagation]
internal class HeaderPropagation : IHeaderPropagationAttribute
{
    public static void LoadHeaders(HeaderPropagationEntryCollection collection)
    {
        // Propagate headers to the outgoing request by adding them to the HeaderPropagationEntryCollection.
    }
}

Detailed Changes

  • Added HeaderPropagation folder containing all classes of this functionality.
    • Added HeaderPropagationMiddleware which will be used to intercept any incoming request, assigning the headers into a context for later use.
    • Added HeaderPropagationContext which is in charge of maintaining the headers over the current asynchronous control flow using AsyncLocal, also, it filters which headers have to save for the propagation by looking at the options configured in the middleware configuration.
  • Added UseHeaderPropagation to the application builder extension that lets a customer enable the header propagation functionality.
  • Added AddHeaderPropagation as a private application host extension to register the header propagation related classes.
  • Added to the HostedActivityService the ability to read the headers from another http context by obtaining the headers from the ActivityWithClaims instance and assigning them to the new header propagation context.

Note

Working with Skills:
To propagate headers coming from a Root bot, the Skill will also need to register the middleware via UseHeaderPropagation.

Testing

The following image shows a testing header being propagated.
image

@sw-joelmut sw-joelmut requested a review from tracyboehrer March 21, 2025 10:07
@github-actions github-actions bot added ML: Core Tags changes to core libraries ML: Tests Tags changes to tests labels Mar 21, 2025
@tracyboehrer
Copy link
Member

@sw-joelmut Oh nice. I have a little POC branch, but I like how this uses existing features. I'll take a look.

@tracyboehrer tracyboehrer added the Investigating Issue is being investigated label Mar 26, 2025
@sw-joelmut sw-joelmut marked this pull request as draft April 9, 2025 09:53
@sw-joelmut
Copy link
Collaborator Author

sw-joelmut commented Apr 9, 2025

Hi @tracyboehrer, we added the C# Attributes to set which headers require propagation to outgoing requests. The implementation is working when we tested it, but after merging with the latest main changes, things changed and may not work for some scenarios (we suspect Skills).

We marked this PR as draft as it is not fully ready to merge with main, and we wanted to know if you like this new approach better, so we can do the final touches and ensure it is working correctly.

Let us know if there are some improvements that we could apply, thanks!

Note

The app.UseHeaderPropagation() middleware will still be required in the bot, but without parameters, as it is used to capture all incoming request headers.

@sw-joelmut
Copy link
Collaborator Author

Hi @tracyboehrer, we pushed the latest improvements for setting the header to propagate easier, and updated the PR's description to include attributes.

@sw-joelmut sw-joelmut marked this pull request as ready for review April 25, 2025 14:01
@Copilot Copilot AI review requested due to automatic review settings April 25, 2025 14:01
@sw-joelmut sw-joelmut requested a review from a team as a code owner April 25, 2025 14:01
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces header propagation functionality by adding middleware to capture incoming request headers and propagate them to outgoing requests.

  • Updates test mocks to include header parameters.
  • Implements the UseHeaderPropagation extension and associated middleware.
  • Adds core header propagation support with new context and attribute-based configuration.

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs Updated unit tests to adjust for the new header parameter.
src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs Added UseHeaderPropagation extension to register the middleware.
src/libraries/Hosting/AspNetCore/HeaderPropagationMiddleware.cs New middleware for capturing headers from incoming requests.
src/libraries/Hosting/AspNetCore/CloudAdapter.cs Updated to propagate headers via updated activity queue.
src/libraries/Hosting/AspNetCore/BackgroundQueue/* Modified QueueBackgroundActivity signature and header copy handling.
src/libraries/Core/Microsoft.Agents.Core/HeaderPropagation/* Added core header propagation types and attribute processing.
src/libraries/Client/* Updated clients to include header propagation when creating HttpClient.

{
ArgumentNullException.ThrowIfNull(claimsIdentity);
ArgumentNullException.ThrowIfNull(activity);

// Copy to prevent unexpected side effects from later mutations of the original headers.
var copyHeaders = headers != null ? new HeaderDictionary(headers.ToDictionary()) : [];
Copy link
Preview

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback using '[]' when headers is null may cause a type mismatch. Consider creating an empty HeaderDictionary instead, e.g. 'new HeaderDictionary()'.

Suggested change
var copyHeaders = headers != null ? new HeaderDictionary(headers.ToDictionary()) : [];
var copyHeaders = headers != null ? new HeaderDictionary(headers.ToDictionary()) : new HeaderDictionary();

Copilot uses AI. Check for mistakes.


foreach (var header in HeaderPropagationContext.HeadersFromRequest)
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, [header.Value]);
Copy link
Preview

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing '[header.Value]' wraps header.Value in an array, which may not match the expected type. It is likely more appropriate to pass 'header.Value' directly.

Suggested change
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, [header.Value]);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);

Copilot uses AI. Check for mistakes.

Comment on lines +46 to +47
loadHeaders.Invoke(assembly, [HeaderPropagationContext.HeadersToPropagate]);
}
Copy link
Preview

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When invoking a static method via reflection, the instance parameter should be 'null' rather than 'assembly'. Use 'loadHeaders.Invoke(null, new object[]{ HeaderPropagationContext.HeadersToPropagate })'.

Suggested change
loadHeaders.Invoke(assembly, [HeaderPropagationContext.HeadersToPropagate]);
}
loadHeaders.Invoke(null, new object[] { HeaderPropagationContext.HeadersToPropagate });

Copilot uses AI. Check for mistakes.

@tracyboehrer
Copy link
Member

@sw-joelmut Getting back to this. I'll let you know.

# Conflicts:
#	src/libraries/Client/Microsoft.Agents.Client/HttpAgentClient.cs
@github-actions github-actions bot added the ML: Samples Tags changes to samples label May 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Investigating Issue is being investigated ML: Core Tags changes to core libraries ML: Samples Tags changes to samples ML: Tests Tags changes to tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants