BotBuilder-Samples/experimental/immediate-accept-adapter/csharp_dotnetcore
Tracy Boehrer dcf7786d12 Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
..
BackgroundActivityService Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
BackgroundTaskService Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
Bots Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
Controllers R10 samples (#2706) 2020-09-01 09:43:13 -07:00
DeploymentTemplates [Node]Update node version to 16 in ARM templates (#3921) 2023-10-24 15:35:08 -05:00
Properties R10 samples (#2706) 2020-09-01 09:43:13 -07:00
wwwroot R10 samples (#2706) 2020-09-01 09:43:13 -07:00
ImmediateAcceptAdapter.cs Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
ImmediateAcceptBot.csproj Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
Program.cs Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
README.md Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
Startup.cs Updated immediate-accept-adapter to CloudAdapter 2024-05-31 14:25:11 -05:00
appsettings.json R10 samples (#2706) 2020-09-01 09:43:13 -07:00

README.md

Immediate Accept Bot

Built starting from Bot Framework v4 echo bot sample.

This example demonstrates how to create a simple bot that accepts input from the user and echoes it back. All incoming activities are processed on a background thread, alleviating 15 second timeout concerns.

ImmediateAcceptAdapter verifies the authorization header, adds the message to a Microsoft.Extensions.Hosting.BackgroundService (HostedActivityService) for processing, and writes HttpStatusCode.OK to HttpResponse. This causes all messages sent by the bot to effectively be proactive.

Note: This bot demonstrates how to extend in process execution of incoming messages, passed the 15 second timeout. In general, long running processes should be executed by a Web Job or Azure Function and not a web application hosting server.

ImmediateAcceptAdapter.cs

New method in BotFrameworkHttpAdapter implementation:

/// <summary>
/// This method can be called from inside a POST method on any Controller implementation.  If the activity is Not an Invoke, and
/// DeliveryMode is Not ExpectReplies, and this is NOT a Get request to upgrade to WebSockets, then the activity will be enqueued
/// to be processed on a background thread.
/// </summary>
/// <remarks>
/// Note, this is an ImmediateAccept and BackgroundProcessing override of: 
/// Task IBotFrameworkHttpAdapter.ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default);
/// </remarks>
/// <param name="httpRequest">The HTTP request object, typically in a POST handler by a Controller.</param>
/// <param name="httpResponse">The HTTP response object.</param>
/// <param name="bot">The bot implementation.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive
///     notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
public new async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default)
{
    if (httpRequest == null)
    {
        throw new ArgumentNullException(nameof(httpRequest));
    }

    if (httpResponse == null)
    {
        throw new ArgumentNullException(nameof(httpResponse));
    }

    if (bot == null)
    {
        throw new ArgumentNullException(nameof(bot));
    }

    // Get is a socket exchange request, so should be processed by base BotFrameworkHttpAdapter
    if (httpRequest.Method == HttpMethods.Get)
    {
        await base.ProcessAsync(httpRequest, httpResponse, bot, cancellationToken);
    }
    else
    {
        // Deserialize the incoming Activity
        var activity = await HttpHelper.ReadRequestAsync<Activity>(httpRequest).ConfigureAwait(false);

        if (string.IsNullOrEmpty(activity?.Type))
        {
            httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;
        }
        else if (activity.Type == ActivityTypes.Invoke || activity.DeliveryMode == DeliveryModes.ExpectReplies)
        {
            // NOTE: Invoke and ExpectReplies cannot be performed async, the response must be written before the calling thread is released.
            await base.ProcessAsync(httpRequest, httpResponse, bot, cancellationToken);
        }
        else
        {
            // Grab the auth header from the inbound http request
            var authHeader = httpRequest.Headers["Authorization"];

            try
            {
                // If authentication passes, queue a work item to process the inbound activity with the bot
                var authResult = await BotFrameworkAuthentication.AuthenticateRequestAsync(activity, authHeader, cancellationToken).ConfigureAwait(false);

                // Queue the activity to be processed by the ActivityBackgroundService
                _activityTaskQueue.QueueBackgroundActivity(authResult.ClaimsIdentity, activity);

                // Activity has been queued to process, so return Ok immediately
                httpResponse.StatusCode = (int)HttpStatusCode.OK;
            }
            catch (UnauthorizedAccessException)
            {
                // handle unauthorized here as this layer creates the http response
                httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
        }
    }
}

Startup.cs

Register BackgroundServices and classes:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
    });

    // Activity specific BackgroundService for processing athenticated activities.
    services.AddHostedService<HostedActivityService>();
    // Generic BackgroundService for processing tasks.
    services.AddHostedService<HostedTaskService>();
    
    // BackgroundTaskQueue and ActivityTaskQueue are the entry points for
    // the enqueueing activities or tasks to be processed by the BackgroundService.
    services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
    services.AddSingleton<IActivityTaskQueue, ActivityTaskQueue>();

    // Configure the ShutdownTimeout based on appsettings.
    services.Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(Configuration.GetValue<int>("ShutdownTimeoutSeconds")));

    // Create the Bot Framework Authentication to be used with the Bot Adapter.
    services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

    // Create the ImmediateAcceptAdapter.
    // Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance.
    services.AddSingleton<ImmediateAcceptAdapter>();
    services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<ImmediateAcceptAdapter>());
    services.AddSingleton<BotAdapter>(sp => sp.GetService<ImmediateAcceptAdapter>());

    // Create the bot. In this case the ASP Controller and ImmediateAcceptAdapter is expecting an IBot.
    services.AddTransient<IBot, EchoBot>();
}

Interacting with the Bot

send: 4 pause ... and the bot will pause for 4 seconds while processing your message. send: 4 background ... and the bot will push your message to an additional background thread to process for 4 seconds.

Additional Resources

QueuedHostedService is from https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/fundamentals/host/hosted-services/samples/3.x/BackgroundTasksSample