0
votes

I am working on Proactive message for a BOT hosted in IIS and registered in Azure as a BOT Channel registration BOT.

I am getting below error,

Message: Operation returned an invalid status code 'Unauthorized'

StackTrace : at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Bot.Connector.ConversationsExtensions.d__17.MoveNext()

The code is as given below,

-- Notify.cs

public class Notify
    {
        private readonly IBotFrameworkHttpAdapter _adapter;
        private readonly string _appId;
        private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;

        public Notify(IBotFrameworkHttpAdapter adapter, string appId, ConcurrentDictionary<string, ConversationReference> conversationReferences)
        {
            _adapter = adapter;
            _appId = appId;
            _conversationReferences = conversationReferences;
         }

        public async Task<IActionResult> Get()
        {
            try
            {
                foreach (var conversationReference in _conversationReferences.Values)
                {
                    await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
                }

                // Let the caller know proactive messages have been sent
                return new ContentResult()
                {
                    Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
                    ContentType = "text/html",
                    StatusCode = (int)HttpStatusCode.OK,
                };
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }

        private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            await turnContext.SendActivityAsync("proactive hello");
        }
    }

Edit 1 :

I am trying to send the proactive message to the bot displayed in the systray icon. We have created a windows application through which we are sending a windows notification to users. When the user clicks on the notification it opens the bot present in sys tray icon and it should display the same message notification message in BOT as a proactive message.

We are using direct line API to create the conversation reference. We are passing the same conversation reference and using ContinueConversationAsync of BotApadater to populate the same message from the opened bot. The entire code of form.cs and Notify.cs is return in the windows application and the bot is hosted on IIS and registered in Azure as a BOT Channel registration BOT.

Currently, we are not getting Unauthorized error and code is executed successfully, but we are not getting any proactive message from the bot. For opening the bot we are using iframe URL in windows application.

Kindly help what I am doing wrong here or suggest some other ways.

Window systray application Form.cs Method

using (HttpClient httpClient = new HttpClient())
            {
                httpClient.BaseAddress = new Uri("https://directline.botframework.com/api/tokens");
                httpClient.DefaultRequestHeaders.Accept.Clear();
                //httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("BotConnector", "Directline secret");     
                HttpResponseMessage response = httpClient.GetAsync(httpClient.BaseAddress).Result;


                if (response.IsSuccessStatusCode)
                {
                    var jsonString = response.Content.ReadAsStringAsync();
                    var token = jsonString.Result;
                    var finaltoken = token.Replace(@"\", string.Empty);
                    finaltoken = finaltoken.Replace('"', ' ').Trim();

                    HttpClient client = new HttpClient();
                    client.BaseAddress = new Uri("https://directline.botframework.com/v3/directline/conversations");
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(finaltoken);
                    var httptokenResponse = httpClient.PostAsync("/api/conversations/", new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"));
                    var tokenResponse = httptokenResponse.Result;
                    string convId = string.Empty;
                    string activityId = string.Empty;
                    HttpClient finalclient = new HttpClient();
                    Microsoft.Bot.Schema.Activity act = new Microsoft.Bot.Schema.Activity();
                    ConversationReference convRef = new ConversationReference();
                    if (tokenResponse.IsSuccessStatusCode)
                    {
                        var tokenjsonString = tokenResponse.Content.ReadAsStringAsync().Result;
                        var conversationToken = Newtonsoft.Json.JsonConvert.DeserializeObject<Conversation>(tokenjsonString.ToString());

                        HttpClient lastclient = new HttpClient();
                        lastclient.BaseAddress = new Uri("https://directline.botframework.com/v3/directline/conversations/" + conversationToken.conversationId + "/activities");
                        lastclient.DefaultRequestHeaders.Accept.Clear();
                        lastclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                        lastclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Directline secret");

                        string conversationUrl = conversationToken.conversationId + "/activities/";       
                        string msg2 = "User Message";

                        Rootobject rootobject = new Rootobject();
                        rootobject.type = "message";
                        rootobject.text = msg2;
                        From from = new From();
                        from.id = "User ID";
                        from.user = "User Name";
                        rootobject.from = from;

                        string json = "";
                        json = Newtonsoft.Json.JsonConvert.SerializeObject(rootobject);

                        var httpconvResponse = lastclient.PostAsync(lastclient.BaseAddress, new StringContent(json, System.Text.Encoding.UTF8, "application/json"));
                        var tokenconvResponse = httpconvResponse.Result;

                        if (tokenconvResponse.IsSuccessStatusCode)
                        {
                            var tokenconvjsonString = tokenconvResponse.Content.ReadAsStringAsync().Result;
                            var conversationconvToken = Newtonsoft.Json.JsonConvert.DeserializeObject<ConversationActivityInfo>(tokenconvjsonString.ToString());

                            convId = conversationToken.conversationId;
                            activityId = conversationconvToken.id.Split('|')[1];

                            finalclient.BaseAddress = new Uri("https://directline.botframework.com/v3/directline/conversations/" + conversationToken.conversationId + "/activities");
                            finalclient.DefaultRequestHeaders.Accept.Clear();
                            finalclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                            finalclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Directline Secret");

                            var httpconvfinalResponse = finalclient.GetAsync(finalclient.BaseAddress).Result;      
                            var jsonResponseString = httpconvfinalResponse.Content.ReadAsStringAsync();
                            var finalresponse = jsonResponseString.Result;
                            var activitiesObject = Newtonsoft.Json.JsonConvert.DeserializeObject<ActivitiesRO>(finalresponse);
                            act = activitiesObject.activities[1];

                            convRef = act.GetConversationReference();
            }


                    }
        string MicroSoftAppId = "YOUR Microsoft APP ID";
                    string MicroSoftAppPassword = "YOUR Microsoft APP PASSWORD";
        ICredentialProvider credentialProvider = new SimpleCredentialProvider(MicroSoftAppId, MicroSoftAppPassword);
        MicrosoftAppCredentials.TrustServiceUrl(convRef.ServiceUrl);

                    IBotFrameworkHttpAdapter adapter = new BotFrameworkHttpAdapter(credentialProvider);
                    ConcurrentDictionary<string, ConversationReference> conversationReferences = new ConcurrentDictionary<string, ConversationReference>();

                    convRef.Bot = new ChannelAccount("Bot ID", "Bot Name");

                    conversationReferences.GetOrAdd("test", convRef);

                    Notify notify = new Notify(adapter, MicroSoftAppId, conversationReferences);
                    Task<IActionResult> obj = notify.Get();

            }
        }

**Edit 2: ** Please refer below screenshot, we are getting three activity ids after sending a request to below URL, https://directline.botframework.com/v3/directline/conversations/" + conversationToken.conversationId + "/activities"

Out of which we are using Index1 activity as it contains service URL and user details to whom proactive message needs to send and it does not have BOT details(Bot property), so we are assigning it manually.

enter image description here

1
Regarding your edit: Nothing really stands out except that if you have a valid conversationReference from an existing conversation, you shouldn't need to set the convRef.Bot. If you can provide your appId, I can dig into this a little further.mdrichardson
@mdrichardson Sorry for the delayed response. We have updated our Question and added screenshot and description caption as Edit 2. Please have a look. Thank for your support.Amol Pawar
A couple of things stand out, although I don't know your client app, so this makes it tough to troubleshoot. 1) It doesn't look like your activities have the right schema, especially the from property. 2) Is your client connected via web socket? If not, it has to retrieve the activitiesmdrichardson

1 Answers

0
votes

I believe your issue could be that you're trying to create a ConversationReference for a conversation that doesn't exist (although I can't tell where convoId comes from).

If this is for a brand new conversation, where the user has not interacted with the bot, you can use CreateConversationAsync. You can see how we do it in tests, here:

var activity = new Activity()
{
    Type = ActivityTypes.Message,
    Recipient = User,
    From = Bot,
    Text = "TEST Create Conversation",
};

var param = new ConversationParameters()
{
    Members = new ChannelAccount[] { User },
    Bot = Bot,
    Activity = activity,
};

var convoId = await client.Conversations.CreateConversationAsync(param);

Note: Some clients don't allow proactive messages when a user has never spoken to the bot (mostly email/SMS-related channels)

If the user has interacted with the bot, you can use the Proactive Sample for reference. Particularly, here:

var conversationReference = activity.GetConversationReference();

Alternatively, it's possibly a TrustServiceUrl Issue.

You can fix it by adding your channel's ServiceUrls to the list of trusted URLs:

var serviceUrl = <GetThisFrom Activity.ServiceUrl>;

MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);

Here's the method definition. Note: you can add expiration for this, as well. Otherwise, browsing these issues should help.

It's also possible that your bot is sleeping. You may be able to resolve the issue by going to Azure Portal > Your App Service > Configuration > General Settings > Always On <enable>

Note to others:

This "Trust Service URL Issue" is pretty common. This happens for lots of other URLs when trying to use Proactive messaging. Just replace serviceUrl with whatever is appropriate for your use case. And yes, if you're using multiple channels, you can add multiple URLs when using MicrosoftAppCredentials.TrustServiceUrl() by calling it multiple times.