Search code examples
githubcontinuous-integrationbotframeworkazure-web-app-servicewwwroot

Images are not displayed after publishing the bot with continuous deployment


I have developed a bot using Bot Framework. It shows images when I run it locally on the Bot Framework Emulator. But after publishing it on Azure by creating a private GitHub repository, none of the images are displayed. I'm using Direct-Line API 3.0. What could be the reason?

Like, here's the tree-view of my project. The images folder resides in Content Root.

Project Tree View

I'm using the following static method to retrieve an image path.

public static string CreateImagePath(string filename)
    {
        return Path.Combine(TempData.HostingEnvironment?.ContentRootPath, "images", $"{filename}");
    }

And then converting it into a Uri, and assigning it to an Adaptive Card's image. Here's the code snippet.

(AdaptiveCardFactory.CreateAdaptiveElement(card, "image") as AdaptiveImage).Url =
            new Uri(PathFactory.CreateImagePath("welcome_card_image.png"));

CreateAdaptiveElement is defined as:

public static AdaptiveElement CreateAdaptiveElement(AdaptiveCard card, string adaptiveElementId)
    {
        return card.Body.Find(ae => ae.Id == adaptiveElementId);
    }

This is the Uri that is returned when I run the bot locally. it works fine.

file:///C:/Users/Husai/source/repos/PanjaSahibBot/images/welcome_card_image.png

Here's what is returned after publishing.

file:///D:/home/site/wwwroot/images/welcome_card_image.png

And, it's not working.

Here's the latest command log.

    Command: "D:\home\site\deployments\tools\deploy.cmd"
Handling ASP.NET Core Web Application deployment.
  Restoring packages for D:\home\site\repository\GurdwaraBot.csproj...
  Generating MSBuild file D:\home\site\repository\obj\GurdwaraBot.csproj.nuget.g.props.
  Generating MSBuild file D:\home\site\repository\obj\GurdwaraBot.csproj.nuget.g.targets.
  Restore completed in 5.02 sec for D:\home\site\repository\GurdwaraBot.csproj.
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 1.41 sec for D:\home\site\repository\GurdwaraBot.csproj.
  GurdwaraBot -> D:\home\site\repository\bin\Release\netcoreapp2.2\GurdwaraBot.dll
  GurdwaraBot -> D:\local\Temp\8d6da8df6d390f3\
Creating app_offline.htm
KuduSync.NET from: 'D:\local\Temp\8d6da8df6d390f3' to: 'D:\home\site\wwwroot'
Copying file: 'GurdwaraBot.deps.json'
Copying file: 'GurdwaraBot.dll'
Copying file: 'GurdwaraBot.pdb'
Copying file: 'GurdwaraBot.runtimeconfig.json'
Copying file: 'images\01d.png'
Copying file: 'images\01n.png'
Copying file: 'images\02d.png'
Copying file: 'images\02n.png'
Copying file: 'images\03d.png'
Copying file: 'images\03n.png'
Copying file: 'images\04d.png'
Copying file: 'images\04n.png'
Copying file: 'images\09d.png'
Copying file: 'images\09n.png'
Copying file: 'images\1.jpg'
Copying file: 'images\10.jpg'
Copying file: 'images\10d.png'
Copying file: 'images\10n.png'
Copying file: 'images\11d.png'
Copying file: 'images\11n.png'
Copying file: 'images\13d.png'
Copying file: 'images\13n.png'
Copying file: 'images\2.jpg'
Copying file: 'images\3.jpg'
Copying file: 'images\4.jpg'
Copying file: 'images\5.jpg'
Copying file: 'images\50d.png'
Copying file: 'images\50n.png'
Copying file: 'images\6.jpg'
Copying file: 'images\7.jpg'
Copying file: 'images\8.jpg'
Copying file: 'images\9.jpg'
Copying file: 'images\bill.png'
Copying file: 'images\feedback.png'
Copying file: 'images\Festivals.jpg'
Copying file: 'images\festivals_bandi_chhor_divas.jpg'
Copying file: 'images\festivals_gurpurab.jpeg'
Copying file: 'images\festivals_hola_mohalla.jpg'
Copying file: 'images\festivals_maghi.jpg'
Copying file: 'images\festivals_martyrdom_of_guru_arjan.jpg'
Copying file: 'images\festivals_parkash_utsav_dasveh_patshah.jpg'
Copying file: 'images\festivals_vaisakhi.jpg'
Copying file: 'images\menu_card_image.png'
Copying file: 'images\panja_thumbnail_1.png'
Copying file: 'images\panja_thumbnail_2.png'
Copying file: 'images\panja_thumbnail_3.png'
Copying file: 'images\pof_guest_hotel.jpg'
Copying file: 'images\question.png'
Omitting next output lines...
Finished successfully.

This is what I am expecting. Locally executed And, this is what I'm getting via DirectLine!! Actual result

Here's the content of my welcome_card.json

    {
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "Image",
      "id": "image",
      "horizontalAlignment": "Center",
      "style": "Person",
      "url": "",
      "size": "Medium"
    },
    {
      "type": "TextBlock",
      "horizontalAlignment": "Center",
      "size": "Medium",
      "weight": "Bolder",
      "color": "Accent",
      "text": "Welcome to Gurdwara Bot",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "horizontalAlignment": "Left",
      "text": "Hi there! I'm the Sri Panja Sahib's support bot. What can I help you with today? Just so you know, you can enter **MENU** anytime you want to go back to the options below, or just go ahead type your query.",
      "wrap": true
    }
  ],
  "actions": [
    {
      "type": "Action.Submit",
      "title": "Get an Overview",
      "data": {
        "dataId": "overview"
      }
    },
    {
      "type": "Action.Submit",
      "title": "Ask a Question",
      "data": {
        "dataId": "question"
      }
    },
    {
      "type": "Action.Submit",
      "title": "Book a Room",
      "data": {
        "dataId": "room"
      }
    },
    {
      "type": "Action.Submit",
      "title": "Give a Feedback",
      "data": {
        "dataId": "feedback"
      }
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.0",
  "speak": "Hi there! I'm the Sri Panja Sahib's support bot. What can I help you with today? Just so you know, you can enter **MENU** anytime you want to go back to the options below, or just go ahead type your query."
}

I've defined AdaptiveCardFactory to manipulate with cards.

public class AdaptiveCardFactory
{
    public static AdaptiveCard CreateAdaptiveCard(string path)
    {
        try
        {
            var adaptiveCardJson = File.ReadAllText(path);
            AdaptiveCardParseResult result = AdaptiveCard.FromJson(adaptiveCardJson);
             return result.Card;
        }
        catch (AdaptiveSerializationException)
        {
            throw;
        }
    }

    public static Attachment CreateAdaptiveCardAttachment(AdaptiveCard card)
    {
        return new Attachment
        {
            ContentType = "application/vnd.microsoft.card.adaptive",
            Content = card,
        };
    }

    public static AdaptiveElement CreateAdaptiveElement(AdaptiveCard card, string adaptiveElementId)
    {
        return card.Body.Find(ae => ae.Id == adaptiveElementId);
    }
}

Solution

  • TL;DR;

    You must provide public URLs for items displayed in AdaptiveCards, not local ones. It is working in local with the emulator because... you are in local, and the emulator has access to your local files, so it can understand files with local location (C:\... or D:\... files).

    So change your path so that it uses your sub-domain: you can do it here, for example by using a configuration variable instead of TempData.HostingEnvironment?.ContentRootPath

    public static string CreateImagePath(string filename)
    {
        return Path.Combine(TempData.HostingEnvironment?.ContentRootPath, "images", $"{filename}");
    }
    

    Details:

    AdaptiveCard item is passed as an attachment in the bot message, everything that is inside is rendered by the client (channel), so it can only render things that are "visible" from the outside.

    See my demo here: I used a Card with an ImageSet of 2 images: one with an URL over the web, the other with an URL build like yours.

    Local: both images are OK. enter image description here

    Published: only 1st image is displayed Published

    If you look at the Inspector on the right-side, it becomes obvious that you must change the way you build your image Url.

    You can also have a look to your deployed webchat by right-clicking the zone where the image should be, and you will find a link with a D:\... path

    Code Fix

    1st, move your images folder inside wwwroot folder of your projet. Then you can do the following:

    public string CreateImagePath(string filename)
    {
        if (_hostingEnvironment.IsDevelopment())
        {
            return Path.Combine(TempData.HostingEnvironment?.ContentRootPath, "wwwroot", "images", $"{filename}");
        }
        else
        {
            var targetUri = new Uri(new Uri($"https://{Environment.ExpandEnvironmentVariables("%WEBSITE_SITE_NAME%")}.azurewebsites.net"), Path.Combine("images", $"{filename}"));
            return targetUri.ToString();
        }
    }