Create a VSTS Extension that uses Azure Functions

Posted by: Gouri Sohoni , on 12/8/2016, in Category VSTS & TFS (Azure DevOps)
Views: 15667
Abstract: Create a simple VSTS extension as well as publish it. Also learn how to create and use an Azure function with VSTS extension.

In the past, we have created extensions for TFS on-premises. These extensions go beyond the standard functionality provided by the built-in features of TFS, and extend its services to suit your automation requirements. Since the advent of VSTS (Visual Studio Team Services) which is like TFS in the cloud for you, such extensions to services are required here too.

 

VSTS Extension Creation Process

We decided to create a VSTS extension of one of our products (TaskO). While creating that extension, we realized that creating a VSTS extension is not a simple process. It has many complexities that we had to learn. This article is a log of those challenges and how to conquer them. We will be discussing the following throughout the article:

  • How to create a simple VSTS extension
  • Scopes for the extension
  • How to publish the extension
  • How to install it for a VSTS instance
  • Create an Azure Function
  • Use Azure function with VSTS extension

Let us start the process of creating a Visual Studio Team Services Extension.

Extensions are simple add on features which will extend your VSTS functionality. We can write them with simple client side programming using HTML, CSS, JavaScript, jQuery. I used Visual Studio 2015 to create it. The extension that we will create, will use Azure functions for programmatic support and we will use REST APIs to communicate with VSTS. Once created, extensions can be published on the Visual Studio Marketplace.

The extension has 3 components, JSON file (comprises of extension manifest), any supporting unit like screenshots, and finally static files for user interface (these comprise of HTML, CSS, JavaScript etc.). These 3 components are packaged together to form a .vsix file which gets published to marketplace. Once the extension is available in the marketplace, installing it on an individual VSTS instance is a relatively simple process.

Walkthrough for creating VSTS Extension

Note: Let me first enumerate prerequisites for creating VSTS extension

1.     VSTS account. This account can be created from http://www.visualstudio.com – an IDE to create the extension (as mentioned already I used Visual Studio 2015),

2.     Latest version of node.js which can be downloaded from here.

3.     TFS cross platform command line interface to package the extension. The cross-platform command line interface (tfs-cli) can be installed by using a component of node.js. This component is npm and on the command prompt, we have to enter the command npm install -g tfx-cli

node-js

Figure 1: Node.js installation

Once these prerequisites are in place, let us now use Visual Studio 2015 to create the VSTS extension.

1. I used web application template in order to create a project that will be used to create this extension. We need file VSS.SDK.js file in a folder inside the project. In order to make it available, use npm install command npm install vss-web-extension-sdk

2. Copy the file in SDK\Scripts folder

3. Add a HTML page and write the following code in it:

<!DOCTYPE html>
<html>
<head>
    <title>MyExtension</title>
    <meta charset="utf-8" />
    <script src="sdk/scripts/VSS.SDK.js"></script>
</head>
<body>
    <script type="text/javascript">VSS.init();</script>
    <h1>My Demo Extension</h1>
    <script type="text/javascript">VSS.notifyLoadSucceeded();</script>
</body>
</html>

Observe the VSS.SDK.js is referenced via path sdk\scripts. Observe the method calls to VSS.init and VSS.norifyLoadSucceeded

4. We can add an image to the extension. Under the project, create images folder and add an image to it. My final HTML page looks as follows:

<!DOCTYPE html>
<html>
<head>
    <title>MyExtension</title>
    <meta charset="utf-8" />
    <script src="sdk/scripts/VSS.SDK.js"></script>
</head>
<body>
    <table>
        <tr>
            <td>
                <img src="images/SSGSLogo.png" style="margin-left:40px" />
            </td>
            <td>
                <h2 style="margin-left:200px; font-size:20pt;font-family:'Segoe UI'">My Demo Extension</h2>
            </td>
        </tr>
    </table>
    <script type="text/javascript">VSS.init();</script>
    <script type="text/javascript">VSS.notifyLoadSucceeded();</script>
</body>
</html>

Observe the VSS.SDK.js is referenced via path sdk\scripts. Observe the method calls to VSS.init and VSS.norifyLoadSucceeded

5. We will now define our extension. To do so, create a .json file. I have created VSS-extension.json. This file describes all the components we have created previously like the HTML page, images and script files. It also needs to specify the name, as well as scope for the extension. The Name of the file need not be the same, but it has to be a .json file.

{
  "manifestVersion": 1,
  "id": "MyExtension",
  "version": "1.0",
  "name": "MyExtension",
  "description": "work item spawning",
  "publisher": "SSGS",
  "targets": [
    {
      "id": "Microsoft.VisualStudio.Services"
    }
  ],
  "icons": { "default": "images/SSGSLogo.png" },
  "contributions": [
    {
      "id": "MyExtension-hub-group",
      "type": "ms.vss-web.hub-group",
      "description": "Adds a 'My Extension UI' hub group",
      "targets": [
        "ms.vss-web.project-hub-groups-collection"
      ],
      "properties": {
        "name": "MyExtensionPage",
        "order": 100
      }
    },
    {
      "id": "MyExtension-hub",
      "type": "ms.vss-web.hub",
      "description": "Adds a 'MyExtensionPage' hub group",
      "targets": [
        ".MyExtension-hub-group"
      ],
      "properties": {
        "name": "MyExtension UI",
        "order": 99,
        "uri": "MyExtension.html"
      }

    }
  ],

  "files": [
    {
      "path": "images/SSGSLogo.png",
      "addressable": true
    },
   {
      "path": "MyExtension.html",
      "addressable": true
    },
    {
      "path": "sdk/scripts",
      "addressable": true
    }
  ]
}

The manifest describes the extension name, id, its version, and description. It also has a contributions paragraph. This comprises of the type of contribution where the extension is targeted, and various properties for the extension. The properties are name, order, and uri. There is also a para for files referred via the extension. In this case it is the logo, HTML file and scripts.

6. Let us package the extension by using Command Line Interface(CLI). Enter following command

tfx extension create --manifest-globs vss-extension.json

The Last parameter is the name of the json file. After successful packaging a .vsix file is created.

vsix-creation

7. In order to publish the extension, I chose the publisher as our company name. Enter url https://marketplace.visualstudio.com/manage/publishers

create-publisher

Provide ID and Display name for the publisher. You will need a Microsoft Account or your organization account for this step.

8. After successful creation of the publisher, select the option for Upload New Extension and browse to the folder where .vsix file exists. Click on the Upload button.

upload-extension

The uploaded extension is displayed in browser:

extension-uploaded

9. Click on the Share button and provide the VSTS account to which we will assign the extension. This sharing is for private preview only. It is to used for testing your extension and is not available to general public. This https://marketplace.visualstudio.com/manage/publishers/ will provide the list of all the extensions you want to share.

10. Provide url (https://.visualstudio.com) for the VSTS account in the browser and select extensions as follows:

vsts-extension

The extension is shared with this account. Select the extension and click on Install.

install-extension

11. The account name will be automatically selected, and a window will be shown to confirm this.

confirm-extension-installation

12. Select any of the existing Team Projects and you will find that the extension hub is added.

extension-hub-added

13. Select MyExtension UI and you will see contents of the HTML page we created.

extesion-for-team-project

VSTS Extension that uses Azure Functions

So far, we have seen how to create and publish a VSTS Extension. Let us explore Azure functions, which is a part of the serverless architecture provided by Microsoft on the Azure cloud. For our extension, we can use any function that is accessible on the internet using REST APIs, however we felt convenient to make use of this relatively new feature called Azure Functions. 

You can get more information about Azure functions via this url. If you do not have an Azure account, you can select the option for creating a free account, and continue writing functions. In order to create a new Azure function, enter url https://functions.Azure.com and log in with your Azure account. Provide name for the function app, region and click on Create + get started button in order to create function app.

create-azure-function-page

 

Now you can choose the type of Azure Functions to be created.

There are 4 tabs for functions - Develop, Integrate, Manage and Monitor. In the Develop tab, we can write the actual code for the function. The advantage of using Azure functions is that we can use our preferred language i.e. C# to write code. The main code is written in run.csx file. We can use using statements, as well as other .csx files and write support functions within them.

For using these files, we need to add #load followed by the file name. We can also reference any other assembly by referencing it by adding #r followed by assembly name in the code. The other files which are function.json (binding related information), project.json (to use NuGet packages similar to using it in Visual Studio template), project.lock.json (complete list of NuGet packages. project.json can have wild card characters but not here)

In this example, I am using a HTTP trigger for executing the function.

Following are some examples of these files:

function-json-file

Figure: function.json file

project-json-file

Figure: project.json file

project-lock-json-file

Figure: project.lock.json file excerpt

Let us write some actual code for run.csx in this function. This function will be called from the extension HTML file. I am going to send some parameters to this function. These parameters are the VSTS account name, and the project name and will be separated and used as required in the code. In this case, I am just returning the project name back for demo.

The code looks as follows:

azure-function-code

The moment we save the function, it is compiled and we get to know if there are any compilation errors in the code. The project.json file can be uploaded by using kudu which you will get in function app settings as shown below:

kudu

We get the following interface for kudu. It shows the folder structure. We can then browse to the folder and upload our file.

kudu-interface

We can upload a file or create a new folder by selecting New file or New folder as seen in following figure:

kudu-upload-file

Once the code is written, this Azure function can be called from our VSTS extension by providing the url given in Function Url box.

Let us do the necessary changes in the HTML file for VSTS extension so that it calls the Azure Function.

I have used a button which when clicked will call the Azure function and will show the data in a textbox.

The following two fields can be used for the message and the button.

<form name="form1">
    <div id="div1" style="margin-left:120px">
        <input type='text' id='msgTxt' style='font-size:16pt;font-family:Segoe UI; width:800px; border-style:none; background-color:white' value="Demo Extension"  />
        <br /> <br />
        <input type="button" id="Click" value="Click" style='font-size:16pt;font-family:Segoe UI; width:150px' onClick="ClickMe()" />
    </div>
</form>

I have also added a ClickMe function which will be calling the Azure function.

function ClickMe()
{
    var collection = VSS.getWebContext().collection.name;
    var projectName = VSS.getWebContext().project.name;
    $.ajax(
    {
    dataType: "json",
    async: false,
    contentType: "application/json; charset=utf-8",
    headers: {
    Accept: "application/json",
    "Access-Control-Allow-Origin": "*"
    },
    type: "GET",
    data: "",
    url: "https://?collName=" + collection +
        "&projName=" + projectName,
    success: function (data)
    {
        document.getElementById("msgTxt").value = data;
    },
    error: function (xhr, ajaxOptions, thrownError) {
    document.getElementById("msgTxt").value = xhr.readyState + " Error: " + thrownError;
    }
    });
}

Observe how the values of VSTS instance name (referred to as collection) and project name are obtained by using VSS.getWebContext() and are sent to the function with the help of the url.

We have to create a new .vsix file to incorporate the changes. Now we can either change the version and update this change in the extension, or completely remove the extension and can add a new extension.

Now my extension on VSTS instance looks as below:

extension-changed

Once the button is clicked, the Azure function will be called and the result will be shown in the textbox.

changed-extension-called

On the Azure portal, the result of the function execution can be seen in the logs.

logs-after-execution-function

If we change code in the Azure function, and the change does not affect the result, we do not have to create the .vsix again. But if we do any change in the HTML file or json file, we need to create .vsix file again and update the extension to reflect changes.

Conclusion

In this article, we discussed how to create a simple VSTS extension. We also discussed scopes for the extension, how to publish the extension and how to install it for VSTS instance. In the later part of the article, we saw to create and use Azure function with VSTS extension.

There are many features of the VSTS Extension like authorization to access VSTS data, creating a service hook for executing a function as an event handler etc. that I have not yet touched. More about these in the future articles.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Gouri is a Trainer and Consultant on Azure DevOps and Azure Development. She has an experience of three decades in software training and consulting. She is a graduate from Pune University and PGDCA from Pune University. Gouri is a Microsoft Most Valuable Professional (MVP)  - Developer Technologies (Azure DevOps), Microsoft Certified Trainer (MCT) and a Microsoft Certified Azure DevOps Engineer Expert. She has conducted over 150 corporate trainings on various Microsoft technologies. She is a speaker with Pune User Group and has conducted sessions on Azure DevOps, SQL Server Business Intelligence and Mobile Application Development. Gouri has written more than 75 articles on Azure DevOps, TFS, SQL Server Business Intelligence and SQL Azure which are published on www.sqlservercurry.com and www.dotnetcurry.com. You can connect with her on LinkedIn.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!