Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building Java Messaging Extensions and Connectors for Microsoft Teams Part 1: Building a Messaging Extension for Search

0.00/5 (No votes)
28 Jan 2022 1  
This tutorial demonstrates steps to build the messaging extension for Microsoft Teams.
This article demonstrates how to create a Spring Boot web app in Java that replicates the functionality of the “Messaging extensions - search” sample app on GitHub.

When developers build extensions for any application, they’d like to embed the custom functionality into the host application’s native user interface (UI). The same is true of Microsoft Teams, where your organization works and collaborates daily. You can ensure the transition to your helpful productivity app is as seamless as possible.

Specifically, you can create buttons or other controls that look like part of the host app, and the user can interact with your existing or new web services through those host controls. Your web service then handles the requests users send using those controls and produces the natural response for the given host app. You can achieve this embedded functionality in Microsoft Teams using messaging extensions.

We’ll explore embedding a Java-based application in Microsoft Teams in this three-part series. First, I’ll highlight how messaging extensions work in Microsoft Teams and build a messaging extension for search. Later, I’ll demonstrate building a link unfurling messaging extension and a notification connector.

Exploring Messaging Extensions in Microsoft Teams

Like any other Microsoft Teams app, a messaging extension comprises two elements: an existing or new web service and the app manifest. You implement the web service using your favorite tool.

You can specify up to ten commands in the app manifest, which act as shortcuts to your web service. You set the type and location in Microsoft Teams for each command where a user can invoke the command, like the compose message area, command bar, or a message.

Teams sends a JSON payload to your web service when the user invokes the command. You can then implement a custom login within your web service to handle the request and produce the response. You can use cards to display information to the user, such as visuals, text, or audio that accelerate conversations and user actions.

Microsoft Teams uses the Bot Framework to communicate with your web service. So, from our developer perspective, implementing the messaging extension involves implementing additional wrappers for the web service using the Bot Framework SDK.

I’ll show you how to implement a messaging extension for Microsoft Teams using the Java Spring MVC framework for a web service. The web service will return a response in the form of a card.

In this tutorial, I’ll test the entire solution using the local environment. However, you can also deploy the web service to Azure Spring Cloud.

To reproduce the example, you should install ngrok and prepare your environment.

You can find the companion code on GitHub.

Creating a Project

Start by creating the project using the Bot Framework Echo project template. Use the Yeoman generator:

yo botbuilder-java

Then configure your bot as follows:

  • Name: msg-extension-search
  • Package name: db.teamsext.search
  • Template: Echo Bot

The command above generates the project template. This template has a structure you can use for developing bots. Namely, the EchoBot class extends the ActivityHandler. The Echo Bot echos every message you send to the bot. Use this template for the Echo Bot:

Java
public class EchoBot extends ActivityHandler {

    @Override
    protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
        return turnContext.sendActivity(
            MessageFactory.text("Echo: " + turnContext.getActivity().getText())
        ).thenApply(sendResult -> null);
    }
 
    @Override
    protected CompletableFuture<Void> onMembersAdded(
        List<ChannelAccount> membersAdded,
        TurnContext turnContext
    ) {
        return membersAdded.stream()
            .filter(
                member -> !StringUtils
                    .equals(member.getId(), turnContext.getActivity().getRecipient().getId())
            ).map(channel -> turnContext.sendActivity
                 (MessageFactory.text("Hello and welcome!")))
            .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null);
    }
}

Before proceeding further, we need to register the bot and prepare the app manifest.

Registering and Configuring the Bot

We register the bot to ensure secure communication. You can use the App Studio in Microsoft Teams or the Developer Portal to register the bot. If you don’t already have a developer account, register one for free.

Here, I use the App Studio to register the bot. First, click Bot Management:

Next, type msg-extension-bot, then click Create. This action creates the application ID and password.

Now, paste those credentials into the resources/application.json file of the project you created in the previous step (msg-extension-search):

Next, run ngrok:

ngrok http -host-header=rewrite 3978

Note that the port should match the server.port property from application.properties.json. Here, I use a default value of 3978. The ngrok should report the forwarding URL.

In my case, the value is https://e1fa-46-229-154-234.ngrok.io. We use this value to configure the bot. So, let’s go back to the Microsoft Teams manifest and update the bot endpoint address to: https://e1fa-46-229-154-234.ngrok.io/api/messages. Press Enter to save the changes.

Making the Application Manifest

After registering the bot, we can create the application manifest. So, in App Studio, click Create a new application. Then, populate the app details as follows:

  • Short name: msg-extension-search
  • Full name: Messaging Extension Search
  • App ID: Click Generate in the Identification group to create a new ID
  • Package name: db.teams.msgextensionsearch
  • Version: 1.0.0
  • Descriptions: Type any value
  • Developer information: Type any value, for example, Contoso, Inc., and https://www.teams.com
  • App URLs:
    • Privacy statement: https://example.azurewebsites.net or any other valid URL
    • Terms of use: https://example.azurewebsites.net or any valid URL

Then, go to Messaging Extensions and click Set Up. This action activates the popup window, where you click Use existing bot, click the Select from one of my existing bots radio button and choose msg-extension-bot from the dropdown list. For the bot name, type: msg-extension-bot.

Click Save. The following screen then displays:

Now, click Add under Command. App Studio will display the new popup:

Next, choose the first option: Allow users to query your service for information and insert that into a message. This option enables your users to type the search query that the app will send to the Bot.

After, the App Manifest displays a New command screen, where you provide the following:

  • Command Id: searchQuery
  • Title: Search
  • Context: Command Box, Compose Box
  • Parameter:
    • Name: searchQuery
    • Title: Search Query
    • Description: Your search query
    • Type: Text

Finally, click Save. Wait for the popup to close, then go to Test and distribute in the Finish tab. Click Install and add the app to Microsoft Teams.

Adding the Echo Messaging Extension

Now, let’s implement the web service. It will retrieve the search query from the messaging extension then produce the card with the title set to “search query.”

First, open the EchoBot.java file and modify the class declaration as follows (replace ActivityHandler with TeamsActivityHandler):

Java
public class EchoBot extends TeamsActivityHandler

Then, add the following method:

Java
@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionQuery(
        TurnContext turnContext,
        MessagingExtensionQuery query
) {
    // Get query text
    String queryText = GetQueryText(query);
   
    // Create a hero card
    HeroCard card = new HeroCard();
    card.setTitle("Echo");
    card.setSubtitle(queryText);
    card.setText("This sample is a sample hero card");
 
    // Create attachment
    MessagingExtensionAttachment attachment = new MessagingExtensionAttachment();
    attachment.setContent(card);
    attachment.setContentType(HeroCard.CONTENTTYPE);
    attachment.setPreview(card.toAttachment());
 
    // Prepare result
    MessagingExtensionResult result = new MessagingExtensionResult();
    result.setAttachmentLayout("list");
    result.setType("result");
    result.setAttachment(attachment);
 
    // Return the response
    return CompletableFuture.completedFuture(

        new MessagingExtensionResponse(result));      
}

The above method reads the search query (the value Microsoft Teams sends to the messaging extension) and stores it under the queryText local variable. Then, this value populates the hero card, displaying one item. The card will have the Echo title and show the value from the queryText variable as the subtitle. Additionally, the “This is a sample hero card” string displays as the screenshot below shows:

To get the query text, use the helper method, GetQueryText:

Java
private String GetQueryText(MessagingExtensionQuery query) {
    String queryText = "Empty query";
   
    if (query != null && query.getParameters() != null) {
        List<MessagingExtensionParameter> queryParams = query.getParameters();
 
        if (!queryParams.isEmpty()) {
            MessagingExtensionParameter firstParam = queryParams.get(0);
 
            if(firstParam.getName().equals("searchQuery")) {
                queryText = (String) queryParams.get(0).getValue();
            }
        }
    }
   
    return queryText;
}

This method accepts an instance of the MessagingExtensionQuery class. This class represents the payload Microsoft Teams sends to your bot through the messaging extension. In particular, this payload includes the following:

  • commandId – The command’s identifier. This identifier corresponds to what we declared earlier in the application manifest.
  • parameters – The query parameters.
  • queryOptions – The query options.
  • state – Teams passes this parameter back to the bot after authentication or configuration flow.

We configured the messaging extension such that it handles only one command. This command has only one parameter (searchQuery). After validating the input, the GetQueryText method looks for the searchQuery parameter, then reads its value. We check if the parameter name matches the expected string because when you open the messaging extension for the first time, you get the initialRun parameter with the Boolean value.

Finally, note that onTeamsMessagingExtensionQuery returns an instance of CompletableFuture with the MessagingExtensionResponse. We create MessagingExtensionResponse using a default constructor.

Then, we supplement the MessagingExtensionResponse instance by attaching the hero card. We abstract the attachment with the MessagingExtensionAttachment class instance. We set the content and preview based on the created HeroCard class instance.

To test the above solution, launch the web service. Then, open Microsoft Teams. In the compose area, click the triple dots icon and look up the msg-extension-search:

Click msg-extension-search and type your search query. Note that the search query passes to your web service, where you can process it and generate results. Here, we only read the search query and create the hero card. So, the final result will look like the screenshot below:

Creating the Messaging Extension with Search

Finally, we reproduce the C# messaging extension with search. The code takes the search query to look up the NuGet packages. To this end, the messaging extension sends the HTTP GET request via this link:

https://azuresearch-usnc.nuget.org/query?q=<search_query>&prerelease=true

The above Azure search responds with a JSON-formatted document comprising the NuGet package list. For example, the query https://azuresearch-usnc.nuget.org/query?q=sqlite&prerelease=true returns the JSON in the screenshot below:

We can use the findPackage method to do the same in Java:

Java
private CompletableFuture<List<String[]>> findPackages(String text) {
        return CompletableFuture.supplyAsync(() -> {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(String
                         .format(
                             "https://azuresearch-usnc.nuget.org/query?q=id:%s&prerelease=true",
                              text
                            ))
                    .build();
 
            List<String[]> filteredItems = new ArrayList<>();
            try {
                Response response = client.newCall(request).execute();
                JsonNode obj = Serialization.jsonToTree(response.body().string());
                ArrayNode dataArray = (ArrayNode) obj.get("data");
 
                for (int i = 0; i < dataArray.size(); i++) {
                    JsonNode item = dataArray.get(i);
                    filteredItems.add(new String[] {
                            item.get("id").asText(),
                            item.get("version").asText(),
                            item.get("description").asText(),
                            item.has("projectUrl") ? item.get("projectUrl").asText() : "",
                            item.has("iconUrl") ? item.get("iconUrl").asText() : ""
                    });
                }
            } catch (IOException e) {
                LoggerFactory.getLogger(EchoBot.class)
                        .error("findPackages", e);
                throw new CompletionException(e);
            }
            return filteredItems;
        });
    }

Then, we invoke the above method within onTeamsMessagingExtensionQuery as follows:

Java
@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionQuery(
        TurnContext turnContext,
        MessagingExtensionQuery query
) {
    String text = "";
    if (query != null && query.getParameters() != null) {
        List<MessagingExtensionParameter> queryParams = query.getParameters();
        if (!queryParams.isEmpty()) {
            text = (String) queryParams.get(0).getValue();
        }
    }
 
    return findPackages(text)
            .thenApply(packages -> {
                // We take every row of the results
                // and wrap them in cards wrapped in MessagingExtensionAttachment objects.
                // The Preview is optional, if it includes a Tap
                // that will trigger the onTeamsMessagingExtensionSelectItem event 
                // back on this bot.
                List<MessagingExtensionAttachment> attachments = new ArrayList<>();
                for (String[] item : packages) {
                    ObjectNode data = Serialization.createObjectNode();
                    data.set("data", Serialization.objectToTree(item));
 
                    CardAction cardAction = new CardAction();
                    cardAction.setType(ActionTypes.INVOKE);
                    cardAction.setValue(Serialization.toStringSilent(data));
                    ThumbnailCard previewCard = new ThumbnailCard();
                    previewCard.setTitle(item[0]);
                    previewCard.setTap(cardAction);
 
                    if (!StringUtils.isEmpty(item[4])) {
                        CardImage cardImage = new CardImage();
                        cardImage.setUrl(item[4]);
                        cardImage.setAlt("Icon");
                        previewCard.setImages(Collections.singletonList(cardImage));
                    }
 
                    HeroCard heroCard = new HeroCard();
                    heroCard.setTitle(item[0]);
 
                    MessagingExtensionAttachment attachment = 
                                                 new MessagingExtensionAttachment();
                    attachment.setContentType(HeroCard.CONTENTTYPE);
                    attachment.setContent(heroCard);
                    attachment.setPreview(previewCard.toAttachment());
 
                    attachments.add(attachment);
                }
 
                MessagingExtensionResult composeExtension = new MessagingExtensionResult();
                composeExtension.setType("result");
                composeExtension.setAttachmentLayout("list");
                composeExtension.setAttachments(attachments);
 
                // The list of MessagingExtensionAttachments 
                // must be wrapped in a MessagingExtensionResult
                // wrapped in a MessagingExtensionResponse.
                return new MessagingExtensionResponse(composeExtension);
            });
}

Additionally, we can respond to user actions using onTeamsMessagingExtensionSelectItem:

Java
@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionSelectItem(
        TurnContext turnContext,
        Object query
) { 
    // The Preview card's Tap should have a Value property assigned, 
    // this will be returned to the bot in this event.
    Map cardValue = (Map) query;
    List<String> data = (ArrayList) cardValue.get("data");
    CardAction cardAction = new CardAction();
    cardAction.setType(ActionTypes.OPEN_URL);
    cardAction.setTitle("Project");
    cardAction.setValue(data.get(3));
 
    // We take every row of the results
    // and wrap them in cards wrapped in MessagingExtensionAttachment objects.
    // The Preview is optional, if it includes a Tap
    // that will trigger the onTeamsMessagingExtensionSelectItem event back on this bot.
    ThumbnailCard card = new ThumbnailCard();
    card.setTitle(data.get(0));
    card.setSubtitle(data.get(2));
    card.setButtons(Arrays.asList(cardAction));
 
    if (StringUtils.isNotBlank(data.get(4))) {
        CardImage cardImage = new CardImage();
        cardImage.setUrl(data.get(4));
        cardImage.setAlt("Icon");
        card.setImages(Collections.singletonList(cardImage));
    }
 
    MessagingExtensionAttachment attachment = new MessagingExtensionAttachment();
    attachment.setContentType(ThumbnailCard.CONTENTTYPE);
    attachment.setContent(card);
 
    MessagingExtensionResult composeExtension = new MessagingExtensionResult();
    composeExtension.setType("result");
    composeExtension.setAttachmentLayout("list");
    composeExtension.setAttachments(Collections.singletonList(attachment));
    return CompletableFuture.completedFuture(new MessagingExtensionResponse(composeExtension));
}

After running the above code, you’ll see the list of NuGet packages:

You can click on any item you want. The messaging extension populates the message compose area with details about the selected NuGet package:

Next Steps

In this tutorial, I demonstrated the steps to build the messaging extension for Microsoft Teams. We learned that the messaging extension uses the Microsoft Bot framework for securing communication with the external web service. The response the bot produces can be composed of cards.

Continue to the second part of this three-part series to learn how to build a link unfurling messaging extension.

To learn how you can build, migrate and scale Java applications on Azure using Azure services, check out Get started with Java on Azure.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here