Skip to content
3 changes: 3 additions & 0 deletions libraries/Microsoft.Bot.Builder.Dialogs/SkillDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ private async Task<Activity> SendToSkillAsync(ITurnContext context, Activity act
await DialogOptions.ConversationState.SaveChangesAsync(context, true, cancellationToken).ConfigureAwait(false);

var skillInfo = DialogOptions.Skill;

DialogOptions.SkillClient.AddDefaultHeaders();

var response = await DialogOptions.SkillClient.PostActivityAsync<ExpectedReplies>(DialogOptions.BotId, skillInfo.AppId, skillInfo.SkillEndpoint, DialogOptions.SkillHostEndpoint, skillConversationId, activity, cancellationToken).ConfigureAwait(false);

// Inspect the skill response status
Expand Down
2 changes: 1 addition & 1 deletion libraries/Microsoft.Bot.Builder/CloudAdapterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ protected virtual ConnectorFactory GetStreamingConnectorFactory(Activity activit
/// <param name="callback">The method to call for the resulting bot turn.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, Activity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken)
protected virtual async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, Activity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken)
{
Logger.LogInformation($"ProcessProactiveAsync for Conversation Id: {continuationActivity.Conversation.Id}");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ public async override Task<InvokeResponse<T>> PostActivityAsync<T>(string fromBo
}
}

public override void AddDefaultHeaders()
{
ConnectorClient.AddDefaultRequestHeaders(_httpClient);
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public async virtual Task<InvokeResponse> PostActivityAsync(string fromBotId, st
/// <returns>Async task with optional invokeResponse.</returns>
public abstract Task<InvokeResponse<T>> PostActivityAsync<T>(string fromBotId, string toBotId, Uri toUrl, Uri serviceUrl, string conversationId, Activity activity, CancellationToken cancellationToken = default);

/// <summary>
/// Allows to add default headers to the HTTP client after the creation of the instance.
/// </summary>
public virtual void AddDefaultHeaders()
{
}

/// <inheritdoc/>
public void Dispose()
{
Expand Down
13 changes: 13 additions & 0 deletions libraries/Microsoft.Bot.Connector/ConnectorClientEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ public static void AddDefaultRequestHeaders(HttpClient httpClient)
}
}

var headersToPropagate = HeaderPropagation.HeadersToPropagate;

if (headersToPropagate != null && headersToPropagate.Count > 0)
{
foreach (var header in headersToPropagate)
{
if (!httpClient.DefaultRequestHeaders.Contains(header.Key))
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToArray());
}
}
}

httpClient.DefaultRequestHeaders.ExpectContinue = false;

var jsonAcceptHeader = new MediaTypeWithQualityHeaderValue("*/*");
Expand Down
88 changes: 88 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Class to handle header propagation from incoming request to outgoing request.
/// </summary>
public static class HeaderPropagation
{
private static readonly AsyncLocal<IDictionary<string, StringValues>> _requestHeaders = new ();

private static readonly AsyncLocal<IDictionary<string, StringValues>> _headersToPropagate = new ();

/// <summary>
/// Gets or sets the headers from an incoming request.
/// </summary>
/// <value>The headers from an incoming request.</value>
public static IDictionary<string, StringValues> RequestHeaders
{
get => _requestHeaders.Value ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
set => _requestHeaders.Value = value;
}

/// <summary>
/// Gets or sets the selected headers for propagation.
/// </summary>
/// <value>The selected headers for propagation.</value>
public static IDictionary<string, StringValues> HeadersToPropagate
{
get => _headersToPropagate.Value ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
set => _headersToPropagate.Value = value;
}

/// <summary>
/// Filters the request's headers to include only those relevant for propagation.
/// </summary>
/// <param name="headerFilter">The chosen headers to propagate.</param>
/// <returns>The filtered headers.</returns>
public static IDictionary<string, StringValues> FilterHeaders(HeaderPropagationEntryCollection headerFilter)
{
// We propagate the X-Ms-Correlation-Id header by default.
headerFilter.Propagate("X-Ms-Correlation-Id");

var filteredHeaders = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);

foreach (var filter in headerFilter.Entries)
{
if (RequestHeaders.TryGetValue(filter.Key, out var value))
{
switch (filter.Action)
{
case HeaderPropagationEntryAction.Add:
break;
case HeaderPropagationEntryAction.Append:
filteredHeaders[filter.Key] = StringValues.Concat(value, filter.Value);
break;
case HeaderPropagationEntryAction.Override:
filteredHeaders.Add(filter.Key, filter.Value);
break;
case HeaderPropagationEntryAction.Propagate:
filteredHeaders.Add(filter.Key, value);
break;
}
}
else
{
switch (filter.Action)
{
case HeaderPropagationEntryAction.Add:
filteredHeaders.Add(filter.Key, filter.Value);
break;
case HeaderPropagationEntryAction.Override:
filteredHeaders.Add(filter.Key, filter.Value);
break;
}
}
}

return filteredHeaders;
}
}
}
62 changes: 62 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagationEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Runtime.Serialization;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Represents the action to perform with the header entry.
/// </summary>
public enum HeaderPropagationEntryAction
{
/// <summary>
/// Adds a new header entry to the outgoing request.
/// </summary>
[EnumMember(Value = "add")]
Add,

/// <summary>
/// Appends a new header value to an existing key in the outgoing request.
/// </summary>
[EnumMember(Value = "append")]
Append,

/// <summary>
/// Propagates the header entry from the incoming request to the outgoing request without modifications.
/// </summary>
[EnumMember(Value = "propagate")]
Propagate,

/// <summary>
/// Overrides an existing header entry in the outgoing request.
/// </summary>
[EnumMember(Value = "override")]
Override
}

/// <summary>
/// Represents a single header entry used for header propagation.
/// </summary>
public class HeaderPropagationEntry
{
/// <summary>
/// Gets or sets the key of the header entry.
/// </summary>
/// <value>Key of the header entry.</value>
public string Key { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the value of the header entry.
/// </summary>
/// <value>Value of the header entry.</value>
public StringValues Value { get; set; } = new StringValues(string.Empty);

/// <summary>
/// Gets or sets the action of the header entry (Add, Append, Override or Propagate).
/// </summary>
/// <value>Action of the header entry.</value>
public HeaderPropagationEntryAction Action { get; set; } = HeaderPropagationEntryAction.Propagate;
}
}
102 changes: 102 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagationEntryCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Represents a collection of the header entries configured to be propagated to outgoing requests.
/// </summary>
public class HeaderPropagationEntryCollection
{
private readonly Dictionary<string, HeaderPropagationEntry> _entries = new (StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Gets the collection of header entries to be propagated to outgoing requests.
/// </summary>
/// <value>The collection of header entries.</value>
public List<HeaderPropagationEntry> Entries => _entries.Values.ToList();

/// <summary>
/// Attempts to add a new header entry to the collection.
/// </summary>
/// <remarks>
/// If the key already exists, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value to add for the specified key.</param>
public void Add(string key, StringValues value)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = value,
Action = HeaderPropagationEntryAction.Add
};
}

/// <summary>
/// Appends a new header value to an existing key.
/// </summary>
/// <remarks>
/// If the key does not exist, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to append the value.</param>
/// <param name="value">The value to append for the specified key.</param>
public void Append(string key, StringValues value)
{
StringValues newValue;

if (_entries.TryGetValue(key, out var entry))
{
// If the key already exists, append the new value to the existing one.
newValue = StringValues.Concat(entry.Value, value);
}

_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = !StringValues.IsNullOrEmpty(newValue) ? newValue : value,
Action = HeaderPropagationEntryAction.Append
};
}

/// <summary>
/// Propagates the incoming request header value to outgoing requests without modifications.
/// </summary>
/// <remarks>
/// If the key does not exist, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to propagate.</param>
public void Propagate(string key)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Action = HeaderPropagationEntryAction.Propagate
};
}

/// <summary>
/// Overrides the header value of an existing key.
/// </summary>
/// <remarks>
/// If the key does not exist, it will add it.
/// </remarks>
/// <param name="key">The key of the element to override.</param>
/// <param name="value">The value to override in the specified key.</param>
public void Override(string key, StringValues value)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = value,
Action = HeaderPropagationEntryAction.Override
};
}
}
}
31 changes: 31 additions & 0 deletions libraries/Microsoft.Bot.Connector/Teams/TeamsHeaderPropagation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector.Teams
{
/// <summary>
/// Instantiate this class to set the headers to propagate from incoming request to outgoing request.
/// </summary>
public static class TeamsHeaderPropagation
{
/// <summary>
/// Returns the headers to propagate from incoming request to outgoing request.
/// </summary>
/// <returns>The collection of headers to propagate.</returns>
public static HeaderPropagationEntryCollection GetHeadersToPropagate()
{
// Propagate headers to the outgoing request by adding them to the HeaderPropagationEntryCollection.
// For example:
var headersToPropagate = new HeaderPropagationEntryCollection();

//headersToPropagate.Propagate("X-Ms-Teams-Id");
//headersToPropagate.Add("X-Ms-Teams-Custom", new StringValues("Custom-Value"));
//headersToPropagate.Append("X-Ms-Teams-Channel", new StringValues("-SubChannel-Id"));
//headersToPropagate.Override("X-Ms-Other", new StringValues("new-value"));

return headersToPropagate;
}
}
}
Loading