Power BI Report

PowerBIReport shows a Power BI report in the portal to users who have no Power BI license, with each viewer seeing only their own rows. It is in its own package — PowerPortalsPro.Web.Blazor.PowerBI for Blazor, @powerportalspro/react-powerbi for React — so a portal that shows no reports doesn't carry Power BI's script.

React Blazor

How it works

The component names a report by a key. The portal looks the key up, decides whether the viewer may see the report, and asks Power BI for a token for them — as the identity it already connects to Dataverse as, so there is nothing more to configure in the portal. The token carries the viewer's id as the username: their contact id, or their user id when they are staff signed in to the portal. Visitors who aren't signed in get the username anonymous. The component renews the token a few minutes before it expires, and the report keeps its state when it does.

Each viewer may ask for report tokens 30 times a minute — a signed-in user by their account, a visitor by their address — and is refused with a 429 beyond that. A token is reused while it has time to run, so a page never comes near it; the limit stops one viewer turning repeated requests into a load on the portal or Power BI. Change it with PowerPortalsPro:PowerBIRateLimit:PermitLimit and Window in configuration; a PermitLimit of 0 turns it off.

Registering a report

Which reports a portal may show, and to whom, is data in Dataverse rather than code, because each environment embeds reports from its own workspace. Two tables in the Power Portals Pro solution hold it:

  • Portal Power BI Report — the key a component names, and the Power BI workspace and report it stands for. Allow Without Row-Level Security is off by default: a report whose model has no row-level security is refused, because every viewer would see every row.
  • Portal Power BI Report Access — who may see the report, and which of its model's roles they get. Each row is for holders of a portal role, every signed-in user, anonymous visitors, or internal users — staff signed in as themselves rather than as a contact — and names the model's roles in Power BI Roles, separated by commas.

A viewer sees the report when any of its access rows applies to them, with every role the rows that apply name. A key that names no report and a report that isn't for the viewer look the same to them: a message that the report isn't available. Registrations are read every fifteen minutes; clear the PowerBIReports cache to apply a change at once.

Row-level security

Power BI applies the report's row-level security to the token's username. To keep each viewer to their own rows, give the model a role that compares the column holding a contact id with USERNAME():

The portal checks the model before it asks for a token. A model with row-level security gets the viewer's id and roles in every token, and an access row that names no role is refused. Power BI doesn't check role names, so a misspelled role fails only when the report draws: the MCP server checks them against the model when it writes access rows.

Custom data

A role can filter on something other than who the viewer is — their account's number, their region — through DAX's CUSTOMDATA(). Register an IPowerBICustomDataProvider in the server's Program.cs: it is asked for every token the portal issues for an item whose model uses row-level security, with the viewer and the item, and what it returns goes into the token. It runs on the server and is never taken from the browser, because it decides which rows a viewer sees. It isn't asked for an item shown with the viewer's own Power BI access.

The MCP server's add_powerbi_rls_role writes such a role with filter: "customdata", comparing a text or number column. Its filter also requires custom data in the token, so a token without any shows no rows, rather than every row whose column is blank.

Setting it up with the MCP server

The MCP server sets up everything but the report itself, acting as a Power BI account you sign in as once per folder with dnx PowerPortalsPro.Mcp powerbi login. That account needs a Power BI Pro license. Ask your assistant to set up a report, or call the tools yourself:

  1. create_powerbi_workspace creates the workspace you publish the report into from Power BI Desktop.
  2. grant_powerbi_workspace_access makes the portal's identity a Member of it.
  3. add_powerbi_rls_role writes a role into the report's model: one keeping a viewer to their own contact's rows, their company's, their company's and its subsidiaries', those a mapping table lists against them, or fixed values — or one showing every row. It gives the DAX to add the same role in Power BI Desktop, because publishing the report again replaces its model.
  4. register_powerbi_report and set_powerbi_report_access write the two Dataverse tables.
  5. check_powerbi checks the whole chain, signed in as the portal itself.

Two Power BI tenant settings are a Fabric administrator's to change: Service principals can call Fabric public APIs and Embed content in apps. New tenants have both on. When either is off, check_powerbi says which one to turn on.

Opening on a page, filtered to a record

PageName opens the report on a page, and Filters narrows it to rows whose column holds one of the values — the account a detail page is about, say. Changing either applies to the report already shown, without reloading it. A filter is a convenience, not security: the viewer can remove it in their browser. What they may see is decided by row-level security.

React Blazor

Dashboards and single tiles

A registration can name a dashboard, or one tile of a dashboard, instead of a report — its Item Type, with the dashboard's id and the tile's. The same component shows it: PowerBIReport embeds whatever the key's registration names. A dashboard shows fitted to its width, and OnTileClicked (onTileClicked) says when a tile is clicked and where it leads. A page, filters, panes and the portal's colors apply to a report only: a dashboard or tile shows as it was made.

A dashboard's tiles can come from several semantic models, and each is held to the same rules as a report's: a model with row-level security gets the viewer's identity and roles, and one without is refused unless the registration allows it. A tile of text or an image reads no rows and needs neither. register_powerbi_report takes dashboard, and tile for one of its tiles, and list_powerbi_reports lists a workspace's dashboards and tiles. Tiles are pinned in the Power BI service; there's no way to pin one from outside it.

Each viewer's own Power BI access

A registration's Embed Mode can show the item with each viewer's own Power BI access instead of the portal's, as they would see it in Power BI. Power BI applies its own sharing and the models' row-level security to them — USERPRINCIPALNAME() is the name they sign in with — so each viewer needs a work or school account, a Power BI license, and the item shared with them. The access rows still say who may ask; their Power BI roles aren't used. It suits staff and partners who use Power BI already. For customers, the portal's own access needs no license.

The first time, the component offers to connect the viewer's work account to Power BI. They sign in with Microsoft, agree to let the portal read their Power BI reports and dashboards, and come back to the page. What that gives the portal stays on the server, encrypted in the host's IDistributedCache; the browser only ever gets a short-lived access token. With the in-memory cache, viewers connect again after the portal restarts, and a shared cache (Redis, SQL Server) keeps them connected. Signing out disconnects them. A personal Microsoft account, Google or a password can't connect, and nor can a session someone is impersonating.

Viewers connect through the portal's Microsoft sign-in app registration, which needs Power BI's delegated Report.Read.All and Dashboard.Read.All, Microsoft Graph's openid and offline_access, and the redirect URI https://<portal host>/signin-powerbi. The MCP server's add_powerbi_signin_permissions adds them all, and register_powerbi_report takes embedMode: "user". A tenant that only lets administrators consent needs one to grant it once. To let the tenant's guests in too, set PowerBIOptions.TenantId to the tenant that holds the workspaces.

Colors and dark mode

By default the report takes the portal's colors: its light or dark theme, its accent color, and the series palette the portal's own charts use. It follows the theme when the visitor switches it, without reloading. The colors are read from the Fluent theme where the report sits, and applied as a Power BI report theme, which sets the report's page, text, axes, filter pane and series. A color the report's author set on one visual stays as they set it, and Power BI draws the page tabs below the report in its own colors whatever the theme.

ThemeJson gives the report a theme of your own instead, in Power BI's theme JSON format; like any theme applied to an embedded report, it replaces the report's saved theme rather than adding to it. UsePortalTheme="false" keeps the theme the report was saved with.

React Blazor

Development and production

A workspace on no capacity gets Power BI's trial embed tokens, which are limited in number, show a banner, and are for development and testing. In production, put the workspace on a Fabric capacity: create_powerbi_capacity creates one in your Azure subscription after showing its price, and set_powerbi_capacity_state pauses it. A paused capacity draws no reports. Embedding for portal visitors isn't supported in a Premium Per User workspace.

React Blazor

PowerBIReport Class

Parameters

Name
Type
Default
Description
FilterPaneVisiblebool
False
FiltersIReadOnlyList<PowerBIFilter>?
Heightstring
600px
PageNamestring?
PageNavigationVisiblebool
True
ReportKey*string
ThemeJsonstring?
TransparentBackgroundbool
False
UsePortalThemebool
True
Widthstring
100%
Name: FilterPaneVisible
Type: bool
Default: False
Name: Filters
Type: IReadOnlyList<PowerBIFilter>?
Name: Height
Type: string
Default: 600px
Name: PageName
Type: string?
Name: PageNavigationVisible
Type: bool
Default: True
Name: ReportKey*
Type: string
Name: ThemeJson
Type: string?
Name: TransparentBackground
Type: bool
Default: False
Name: UsePortalTheme
Type: bool
Default: True
Name: Width
Type: string
Default: 100%

Events

Name
Type
Description
OnDataSelectedEventCallback<PowerBIDataSelection>
OnErrorEventCallback<string>
OnLoadedEventCallback
OnPageChangedEventCallback<PowerBIPage>
OnRenderedEventCallback
OnTileClickedEventCallback<PowerBITileClick>
Name: OnDataSelected
Type: EventCallback<PowerBIDataSelection>
Name: OnError
Type: EventCallback<string>
Name: OnLoaded
Type: EventCallback
Name: OnPageChanged
Type: EventCallback<PowerBIPage>
Name: OnRendered
Type: EventCallback
Name: OnTileClicked
Type: EventCallback<PowerBITileClick>

Methods

Name
Parameters
Type
Description
NotifyDataSelectedAsyncPowerBIDataSelection selection
Task
NotifyErrorAsyncstring message
Task
NotifyLoadedAsyncTask
NotifyPageChangedAsyncPowerBIPage page
Task
NotifyRenderedAsyncTask
NotifyTileClickedAsyncPowerBITileClick click
Task
RenewTokenAsyncTask<PowerBIEmbedTokenResponse>
Name: NotifyDataSelectedAsync
Parameters: PowerBIDataSelection selection
Type: Task
Name: NotifyErrorAsync
Parameters: string message
Type: Task
Name: NotifyLoadedAsync
Type: Task
Name: NotifyPageChangedAsync
Parameters: PowerBIPage page
Type: Task
Name: NotifyRenderedAsync
Type: Task
Name: NotifyTileClickedAsync
Parameters: PowerBITileClick click
Type: Task
Name: RenewTokenAsync
Type: Task<PowerBIEmbedTokenResponse>