report

Hosting Power BI Content - 2/6

I will start from working code to guide my explanation. I think this is an effective way to explain things. As said, the language used is F#. The full project is in [github](https://github.com/franco

October 14, 2017

I will start from working code to guide my explanation. I think this is an effective way to explain things. As said, the language used is F#. The full project is in github. The first F# module, presented in this post is the PowerBI module.

PowerBI.fs

This file implements a module which wraps the PBI SDK .NET API. The module features three important operations with the PBI cloud service, which need to be performed in different moments during the lifetime of the application. To better understand this let's adopt a metaphor where our application is a powerpoint presentation, the PBI account is an archive containing folders (PBI workspaces) which in turns contain paper reports. Our job is to build the presentation including in it copies of the reports in the archive. The operations are

  • Gain access to PBI account files. We identify ourselves to the archive owner and ask for the key to open the archive. This key has a time expiration, after this we need to ask for a new key

  • Collect PBI resources metadata. With the key we open the archive. We are now looking at the archive directory where all the folders are listed by their name; for each folder we also can consult the list of reports in them

  • Obtain embed metadata. We now ask the archive owner the permission to grab the reports we want for our presentation; we obtain an electronic copy of the report that will expire after a while and we will need to refresh it

Gain access to PBI account files

Let's see how to get access to PBI [code language="fsharp"] let private powerBiClientRefresh () = let ``as`` = ConfigurationManager.AppSettings let credential = new UserPasswordCredential(Credential.User1.Email, Credential.User1.Password); let authenticationContext = new AuthenticationContext(``as``.["AuthenticationAuthorityURL"]) let authenticationResult = authenticationContext.AcquireTokenAsync(``as``.["PowerBIAuthURL"], ``as``.["AppClientId"], credential) |> Async.AwaitTask |> Async.RunSynchronously let tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer") new PowerBIClient(new Uri(``as``.["PowerBIApiURL"]), tokenCredentials)

let mutable private powerBiClient = powerBiClientRefresh()[/code]

Almost all the specific settings are in the web.config file section, except the user credentials (red), for privacy reasons.

Note: if you clone the github repo, you will find a dependency on a NCF.Private project, which contains the credentials in the original implementation. This project not fully published. Anyway, you don't need it. To get it working, just remove the reference to NCF.Private from NCF.BPP references and replace Credential.User1.Email and Credential.User1.Password with your account's credentials.

The relevant entries are:

First, an authorization context is created out of the account credentials. The context is then used to acquire a PBI access token. Ultimately an instance of the PBI API PowerBIClient .NET class is created; this object is the one used to access the PBI resources defined in the used account.

For this to work:

  • The user credentials must be those of an existing Microsoft account

  • An application Id must be obtained by registering your application with Microsoft Azure

The second step can be completed in the Azure Portal, but there is a easier way through a specific Power BI online registering tool available at https://dev.powerbi.com/apps. A complete documentation page is available at https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-register-app/. My application uses the app-owns-data scheme (users don't need to have PBI accounts), with read-only access:

Once we click on the "Register App" button at the bottom of the form (not shown here) we get the application id to copy into the AppClientId setting in web.config.

Note that powerBiClient is mutable. The created PowerBIClient instance is cached by this binding and reused by our application for successive requests until something invalidates it (e.g. expiration). Later we'll see when this event is detected and the object regenerated by repeating the cloud procedure.

Collect PBI resources metadata

With our powerBIClient object, we can now read what's in it: [code language="fsharp"] type PBIGroup = Group type PBIReport = Report

type Report = { report : PBIReport mutable token : EmbedToken }

type Group = { group : PBIGroup reports : Map<string, Report> }

type Workspaces = Map<string, Group>

let private getReports gid = powerBiClient.Reports.GetReportsInGroup(gid).Value |> Seq.fold (fun (m:Map<string, Report>) (r:PBIReport) -> Map.add r.Id {report = r; token = EmbedToken()} m) Map.empty

let internal getGroups () = powerBiClient.Groups.GetGroups().Value |> Seq.map (fun g -> g, getReports g.Id) |> Seq.fold (fun m (g, rs) -> Map.add g.Id {group = g; reports = rs} m) Map.empty [/code]

The PBI API uses "Group" to indicate Workspace. These two functions produce a map of workspaces containing maps of reports. This metadata is cached in the State module explained later. Caching serves to reduce at the minimum the expensive queries to the cloud.

Obtain embed metadata

Now we know what reports are available. To use them in our application, though, we need to ask a special permission. This permission is called embed token, and must be requested to the PBI authority through the powerBIClient object initially created.

[code language="fsharp"] let private generateTokenRequestParameters = new GenerateTokenRequest(accessLevel = "view")

let internal getEmbedToken gId rId = try let token = powerBiClient.Reports.GenerateTokenInGroup(gId, rId, generateTokenRequestParameters) Ok (token.Token, token.Expiration) with _ -> powerBiClient <- powerBiClientRefresh() try let token = powerBiClient.Reports.GenerateTokenInGroup(gId, rId, generateTokenRequestParameters) Ok (token.Token, token.Expiration) with x -> Error x.Message [/code]

getEmbedToken asks PBI cloud services an embed token for a specific element id (rId) belonging to a workspase gId. If the powerBIClient object is invalid, an exception is generated. In this case, the client object is regenerated and we try again to get the embed token; if the request fails the second time as well, then we return an error that will propagate up to the user and displayed as a browser alert (we'll see this in one of the next posts).

Summary

The F# module presented in this post wraps the PBI .NET API. Its code is used only when the application needs to interrogate the PBI cloud services, which occurs:

  • At the startup of the application. When the state is created for the first time (more on this in the next post on the State module). During this phase, the PowerBIClient API object is created (the REST protocol to the various remote web servers is hidden inside the API), and after that the two level directory Workspaces/Reports is downloaded and cached into the application state. This state is rendered to the client browser and the user is allowed to select reports through hyperlinks

  • When a client asks to see a report by clicking in one of the hyperlinks. The browser sends an AJAX request to our server, asking for the embed token. At server side, our application checks whether the embed token is cached (this is true if the report has already been asked previously); if the cache doesn't contain the token or if the cached token is expired, then again the PBI cloud service are interrogated to grant a new embed token; this new token is cached for future requests and then delivered to the browser.

  • If the principal access permit obtained at startup becomes invalid. This occurs mainly when a new embed token is needed after the main access permit has expired or has become invalid for other reasons. The permit is re-instated and the embed token is then requested again.

Conclusion

In the next post I will be publishing another F# module in the project: the State module. Stay tuned.

Related articles