Power BI Embedded – A minimal example

Warning: Microsoft has updated the Javascript part of PowerBI Embedded and the example code in this article is no longer working out-of-the-box..

Now we have an embedded version of Power BI. Power BI is a great reporting/BI tools, and the possibilities in embedding it into your own products or site are great.

Microsoft has even gone the whole way, and created a beautiful example application web site, it can be found here:

https://azure.microsoft.com/en-us/documentation/articles/power-bi-embedded-get-started-sample/

The problem is (for me at least), that my world is different, compared to the example. So I really just want a minimal, barebone example that I can extend and build a prototype from.

Here is my minimal recipe for a Embedded Power BI example:

First you need a Power BI Workspace Collection. Go to your Azure Portal, and create one (Search for Power BI after pressing New). This is your “empty” report collection and the starting point for a Power BI Embedded installation.  You need to remember the name and copy one of the Access Keys, we are going to need that later. After this, you’re done in Azure for now. (Presently PowerBI Embedded is only available in South Central US. I was told at /BUILD that with this month it should be available in all Azure data centers.)

To continue, we must create a workspace inside our collection. I’m using C# and there are NuGet (pre-release) availables. The first thing we need, is to log in and get a valid PowerBIClient – To do that, we need the Access Key we just got from the Azure Portal.

static IPowerBIClient CreateClient(PowerBIToken token)
{
    var jwt = token.Generate(<access key from azure portal>);
    var credentials = new TokenCredentials(jwt, "AppToken");
    var client = new PowerBIClient(credentials);
    client.BaseUri = new Uri("https://api.powerbi.com");
    return client;
}        

Next we can use the client to create a workspace, you need to supply the name of the Azure Workspace Collection just created.

Workspace workspace;
var provisionToken = PowerBIToken.CreateProvisionToken(<name of workspace collection>);
using (var client = CreateClient(provisionToken))
{
     workspace = await client.Workspaces.PostWorkspaceAsync(<name of workspace collection>);
}

The newly created workspace has an ID – Get it from workspace.WorkspaceId or from the Azure portal.

The next task is to create a report. Report are in a file format called PBIX. Report can be created with Power BI Desktop (download).

When creating a report, you need to decide where the data is stored. My world is SQL and Azure SQL, so I’m using “Direct Query” – meaning that the data is not bundled with the report. Your mileage may vary. There is a lot of material on creating reports in Power BI, so I’ve skip that part in this minimal example.

After that, we need to upload the report to our workspace:

static async Task<Import> ImportPbix(string workspaceCollectionName, string workspaceId, string datasetName, string filePath)
{
    using (var fileStream = File.OpenRead(filePath))
    {
        // Create a dev token for import
        var devToken = PowerBIToken.CreateDevToken(workspaceCollectionName, workspaceId);
        using (var client = CreateClient(devToken))
        {

            // Import PBIX file from the file stream
            var import = await client.Imports.PostImportWithFileAsync(workspaceCollectionName, workspaceId, fileStream, datasetName);

            // Example of polling the import to check when the import has succeeded.
            while (import.ImportState != "Succeeded" && import.ImportState != "Failed")
            {
                import = await client.Imports.GetImportByIdAsync(workspaceCollectionName, workspaceId, import.Id);
                Console.WriteLine("Checking import state... {0}", import.ImportState);
                Thread.Sleep(1000);
            }

            return import;
        }
    }
}

After import the report also gets an ID.

I’m my case, I’m using a dynamic back end where I have multiple databases, so I need to update the report for a specific database that is different from the database I used when I created the report:

static async Task UpdateConnection(string workspaceCollectionName, string workspaceId)
{
    string connectionString = "data source = <Azure SQL Server>.database.windows.net; initial catalog = <database>; persist security info = True; encrypt = True; trustservercertificate = False";

    var devToken = PowerBIToken.CreateDevToken(workspaceCollectionName, workspaceId);
    using (var client = CreateClient(devToken))
    {
        // Get the newly created dataset from the previous import process
        var datasets = await client.Datasets.GetDatasetsAsync(workspaceCollectionName, workspaceId);

        // Optionally udpate the connectionstring details if preent
        if (!string.IsNullOrWhiteSpace(connectionString))
        {
            var connectionParameters = new Dictionary<string, object>
            {
                { "connectionString", connectionString }
            };
            await client.Datasets.SetAllConnectionsAsync(workspaceCollectionName, 
                                                            workspaceId, 
                                                            datasets.Value[datasets.Value.Count - 1].Id, connectionParameters);
        }

        // Get the datasources from the dataset
        var datasources = await client.Datasets.GetGatewayDatasourcesAsync(workspaceCollectionName, workspaceId, datasets.Value[datasets.Value.Count - 1].Id);

        // Reset your connection credentials
        var delta = new GatewayDatasource
        {
            CredentialType = "Basic",
            BasicCredentials = new BasicCredentials
            {
                Username = <Azure SQL User Name>,
                Password = <Azure SQL Password>                        
            }
        };

        // Update the datasource with the specified credentials
        await client.Gateways.PatchDatasourceAsync(workspaceCollectionName, workspaceId, datasources.Value[datasources.Value.Count - 1].GatewayId, datasources.Value[datasources.Value.Count - 1].Id, delta);
    }
}

Now, we are ready, and the report can be used, lets recap all the “keys”, “tokens”, and “IDs” thats needed:

  • Access Key to the Azure Power BI Workspace Collection
  • ID of the workspace
  • ID of the report

To the an active working instance of the report, we need a “Access Token”. This token, together with the Report Id will be visible to users. The token will expire after a defined period of time.

This function will create an report Access Token that we need to execute the report.

public static async Task<string> Report(string reportId)
{
    var devToken = PowerBIToken.CreateDevToken(<Azure WorkSpace Collection>, <Workspace ID>);
    using (var client = CreateClient(devToken))
    {
        var embedToken = PowerBIToken.CreateReportEmbedToken(<Azure WorkSpace Collection>, <Workspace ID>,reportId);

        return embedToken.Generate(<Access Key>);
    }
}

Then, instead of an entire website, I’ve just create one HTML file that will show the report, there is a bit of Javascript in the beginning and one div section with an iframe. The good pieces are at the bottom, the HTML needs the report Id (3 times) and the Access Token we just generated.

<html>
<script>
(function (powerbi) {
    'use strict';

    powerbi.Embed = Embed;

    function Embed() { }

    Embed.prototype = {
        init: function () {
            var embedUrl = this.getEmbedUrl();
            var iframeHtml = '<iframe style="width:100%;height:100%;" src="' + embedUrl + '" scrolling="no" allowfullscreen="true"></iframe>';
            this.element.innerHTML = iframeHtml;
            this.iframe = this.element.childNodes[0];
            this.iframe.addEventListener('load', this.load.bind(this), false);
        },
        load: function () {
            var computedStyle = window.getComputedStyle(this.element);
            var accessToken = this.getAccessToken();
            
            var initEventArgs = {
                message: {
                    action: this.options.loadAction,
                    accessToken: accessToken,
                    width: computedStyle.width,
                    height: computedStyle.height
                }
            };

            powerbi.utils.raiseCustomEvent(this.element, 'embed-init', initEventArgs);
            this.iframe.contentWindow.postMessage(JSON.stringify(initEventArgs.message), '*');
        },
        getAccessToken: function () {
            var accessToken = this.element.getAttribute('powerbi-access-token');

            if (!accessToken) {
                accessToken = powerbi.accessToken;
                
                if (!accessToken) {
                    throw new Error("No access token was found for element. You must specify an access token directly on the element using attribute 'powerbi-access-token' or specify a global token at: powerbi.accessToken.");
                }
            }

            return accessToken;
        },
        getEmbedUrl: function () {
            return this.element.getAttribute('powerbi-embed');
        },
        fullscreen: function () {
            var elem = this.iframe;

            if (elem.requestFullscreen) {
                elem.requestFullscreen();
            } else if (elem.msRequestFullscreen) {
                elem.msRequestFullscreen();
            } else if (elem.mozRequestFullScreen) {
                elem.mozRequestFullScreen();
            } else if (elem.webkitRequestFullscreen) {
                elem.webkitRequestFullscreen();
            }
        },
        exitFullscreen: function () {
            if (!this.isFullscreen()) {
                return;
            }

            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
        },
        isFullscreen: function () {
            var options = ['fullscreenElement', 'webkitFullscreenElement', 'mozFullscreenScreenElement', 'msFullscreenElement'];
            for (var i = 0; i < options.length; i++) {
                if (document[options[i]] === this.iframe) {
                    return true;
                }
            }

            return false;
        }
    };
} (window.powerbi = window.powerbi || {}));
(function(powerbi){
    'use strict';
    
    powerbi.Tile = Tile;
    powerbi.Tile.prototype = powerbi.Embed.prototype;
    
    function Tile(element, options) {
        var me = this;
        
        this.element = element;
        this.options = options || {};
        this.options.loadAction = 'loadTile';
        this.getEmbedUrl = getEmbedUrl;

        this.init();

        ///////////////////////////////

        function getEmbedUrl() {
            var embedUrl = powerbi.Embed.prototype.getEmbedUrl.call(me);
            if (!embedUrl) {
                var dashboardId = me.element.getAttribute('powerbi-dashboard');
                var tileId = me.element.getAttribute('powerbi-tile');

                if (!(dashboardId && tileId)) {
                    throw new Error('dashboardId & tileId are required');
                }

                embedUrl = 'https://app.powerbi.com/embed?dashboardId=' + dashboardId + '&tileId=' + tileId;
            }

            return embedUrl;
        }
    }
}(window.powerbi = window.powerbi || {}));
(function(powerbi){
    'use strict';
    
    powerbi.Report = Report;
    powerbi.Report.prototype = powerbi.Embed.prototype;
    
    function Report(element, options) {
        var me = this;
        
        this.element = element;
        this.options = options || {};
        this.options.loadAction = 'loadReport';
        this.getEmbedUrl = getEmbedUrl;

        this.init();

        ///////////////////////////////

        function getEmbedUrl() {
            var embedUrl = powerbi.Embed.prototype.getEmbedUrl.call(me);
            if (!embedUrl) {
                var reportId = me.element.getAttribute('powerbi-report');

                if (!reportId) {
                    throw new Error('reportId is required');
                }

                embedUrl = 'https://app.powerbi.com/reportEmbed?reportId=' + reportId;
            }

            return embedUrl;
        }
    }
}(window.powerbi = window.powerbi || {}));
(function (powerbi) {
    'use strict';
    
    powerbi.utils = {
        raiseCustomEvent: raiseCustomEvent
    };

    function raiseCustomEvent(element, eventName, eventData) {
        var customEvent;
        if (typeof (window.CustomEvent) === 'function') {
            customEvent = new CustomEvent(eventName, {
                detail: eventData,
                bubbles: true,
                cancelable: true
            });
        } else {
            customEvent = document.createEvent('CustomEvent');
            customEvent.initCustomEvent(eventName, true, true, eventData);
        }

        element.dispatchEvent(customEvent);
        if (customEvent.defaultPrevented || !customEvent.returnValue) {
            return;
        }

        var inlineEventAttr = 'on' + eventName.replace('-', '');
        var inlineScript = element.getAttribute(inlineEventAttr);
        if (inlineScript) {
            eval.call(element, inlineScript);
        }
    }
} (window.powerbi = window.powerbi || {}));
(function (powerbi) {
    'use strict';

    var embeds = [];
    var componentTypes = [];

    powerbi.get = get;
    powerbi.embed = embed;
    powerbi.init = init;

    activate();
    
    //////////////////////////////////    
    
    function activate() {
        window.addEventListener('DOMContentLoaded', init, false);
        window.addEventListener('message', onReceiveMessage, false);

        componentTypes = [
            { type: 'powerbi-tile', component: powerbi.Tile },
            { type: 'powerbi-report', component: powerbi.Report }
        ];
    }

    var EmbedEventMap = {
        'tileClicked': 'tile-click',
        'tileLoaded': 'tile-load',
        'reportPageLoaded': 'report-load'
    };

    function init(container) {
        container = (container && container instanceof HTMLElement) ? container : document.body;
        
        var components = container.querySelectorAll('[powerbi-embed]');
        for (var i = 0; i < components.length; i++) {
            embed(components[i]);
        }
    }

    function get(element) {
        return element.powerBIEmbed || embed(element);
    }

    function embed(element) {
        var instance;

        if (element.powerBIEmbed) {
            return element.powerBIEmbed;
        }

        for (var j = 0; j < componentTypes.length; j++) {
            var componentType = componentTypes[j];

            if (element.getAttribute(componentType.type) !== null) {
                instance = new componentType.component(element);
                element.powerBIEmbed = instance;
                embeds.push(instance);
                break;
            }
        }

        return instance;
    }

    function onReceiveMessage(event) {
        if (!event) {
            return;
        }

        try {
            var messageData = JSON.parse(event.data);
            for (var i = 0; i < embeds.length; i++) {
                var embed = embeds[i];

                // Only raise the event on the embed that matches the post message origin
                if (event.source === embed.iframe.contentWindow) {
                    powerbi.utils.raiseCustomEvent(embed.element, EmbedEventMap[messageData.event], messageData);
                }
            }
        }
        catch (e) {
            if (typeof (window.powerbi.onError) === 'function') {
                window.powerbi.onError.call(window, e);
            }
        }
    }
} (window.powerbi = window.powerbi || {}));
</script>
<body>
<div id="Report" 
     powerbi-access-token="<The access token just generated>>" 
     powerbi-embed="https://embedded.powerbi.com/appTokenReportEmbed?reportId=<The Report ID>" 
     powerbi-report="<The Report ID>" 
     style="height:85vh">
     <iframe style="width:100%;height:100%;" src="https://embedded.powerbi.com/appTokenReportEmbed?reportId=<The Report ID>" scrolling="no" allowfullscreen="true"></iframe></div>
</body>
</html>

Open the HTML file in a browser, and the report is available to you:

powerbi-2.

Hope this will you get going faster with Power BI Embedded.