Embedded Power BI reports with ASP.NET Core

Last year I had some projects where I had to embed Power BI reports to ASP.NET Core applications. There were easy cases that solved practically with copy-paste but I also had more complex situation where server-side code was needed because application uses custom authentication instead of Azure AD. This blog post covers both scenarios for embedding Power BI reports to ASP.NET Core applications.

Source code available! Source code and sample project for this blog posts are available in my GitHub repository gpeipman/AspNetCorePowerBI.

Integration with PowerBI is (always not so) easy to set up. We need reference to Microsoft.PowerBI.Api NuGet package and some coding to get reports integrated to our application. But first we need report. I once built simple report showing data about my craft beer ice distilling (check out my beer IoT journey if you are into complex beers). For embedding I’m using another one with just a chart from main report.

Sample Beer IoT Power BI report

Now I want this report to be available in my ASP.NET Core web application.

There are two ways how to do it:

  • Use embedded Power BI report in iframe – use this option if users are authenticated using Azure AD. It’s the fastest way to get work done.
  • Use C# and JavaScript to embed Power BI report – use this option if users are authenticated using Azure AD or if you have service account for Power BI that all users must use. This option is also good if you need more server-side control over Power BI service.

Let’s start with iframe and then let’s see how C# and JavaScript approach works.

Need simple ad-hoc reporting for ASP.NET Core? Check out my blog post DataSet and DataTable based ad-hoc reporting with ASP.NET Core. I worked out this primitive solution few years ago and it still works well in production.

Using Power BI report in iframe

It’s the simplest option to go with. Just open your report in Power BI portal, move to File menu and select Embed like shown on following screenshot.

Embed Power BI report from portal

Power BI will show you a dialog with embed report URL and iframe definition. Copy iframe definition, paste it to your view and you are done.

NB! This solution expects user to have Azure AD account. If user is already authenticated then report is shown immediately. If user is not authenticated then authentication form is shown and user is sent back to ASP.NET Core application after authentication.

Using C# and JavaScript to embed Power BI report

This is harder approach but it supports also using custom service account to display report. We have more work to do and in this point be ready for all kind of complexities in settings between Power BI and Azure AD if you are not Power BI professional.

NB! This approach works only with reports in workspaces. I wasn’t able to get it work with reports in My workspace.

To make Power BI integration work we need to register our application at Azure AD and get some ID-s.

First we need to register our web application in Power BI. You can do it at address https://dev.powerbi.com/apps. Log in, fill registration form and click Register. Make sure you select same application type as shown on following screenshot.

Power BI: Register new application

Application is actually registered to Azure AD behind your Office 365 tenant. If application got registered successfully then Power BI shows the following dialog.

Power BI: Application was successfully registered

Copy these ID-s to your application settings now! We need these ID-s in web application to communicate with Power BI.

In Power BI open the report you want to show to users and grab report ID from browser’s address bar.

Power BI: Get report ID

Copy report ID to your application settings! We have to tell Power BI components later what report we want to display. Same way you must take workspace ID where report belongs.

Configuring ASP.NET Core application

Application must know few settings to show report from Power BI. Add the following JSON to your appsettings.json file and fill in appropriate values.

"PowerBI": {
  "ApplicationId": "bfca1230-3d9a-4a31-de94-d0abfff13e8d",
  "ApplicationSecret": "tYA0i/dXsauHlkX1LXHQ194/EaH6crrrSoF1q55basc=",
  "ReportId": "5eae2c14-fd87-471e-191a-7fba3e8f918e",
  "WorkspaceId": "041148db-09c1-4e2d-b550-3f49164e868a",
  "AuthorityUrl": "https://login.windows.net/common/oauth2/authorize/",
  "ResourceUrl": "https://analysis.windows.net/powerbi/api",
  "ApiUrl": "https://api.powerbi.com/",
  "EmbedUrlBase": "https://app.powerbi.com/",
  "UserName": "gunnar@example.com",
  "Password": "admin123LikeAlways"
}

To have these settings as object we need class for Power BI settings block.

public class PowerBISettings
{
    public Guid ApplicationId { get; set; }
    public string ApplicationSecret { get; set; }
    public Guid ReportId { get; set; }
    public Guid? WorkspaceId { get; set; }
    public string AuthorityUrl { get; set; }
    public string ResourceUrl { get; set; }
    public string ApiUrl { get; set; }
    public string EmbedUrlBase { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
}

To make settings available in ASP.NET Core application we load settings when application starts and register them as singleton in framework-level dependency injection. This is done in ConfigureServices() method of Startup class.

var powerBISettings = Configuration.GetSection("PowerBI").Get<PowerBISettings>();
services.AddSingleton(powerBISettings);

We need also some NuGet packages:

  • Microsoft.IdentityModel.Clients.ActiveDirectory
  • Microsoft.PowerBI.Api

And Power BI JavaScript library:

From Power BI JavaScript library take minified version from dist folder and copy it to scripts folder of your web application.

Now we have basic work done and it’s time to get to real business.

Getting access token

.NET Core libraries for Azure AD doesn’t support username and password authentication like it was before. Instead we have to write our own hack to get access token for username and password authentication.

Add the following method to controller that hosts action for report.

private async Task<string> GetPowerBIAccessToken(PowerBISettings powerBISettings)
{
    using(var client = new HttpClient())
    {
        var form = new Dictionary<string, string>();
        form["grant_type"] = "password";
        form["resource"] = powerBISettings.ResourceUrl;
        form["username"] = powerBISettings.UserName;
        form["password"] = powerBISettings.Password;
        form["client_id"] = powerBISettings.ApplicationId.ToString();
        form["client_secret"] = powerBISettings.ApplicationSecret;
        form["scope"] = "openid";

        client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded");

        using (var formContent = new FormUrlEncodedContent(form))
        using (var response = await client.PostAsync(powerBISettings.AuthorityUrl, formContent))
        {
            var body = await response.Content.ReadAsStringAsync();
            var jsonBody = JObject.Parse(body);
           
            var errorToken = jsonBody.SelectToken("error");
            if(errorToken != null)
            {
                throw new Exception(errorToken.Value<string>());
            }

            return jsonBody.SelectToken("access_token").Value<string>();
        }
    }
}

Now it’s time to display report.

Displaying report

Here’s the code for controller action to display Power BI report. Notice how I use controller action injection to get Power BI settings to method.

public async Task<IActionResult> Report([FromServices]PowerBISettings powerBISettings)
{
    var result = new PowerBIEmbedConfig { Username = powerBISettings.UserName };
    var accessToken = await GetPowerBIAccessToken(powerBISettings);
    var tokenCredentials = new TokenCredentials(accessToken, "Bearer");

    using (var client = new PowerBIClient(new Uri(powerBISettings.ApiUrl), tokenCredentials))
    {
        var workspaceId = powerBISettings.WorkspaceId.ToString();
        var reportId = powerBISettings.ReportId.ToString();
        var report = await client.Reports.GetReportInGroupAsync(workspaceId, reportId);
        var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
        var tokenResponse = await client.Reports.GenerateTokenAsync(workspaceId, reportId, generateTokenRequestParameters);

        result.EmbedToken = tokenResponse;
        result.EmbedUrl = report.EmbedUrl;
        result.Id = report.Id;
    }

    return View(result);
}

Here is my view to show the report.

@model PowerBIEmbedConfig
<style>
    #reportContainer {
        height: 600px;
        width: 100%;
        max-width: 2000px;
    }
</style>
<script src="https://npmcdn.com/es6-promise@3.2.1"></script>
<script src="~/js/powerbi.min.js"></script>

<div id="reportContainer"></div>

@section scripts {
    <script>
    // Read embed application token from Model
    var accessToken = "@Model.EmbedToken.Token";

    // Read embed URL from Model
    var embedUrl = "@Html.Raw(Model.EmbedUrl)";

    // Read report Id from Model
    var embedReportId = "@Model.Id";

    // Get models. models contains enums that can be used.
    var models = window['powerbi-client'].models;

    // Embed configuration used to describe the what and how to embed.
    // This object is used when calling powerbi.embed.
    // This also includes settings and options such as filters.
    // You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details.
    var config = {
        type: 'report',
        tokenType: models.TokenType.Embed,
        accessToken: accessToken,
        embedUrl: embedUrl,
        id: embedReportId,
        permissions: models.Permissions.All,
        settings: {
            filterPaneEnabled: true,
            navContentPaneEnabled: true
        }
    };

    $(document).ready(function () {
        // Get a reference to the embedded report HTML element
        var reportContainer = $('#reportContainer')[0];

        // Embed the report and display it within the div container.
        powerbi.embed(reportContainer, config);
    });
    </script>
}

And here is my Power BI embedded report when I run my ASP.NET Core application.

Power BI report embedded in ASP.NET Core

Wrapping up

There are multiple options to embed Power BI reports using ASP.NET Core. IFrame solution was simple and straightforward – just copy markup from Power BI portal, paste it to ASP.NET Core view and you are done. With C# and JavaScript our solution was more complex and we had to write more code. Still we were able to make report available for all authenticated users in application that doesn’t use Azure AD.

Serverless360 Logo

A portal focused on Operations and Support for Microsoft Azure Serverless services

FREE TRIAL

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    27 thoughts on “Embedded Power BI reports with ASP.NET Core

    • January 11, 2020 at 2:39 am
      Permalink

      Not yet. Will add sample to GitHub during weekend.

    • January 11, 2020 at 8:31 pm
      Permalink

      I was working with PowerBI last year for a few weeks and struggled with one thing. I wanted to visualise data from a protected Rest API and update live rather than visualise a static report.

      What id like to do is authenticate and get data using the API and update the PowerBI reports live rather than a static file in the workspace.

      Have you done anything similar?

    • January 11, 2020 at 10:21 pm
      Permalink

      Can you give me more details of your scenario?

    • January 22, 2020 at 11:26 am
      Permalink

      Excellent tutorial about embedding power bi into asp net core application.
      I Had just to grant permissions to my application on azure portal to make it work.
      Thank you.

    • January 22, 2020 at 9:29 pm
      Permalink

      If you add application through Power BI portal then it should get all required permissions automatically.

    • January 24, 2020 at 1:45 pm
      Permalink

      thank you for a great tutorial.
      I have downloaded the sample solution and altered the appsettings.json file with my azure app details,
      but when i run the solution and go to the “C# and JavaScript” tab i always see the next error message, can you help me debugging this?:

      n unhandled exception occurred while processing the request.
      JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0.
      Newtonsoft.Json.JsonTextReader.ParseValue()

      Newtonsoft.Json.JsonTextReader.ParseValue()
      Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)
      Newtonsoft.Json.Linq.JObject.Parse(string json, JsonLoadSettings settings)
      Newtonsoft.Json.Linq.JObject.Parse(string json)
      AspNetCorePowerBI.Controllers.HomeController.GetPowerBIAccessToken(PowerBISettings powerBISettings) in HomeController.cs
      +
      var jsonBody = JObject.Parse(body);
      AspNetCorePowerBI.Controllers.HomeController.CsJs(PowerBISettings powerBISettings) in HomeController.cs
      +
      var accessToken = await GetPowerBIAccessToken(powerBISettings);
      Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
      System.Threading.Tasks.ValueTask.get_Result()
      System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()
      Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask actionResultValueTask)
      Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
      Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
      Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
      Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
      Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
      Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
      Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

      Show raw exception details
      Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0.
      at Newtonsoft.Json.JsonTextReader.ParseValue()
      at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)
      at Newtonsoft.Json.Linq.JObject.Parse(String json, JsonLoadSettings settings)
      at Newtonsoft.Json.Linq.JObject.Parse(String json)
      at AspNetCorePowerBI.Controllers.HomeController.GetPowerBIAccessToken(PowerBISettings powerBISettings) in C:\xampp\htdocs\User Owns Data3\AspNetCorePowerBI-master\AspNetCorePowerBI\AspNetCorePowerBI\Controllers\HomeController.cs:line 76
      at AspNetCorePowerBI.Controllers.HomeController.CsJs(PowerBISettings powerBISettings) in C:\xampp\htdocs\User Owns Data3\AspNetCorePowerBI-master\AspNetCorePowerBI\AspNetCorePowerBI\Controllers\HomeController.cs:line 38
      at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
      at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
      at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
      at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
      at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

    • January 24, 2020 at 3:13 pm
      Permalink

      Please check what JSON is GetPowerBIAccessToken() method getting back from Azure AD. Based on stack trace you gave me, it seems that problem is with Azure AD communication.

    • February 7, 2020 at 10:50 am
      Permalink

      Great article! Do you have any recommendations about a User owns data approach for PowerBI Report embedding? I’m struggling with the authentification.

    • February 11, 2020 at 12:43 am
      Permalink

      You shouldn’t have any problems with current user when using iframe. If you want embedded report (C# based) to know user then you need authenticated user from your application. My example here works with service credentials – all users see what service can see.

    • February 11, 2020 at 3:35 pm
      Permalink

      thank you for your great tutorial , I get the same exception above
      “JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0."
      and when I Trace I Found
      body= "

      Sign in to your account

      … etc.

    • February 11, 2020 at 4:08 pm
      Permalink

      JsonReaderException comes probably from GetPowerBIAccessToken() method. If service account doesn’t have access to Power BI then it gives back something else than JSON. Please make sure your service account has access to Power BI and then please check what errors reports response in GerPowerBIAccessToken() method.

    • February 20, 2020 at 11:52 am
      Permalink

      What kind of Power BI subscription is needed to be able to do this? Do every user of the webpage need to have a Power BI Pro subscription?

      Thomas

    • February 24, 2020 at 5:50 am
      Permalink

      As far as I understand then Power BI Pro is minimum subscription that supports embedding.

    • March 24, 2020 at 8:07 pm
      Permalink

      I tried out this method and like others, I am experiencing the following error:
      “JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0."

      I have confirmed that the service account does have access to Power BI, but at this line of code:
      var body = await response.Content.ReadAsStringAsync();

      The content is returning HTML instead of JSON.

    • March 24, 2020 at 9:13 pm
      Permalink

      @Joel – What is the content of “body”? I suspect it will indicate what the exact error is.

    • March 25, 2020 at 6:15 am
      Permalink

      Joel, make sure you have all URL-s and settings correct. Also make sure you are using account that has access to report. If response is login form then something must be wrong with settings or permissions.

    • March 30, 2020 at 4:12 pm
      Permalink

      Hey Gunnar, I confirmed that the URLs are correct, as well as the settings and I was still getting the same results. After looking at a few other places, I saw other solutions with an authority URL of: “https://login.windows.net/common/oauth2/token”. After changing the URL, I am getting the following:

      AADSTS65001: The user or administrator has not consented to use the application with ID ‘[Application ID]’ named ‘[Application Name]’. Send an interactive authorization request for this user and resource.

      Are you aware if there are any other permissions that the application needs to be granted other than the Power BI Tenant Read access?

    • March 31, 2020 at 7:34 am
      Permalink

      It definitely seems like something with permissions. I’m not sure what permissions do you miss but I know you can add these through Azure AD.

      I started my app registration from here: https://dev.powerbi.com/Apps. First I created service principal (it’s application for Azure AD). On this page you can register your application and give it all permissions needed.

    • April 13, 2020 at 9:05 am
      Permalink

      Hi
      How it works with on premise Power Bi, I need to embedded it to my Asp.net Core 3 App

    • May 7, 2020 at 8:45 am
      Permalink

      I also had this problem, the “AuthorityUrl” should be “https://login.windows.net/common/oauth2/token/”.

    • September 2, 2020 at 12:15 am
      Permalink

      good night, there was a code update, currently an error occurs when accessing the token.

      From already thank you, very good material.

    • September 14, 2020 at 11:30 pm
      Permalink

      not able to get pass GetPowerBIAccessToken method. Getting response as
      The Error Message is: Unexpected character encountered while parsing value: <. Path '', line 0, position 0.

      When I read the response, it is the Sign-in page from where username and password will redirect to my organization page.

      Is anyone able to fix this?

    Leave a Reply

    Your email address will not be published. Required fields are marked *