Botbuilder-dotnet: Lack of proactive messaging documentation

Created on 27 Mar 2018  路  14Comments  路  Source: microsoft/botbuilder-dotnet

Working with a partner in Tokyo and we are implementing proactive messaging for our Agent Hand-off scenario. There is no documentation to follow and so the partner is working in the wild going through the source code :)

Most helpful comment

@SarangRapid

When 'Unauthorized' occurs during the sending of a proactive message, it can usually be fixed with a call to: MicrosoftAppCredentials.TrustServiceUrl(context.Activity.ServiceUrl);

So, the .ContinueConversation code becomes:

        await adapter.ContinueConversation(appId, reference, async context =>
        {
            MicrosoftAppCredentials.TrustServiceUrl(context.Activity.ServiceUrl);
            await Task.Delay(2000);
            await context.SendActivity("You've been notified!");
        });

All 14 comments

@ovishesh , There is an open issue (#314), and the design is in flux, so the documentation was taken down. However, in the next release of the NuGet packages, the following pattern should work for a deployed bot.

In your bot or middleware code:

var reference = TurnContext.GetConversationReference(context.Activity);
var adapter = (context as BotFrameworkTurnContext)?.Adapter as BotFrameworkAdapter;
SubscribeUser(adapter, (context as BotFrameworkTurnContext)?.BotAppId, reference);

Depending on how your implementing proactive messaging, you will probably need to save the conversation reference for later use:

private async void SubscribeUser(BotFrameworkAdapter adapter, string appId, ConversationReference reference)
{
    await adapter.ContinueConversation(appId, reference,
        async (ITurnContext context) =>
        {
            await Task.Delay(2000);
            await context.SendActivity("You've been notified!");
        });
}

Let me know if this helps.

Thanks @JonathanFingold. So does that mean that proactive messaging is broken for this release?

This is what I have at the moment:

SubscribeUser(context.Adapter, GetConversationReference(context.Request));

      private async void SubscribeUser(BotAdapter adapter, string appId, ConversationReference reference)
        {
            await adapter.ContinueConversation(reference,
                async (IBotContext context) =>
                {
                    await Task.Delay(2000);
                    await context.SendActivity("You've been notified!");
                });
        }

But then that gives me a StackOverflowException, is that somewhat related?

The error details:

System.StackOverflowException
  HResult=0x800703E9
  Source=<Cannot evaluate the exception source>
  StackTrace:
<Cannot evaluate the exception stack trace>
Message Evaluation of method System.Exception.get_Message requires calling method System.Reflection.RuntimeMethodInfo.CreateDelegate, which cannot be called in this context.   string

Adding to that, seems like this GetPostToBotMessage() is being recursively called.

@ovishesh , Proactive messaging is partly broken in that you need an app ID and password for your bot.

Error for testing locally without credentials for the bot:

Unhandled Exception: System.ArgumentNullException: Value cannot be null.
Parameter name: botAppId
   at Microsoft.Bot.Builder.Adapters.BotFrameworkAdapter.ContinueConversation(String botAppId, ConversationReference reference, Func`2 callback)
   at Microsoft.Bot.Samples.ProactiveBot.<SubscribeUser>d__1.MoveNext() in ...\ProactiveMessaging\ProactiveBot.cs

Here are some additional steps (and more complete information). Note that I'm using the ASP.NET Core "integration" library to write the bot:

  1. Build the libraries locally, and include references to the following projects or assemblies:
    Microsoft.Bot.Builder
    Microsoft.Bot.Builder.Core
    Microsoft.Bot.Builder.Core.Extensions
    Microsoft.Bot.Builder.Integration.AspNet.Core
    Microsoft.Bot.Connector
    Microsoft.Bot.Schema
  2. Install the following NuGet package
    Microsoft.IdentityModel.Protocols.OpenIdConnect
  3. Replace the class definition in your Startup.cs file:
    csharp using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Bot.Builder.BotFramework; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection;
    ```csharp
    public class Startup
    {
    public IConfiguration Configuration { get; }
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(_ => Configuration);
        services.AddBot<ProactiveBot>(options =>
        {
            options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
            options.EnableProactiveMessages = true;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseBotFramework();
    }
}
```

  1. Define your bot (I've called mine ProactiveBot):
    csharp using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Adapters; using Microsoft.Bot.Schema; using System.Threading.Tasks;
    ```csharp
    public class ProactiveBot : IBot
    {
    public Task OnReceiveActivity(ITurnContext context)
    {
    if (context.Activity.Type is ActivityTypes.Message)
    {
    var messageText = context.Activity.AsMessageActivity()?.Text;
    if (messageText?.Trim().ToLowerInvariant() is "subscribe")
    {
    // Confirm the request.
    context.SendActivity("Thank You! We will message you shortly.");
                // Queue the proactive message.
                var reference = TurnContext.GetConversationReference(context.Activity);
                SubscribeUser((BotFrameworkAdapter)context.Adapter,
                    ((BotFrameworkTurnContext)context).BotAppId, reference);
            }
            else
            {
                // Prompt the user to subscribe.
                context.SendActivity("Type `subscribe` to receive a proactive message.");
            }
        }
        return Task.CompletedTask;
    }

    private async void SubscribeUser(BotFrameworkAdapter adapter, string appId, ConversationReference reference)
    {
        // Send a proactive message.
        await adapter.ContinueConversation(appId, reference, async context =>
        {
            await Task.Delay(2000);
            await context.SendActivity("You've been notified!");
        });
    }
}
```

PS. GetPostToUserMessage calls GetPostToBotMessage and then swaps the From and Recipient fields. It might look like a recursive call at first, but they are two separate methods.

Merges from last night changed how to get the bot's App ID from within the bot.

In the bot's OnReceiveActivity method, update the block of code to "queue the proactive message" to the following (and update your using statements as necessary:

// Queue the proactive message.
var reference = TurnContext.GetConversationReference(context.Activity);

var claimsIdentity = context.Services.Get<IIdentity>("BotIdentity") as ClaimsIdentity;

// For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For 
// unauthenticated requests we have anonymouse identity provided auth is disabled.
// For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
var botAppIdClaim =
    (claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)
    ?? claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AppIdClaim));

SubscribeUser((BotFrameworkAdapter)context.Adapter, botAppIdClaim.Value, reference);

Thanks @JonathanFingold. I'll pull the latest and try it out. When do you these will be pushed out to Nuget?

@ovishesh , I was hoping for a NuGet package last week, I'm not positive, but I'm guessing the next will be within two weeks. There is still a lot of refactoring going on. Which also means that my sample code is already out of date.

You can collect NuGet packages from your local build and have a stable set to work from.

I'm closing this for now, but let me know if you need any more assistance.

Great, thanks!

@JonathanFingold : we have requirement for proactive messaging, where we are going to proactively send some more business information to users through web API calls.

current implementation is as below

  1. we are saving conversation id, user id, from id and some other details in azure storage.
private UserEntity FillUserEntity(Activity activity, UserProfile userProfile)
        {
            string[] issuerSplits = userProfile.Issuer.Split("https://sts.windows.net/");
            if (issuerSplits != null && issuerSplits.Length > 1)
            {
                string tenantId = (issuerSplits[1].EndsWith('/')) ? issuerSplits[1].TrimEnd('/') : issuerSplits[1];
                UserEntity userEntity = new UserEntity(tenantId, $"{userProfile.UPN}-{activity.ChannelId}");
                userEntity.AadObjectId = activity.From.AadObjectId;
                userEntity.FromName = activity.From.Name;
                userEntity.FromId = activity.From.Id;
                userEntity.ConversationId = activity.Conversation.Id;
                userEntity.ChannelId = activity.ChannelId;
                userEntity.ServiceUrl = activity.ServiceUrl;
                userEntity.Locale = activity.Locale;
                userEntity.RecipientId = "Bot-id";
                userEntity.RecipientRole = activity.Recipient.Role;
                return userEntity;
            }

            return null;
        }
  1. We are creating input JSON payload, based on table properties to send proactive message.
ProactiveMessage proactiveMessage = new ProactiveMessage()
 {
         BotId = queryResult.RecipientId,
         MessagePayload = activityGeneratorInputModel.MessagePayload,
         Conversation = new Microsoft.Bot.Schema.ConversationReference()
           {
               Bot = new Microsoft.Bot.Schema.ChannelAccount() { Id = queryResult.RecipientId, Name = "Bot", Role = "bot" },
               User = new Microsoft.Bot.Schema.ChannelAccount() { Id = queryResult.FromId, Name = "user" },
               Conversation = new Microsoft.Bot.Schema.ConversationAccount() { Id = queryResult.ConversationId },
               ServiceUrl = queryResult.ServiceUrl,
               ChannelId = queryResult.ChannelId
          }
 }

  1. output JSON is sent to ProactiveController, to prompt message to Microsoft Teams.
        {
        await botFrameworkAdapter.ContinueConversationAsync(proMsg.BotId, proMsg.Conversation, async (context, token) =>
             {
                await context.SendActivityAsync(proMsg.MessagePayload);
             }, new CancellationToken());
        }
       catch { }
  1. Entire functionality flow works fine, but when user is idle for few minutes suddenly bot in teams stops to respond to proactive messages.

  2. Post investigation we found the issue, details below.

Operation returned an invalid status code 'Unauthorized'

@SarangRapid Can you raise this with the support team at Stack Overflow using the botframework tag?

@SarangRapid

When 'Unauthorized' occurs during the sending of a proactive message, it can usually be fixed with a call to: MicrosoftAppCredentials.TrustServiceUrl(context.Activity.ServiceUrl);

So, the .ContinueConversation code becomes:

        await adapter.ContinueConversation(appId, reference, async context =>
        {
            MicrosoftAppCredentials.TrustServiceUrl(context.Activity.ServiceUrl);
            await Task.Delay(2000);
            await context.SendActivity("You've been notified!");
        });

it worked, thanks @EricDahlvang

FYI, part of our team built some fantastic samples/walkthrough for more complicated proactive messaging scenarios - check it out to see if it would have satisfied your issues! https://github.com/lucashuet93/botbuilder-proactivemessaging

Was this page helpful?
0 / 5 - 0 ratings