简体   繁体   中英

Azure Bot project with client and server in same web app

I've made an Azure bot application using the BotFramework v4 and used the WebChat control as an interface. I noticed that the bot server's dotnetcore app had a wwwroot folder with a placeholder HTML page in it, so thought it might be expedient to host the webchat client there. But now seems counter-intuitive that my webchat client is using DirectLine to send activities back to the same back-end that served it.

I had chosen the webchat client because I need to customise the appearance of the client. I also need the MVC app that serves the bot client to include Azure Active Directory B2C authentication (which it does). Users should see the webchat client before and after authentication but the bot back-end (handling the activities) needs to know whether the user is logged in and modify its behaviour accordingly (and I am struggling to achieve that part with DirectLine).

So my first question (ever on StackOverflow) is: With the Bot back-end and the webchat client front-end being hosted in the same, single Azure web app, is it necessary to use DirectLine, or is there a simpler way of doing this?

Relevant code in my Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    _loggerFactory = loggerFactory;

    app.UseStaticFiles(); // to allow serving up the JS, CSS, etc., files.
    app.UseBotFramework(); // to add middleware to route webchat activity to the bot back-end code
    app.UseSession(); // to enable session state
    app.UseAuthentication(); // to enable authentication (in this case AAD B2C)
    app.UseMvcWithDefaultRoute(); // to add MVC middleware with default route
}

Also in Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {

// standard code to add HttpContextAssessor, BotServices, BotConfigs and memory storage singletons ommitted for brevity ...

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
        .AddCookie();

        services.AddMvc();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromHours(1);
            options.CookieHttpOnly = true;
        });


        // Create and add conversation state.
        var conversationState = new ConversationState(dataStore);
        services.AddSingleton(conversationState);

        var userState = new UserState(dataStore);
        services.AddSingleton(userState);

        services.AddBot<MyBot>(options =>
        {
            options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
            options.ChannelProvider = new ConfigurationChannelProvider(Configuration);

            // Catches any errors that occur during a conversation turn and logs them to currently
            // configured ILogger.
            ILogger logger = _loggerFactory.CreateLogger<RucheBot>();
            options.OnTurnError = async (context, exception) =>
            {
                logger.LogError($"Exception caught : {exception}");
                await context.SendActivityAsync("Sorry, it looks like something went wrong.");
            };
        });

    }

My controller's Index method:

    public async Task<ActionResult> Index()
    {
        string userId;
        if (User.Identity.IsAuthenticated)
        {
            string aadb2cUserId = User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
            Users.EnsureAccountExists(aadb2cUserId); // ensure account with given AAD identifier is know locally (by creating it if not)
            userId = $"ia_{aadb2cUserId}";
        }
        else
        {
            userId = $"na_{Guid.NewGuid()}";
        }


        HttpClient client = new HttpClient();
        string directLineUrl = $"https://directline.botframework.com/v3/directline/tokens/generate";
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, directLineUrl);

        // TODO: put this in the config somewhere
        var secret = "<the secret code from my bot's DirectLine channel config in the Azure portal>";
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secret);
        string jsonUser = JsonConvert.SerializeObject(new { User = new { Id = userId } });
        request.Content = new StringContent(jsonUser, Encoding.UTF8, "application/json");
        var response = await client.SendAsync(request);

        string token = string.Empty;

        if (response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync();
            token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
        }

        var config = new ChatConfig()
        {
            Token = token,
            UserId = userId,
        };

        return View(config);
    }

And finally the code in the associated view:

<div id="webchat"></div>
<script type="text/javascript">
...
        /// Called asynchronously during the page load
        function renderWebChat( withSound )
        {
            var webchatOptions =
            {
                directLine: window.WebChat.createDirectLine( { secret: '@Model.Token'} ),
                userID: '@Model.UserId'
            };

            if ( withSound )
            {
                webchatOptions.webSpeechPonyfillFactory = window.WebChat.createBrowserWebSpeechPonyfillFactory();
            }

            window.WebChat.renderWebChat( webchatOptions, document.getElementById( 'webchat' ) );

            document.querySelector( '#webchat > *' ).focus();
        }

</script>

Long question, but the answer will be a lot shorter!

So my first question (ever on StackOverflow) is: With the Bot back-end and the webchat client front-end being hosted in the same, single Azure web app, is it necessary to use DirectLine, or is there a simpler way of doing this?

Yes, it is necessary. In fact, all the channels types are using the Bot Connector to communicate with your backend (your bot code), there is no direct access possible. There are a lot of reasons for that, one is for example the billing!

I'm going to disagree with Nicolas R a little bit. When it comes to directly accessing your bot, you might like to have a look at this: https://www.npmjs.com/package/offline-directline

There's also the option of hosting a bot in the browser , which I think may facilitate the sort of direct communication you're looking for.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM