Vi siete mai chiesti se sia possibile integrare report Power BI all’interno di un portale web, senza far uso del tag iframe autogenerato dalla piattaforma di reportistica?
Il progetto che vi raccontiamo utilizza una semplice applicazione Blazor WebAssembly che è in grado di incorporare i report all’interno di un componente Blazor, sfruttando le API messe a disposizione da Power BI.
Questa soluzione consente inoltre di avere maggior controllo e pulizia del codice.

Architettura

L’architettura illustrata nel diagramma sottostante si prefigge di descrivere il flusso di autenticazione e autorizzazione per accedere ai dati della risorsa del report Power BI. L’utente, quando tenta di visualizzare il report di Power BI, fa implicitamente una richiesta di accesso e, solo successivamente, ottiene in risposta il report stesso. L’applicazione Blazor client (WASM), necessitando di un token per l’accesso a Power BI, pone quindi una richiesta al backend, il quale, a sua volta, la inoltra via Client Credential ad Azure Active Directory alla risorsa Power BI. Una volta ottenuto il token, questo viene utilizzato da Blazor, e in particolare dall’SDK javascript, per accedere agli oggetti della risorsa ed eventualmente controllare il report Power BI per impostare una serie di informazioni, quali filtri, gestione eventi etc.

  1. Richiesta utente per la visualizzazione del report
  2. Richiesta dall’applicazione web al backend
  3. Richiesta dal backend ad Azure Active Directory per la richiesta di accesso alla risorsa
  4. Restituzione del token al backend
  5. Restituzione del token al frontend blazor
  6. Richiesta del token la richiesta della risorsa
  7. Ricezione token per accedere alla risorsa
  8. Restituzione di html e javascript con i dati
Blazor-PowerBI.png

Permessi e Autenticazione

Il seguente paragrafo è un breve riepilogo di come è stato impostato e configurato l’ambiente all’interno del quale andremo a lavorare, partendo dal presupposto che vi sia un report di PowerBi già esistente.

Azure Active Directory

Grazie alle funzionalità offerte da Azure AD abbiamo la possibilità di centralizzare la gestione di autorizzazione e permessi che vogliamo fornire alla nostra applicazione. Per raggiungere tale scopo si è dunque operato nel seguente modo:

  • È stata creata una nuova registrazione associata all’app. Da qui è stato dunque creato un nuovo client secret, che in combinazione al tenant id e client id consentirà all’applicazione Blazor di autenticarsi. 
  • Dopo di che l’applicazione è stata autorizzata a chiamare le API esposte da Power Bi. 
  • In fine, è stato creato un gruppo, in modo tale da semplificare la gestione degli utenti. 
  • Dopo di che l’applicazione è stata autorizzata a chiamare le API esposte da Power Bi.
  • Infine, è stato creato un gruppo, in modo tale da semplificare la gestione degli utenti.

PowerBi

Lato Power Bi, invece, al fine di completare il giro di autenticazione, sono state eseguite le seguenti operazioni:

  • Dai Workspaces è stato selezionato il report che si desidera collegare all’applicazione e premuto il tasto Access.
  • Nella schermata appena apertasi è stato aggiunto come amministratore il gruppo creato in precedenza.

Codice

Si andrà ora ad illustrare i passaggi più importanti che sono stati affrontati durante l’implementazione dell’applicazione fino al raggiungimento del risultato desiderato. Blazor ci permette di eseguire una parte del codice, generato a partire da quello C# definito da noi, direttamente all’interno del browser. La nostra soluzione è dunque divisa in due progetti: BlazorPowerBi.Server e BlazorPowerBi.Client e quest’ultimo sarà appunto la parte che verrà eseguita lato browser.

Backend

Una volta creata la nuova applicazione Blazor WebAssembly, il primo passo è fare in modo che essa sia in grado di autenticarsi mediante Azure AD. Come già anticipato nel paragrafo precedente, per raggiungere tale scopo andremo ad utilizzare il client secret, il client id ed il tenant id di Azure AD. Aggiunti quindi in configurazione lato server i rispettivi parametri, andremo prima a creare un nuovo controller PowerBiController per poi utilizzarli al suo interno per recuperare un token valido da Azure Ad.

 [HttpGet]
 public async Task<IActionResult> Get()
 {
     var app = ConfidentialClientApplicationBuilder.Create(applicationOptions.ClientId)
         .WithTenantId(applicationOptions.TenantId)
         .WithClientSecret(applicationOptions.ClientSecret)
         .Build();

     var result = await app.AcquireTokenForClient(applicationOptions.Scopes)
         .ExecuteAsync();

     return Ok(new AuthApplicationResult
     {
         AccessToken = result.AccessToken,
         ExpiresOn = result.ExpiresOn,
         IdToken = result.IdToken
     });
 }

Sempre all’interno del controller andremo a utilizzare anche un metodo che restituisca al client le informazioni base relative al report che desideriamo recuperare da Power Bi, anche queste salvate in configurazione.

[HttpGet("config/{reportName}")]
public IActionResult GetReportConfig(string reportName)
{
     var report = reportOptions.Value.Reports.FirstOrDefault(t => t.ReportName == reportName);
     if (report == null)
         return NotFound();

     return Ok(new ReportConfiguration
     {
         Dataset = reportOptions.Value.Dataset,
         ReportId = report.ReportId,
         ReportDisplayName = report.ReportDisplayName,
         ReportName = report.ReportName
     });
}

Frontend Blazor

Spostandoci ora lato client, ciò che dobbiamo fare è recuperare tutte le informazioni relative al report e mostrarle all’interno di una pagina web.

Continuando a procedere con ordine andiamo dunque subito a realizzare una pagina Razor che vada a richiamare il servizio PowerBi/config/{ReportName} esposto dal server.

@if (Loading)
{
    <MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
}
else
{
    <ReportComponent ReportOptions="@options" EmbedReportId="@embedReportId">
    </ReportComponent>
}

@code{
    [Parameter]
    public string ReportName { get; set; }
    private bool Loading { get; set; }

    private EmbeddedReportParameters options;
    private string embedReportId;
    private ReportConfiguration reportData;

    protected override async Task OnParametersSetAsync()
    {
        Loading = true;
        try
        {
            logger.LogInformation($"OnParametersSetAsync {ReportName}");
            await FillReportData();

            breadcrumbService.LoadBreadcrumb(new List<BreadcrumbItem>(1) {
                new BreadcrumbItem("Reports", href: null, disabled: true),
                new BreadcrumbItem(reportData.ReportDisplayName, href: $"/reports/show/{ReportName}", disabled: false)
            });
        }
        finally
        {
            Loading = false;
        }

    }
    
    private async Task FillReportData()
    {
        reportData = await client.GetFromJsonAsync<ReportConfiguration>($"PowerBI/config/{ReportName}");
        embedReportId = reportData.ReportId;	
        options = new EmbeddedReportParameters
        {
               Datasets = new Dataset[] { new Dataset() { Id = reportData.Dataset } },
               Reports = new Report[] { new Report() { AllowEdit = false, Id =  embedReportId} }
        };	
    }
}

Il componente Razor ComponentReport, che viene visualizzato nella nostra pagina, avrà al suo interno un semplice div che farà da contenitore per il nostro report e svolgerà principalmente due funzioni basilari:

  • Appena il componente viene inizializzato richiama il metodo del controller per recuperare il token da Azure Ad.
  • Una volta completato il proprio caricamento, invoca una funzione javascript denominata drawBi, alla quale verranno passati come parametri il token di Azure AD, l’id del report che desideriamo recuperare e le altre informazioni relative al DataSet.
<div id="embedContainer" style="height:85vh;margin-top:5px"></div>

@code {
    [Parameter]
    public EmbeddedReportParameters ReportOptions { get; set; }

    [Parameter]
    public string EmbedReportId { get; set; }

    private string accessTokenAAD = string.Empty;
    private string email = string.Empty;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var authResult = await Http.GetFromJsonAsync<AuthApplicationResult>("PowerBI");
            accessTokenAAD = authResult.AccessToken;
           
        }
        catch(Exception ex)
        {
            logger.LogError(ex, "OnInitializedAsync");
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            if (!firstRender && !string.IsNullOrEmpty(accessTokenAAD))
            {
                await jsRuntime.InvokeVoidAsync("drawBi", accessTokenAAD, EmbedReportId, ReportOptions);
            }
        }
        catch(Exception ex)
        {
            logger.LogError(ex, "OnAfterRenderAsync");
        }
    }
}

Per poter integrare completamente ed agevolmente il report di Power Bi, è necessario installare un pacchetto Nuget, il quale implementa al suo interno una libreria javascript per interfacciarsi con il portale.

Ciò che viene effettuato a questo punto all’interno del nostro javascript è innanzitutto autenticarsi e recuperare un token da Power Bi. Questo è possibile grazie al token già recuperato da Azure AD e alla configurazione precedentemente effettuata su i due portali.

async function getEmbeddedToken(bearerToken, payload) {
    let response = await fetch('https://api.powerbi.com/v1.0/myorg/GenerateToken',
        {
            method: 'POST',
            body: JSON.stringify(payload),
            headers: {
                'Content-type': 'application/json; charset=UTF-8',
                Authorization: 'Bearer ' + bearerToken,
            },
        }
    );
    if (response.ok) {
        let biResponse = await response.json();
        return biResponse;
    }
}

Mediante questo secondo token è possibile finalmente richiamare le API di Power Bi per recuperare interamente il report e caricarlo all’interno del nostro div contenitore e, grazie al pacchetto installato in precedenza, sarà possibile utilizzarlo come se fosse parte integrante della piattaforma.

function embedPowerBIReport(biToken, embedReportId) {
    let embedConfiguration = {
        accessToken: biToken,
        embedUrl: 'https://app.powerbi.com/reportEmbed',
        id: embedReportId,
        permissions: models.Permissions.All,
        tokenType: models.TokenType.Embed,
        type: 'report',
        filters: [],
        settings: {
            panes: {
                filters: {
                    visible: false,
                },
                pageNavigation: {
                    visible: true,
                },
            },
        },
    };

    let embedContainer = document.getElementById('embedContainer');
    let report = powerbi.embed(embedContainer, embedConfiguration);
}

A questo punto abbiamo raggiunto il risultato desiderato, e il report è caricato e pienamente fruibile all’interno della nostra pagina.

Andrea Tosato, Sr. Consultant

Walter Mongelluzzo, Consultant

Share This