Search code examples
powerbipowerbi-embedded

Power BI Embedded - saving report to different workspace results in error


I'm having a problem saving a report across different workspaces in PowerBI embedded. For background:

  • We're using PowerBI embedded in an App Owns Data scenario
  • We're operating a SaaS, multi-tenanted single database approach where we're segregating tenant data in PowerBI using RLS
  • We're using a singular dataset that will be shared across multiple workspaces (which I believe is supported in V2 workspaces according to Microsoft documentation)
  • The scopes for the Service Principal are defined correctly and we have a dedicated capacity purchased & configured
  • We're using .NET6 and PowerBI embedded is being displayed in a Razor view.

If I use the built in PowerBI save functionality, it always saves to the workspace where the dataset resides (and it works perfectly fine, which makes be believe the issue is not down to the EmbedToken being generated which is why I've omitted it here).

I've implemented functionality in the Razor View as per below (based on the official Microsoft documentation) to save the report to a defined workspace using the targetWorkspaceId defined in the saveAsParameters.

<script>
    var accessToken = '@Model.EmbedToken.Token';

    var models = window['powerbi-client'].models;

    var config = {
        tokenType: models.TokenType.Embed,
        accessToken: accessToken,
        datasetId: '@Model.DatasetId.ToString()',
        embedUrl: 'https://embedded.powerbi.com/ReportEmbed',
        permissions: models.Permissions.All,
        settings: {
            useCustomSaveAsDialog: true
        }
    };

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

    // Embed the report and display it within the div container.
    let report = powerbi.createReport(reportContainer, config);

    report.on("saveAsTriggered", function (event) {
        showModal();
    });

    function showModal() {
        $('#savereport').modal('show');
    }

    function saveAs() {
        let saveAsParameters = {
            name: $('#ReportName').val(),
            targetWorkspaceId: '@Model.WorkspaceId'
        }

        var result = report.saveAs(saveAsParameters);
    }

    // add event handler to load existing report afer saving new report
    report.on("saved", function (event) {
        alert("Report Saved");
        window.location.href = "/Reporting/View?Id=" + event.detail.reportObjectId;
    });

</script>

I can see from the JSON generated in the request that the target workspace is being called.

Failing JSON

And the response is 403 Forbidden

Fiddler forbidden

The PowerBI Embedded error screen

Power BI Error

Interestingly, it even fails if I try to save to the workspace where the dataset resides which is confusing me.

If I omit the targetWorkspaceId from the saveAsParameters it saves absolutely fine - just not in the desired workspace! Does anybody have any ideas?


Solution

  • Thanks to Andrey for pointing me to the documentation. The issue was I was using GenerateTokenRequest within the C# SDK whereas I should have been using GenerateTokenRequestV2. Working code is below.

    public async Task<EmbedConfig> CreateNewReport()
        {
            try
            {
                var result = new EmbedConfig { };
                var accessToken = await GetPowerBIAccessTokenAsync();
                var tokenCredentials = new TokenCredentials(accessToken, "Bearer");
    
                using var client = new PowerBIClient(new Uri(_powerBISettings.ApiUrl), tokenCredentials);
                var identities = new List<EffectiveIdentity> { new EffectiveIdentity(username: _tenant.Id.ToString(), roles: new List<string> { "Tenant" }, datasets: new List<string> { _powerBISettings.DatasetId.ToString() }) };
                
                // old method
                //var generateTokenRequestParameters = new GenerateTokenRequest(TokenAccessLevel.Create, identities: identites, datasetId: _powerBISettings.DatasetId.ToString(), allowSaveAs: true);
                //EmbedToken tokenResponse = await client.Reports.GenerateTokenForCreateInGroupAsync(_tenant.PowerBIWorkspaceId.Value, generateTokenRequestParameters);
    
                var generateTokenRequestParameters = new GenerateTokenRequestV2(datasets: new List<GenerateTokenRequestV2Dataset> { new GenerateTokenRequestV2Dataset(_powerBISettings.DatasetId.ToString()) }, targetWorkspaces: new List<GenerateTokenRequestV2TargetWorkspace> { new GenerateTokenRequestV2TargetWorkspace(_tenant.PowerBIWorkspaceId.Value) }, identities: identities );
                EmbedToken tokenResponse = await client.EmbedToken.GenerateTokenAsync(generateTokenRequestParameters);
    
                result.EmbedToken = tokenResponse;
                result.DatasetId = _powerBISettings.DatasetId.ToString();
                result.WorkspaceId = _tenant.PowerBIWorkspaceId?.ToString() ?? throw new NullReferenceException("No Power BI workspace set");
    
                return result;
            }
            catch (Exception exception)
            {
                throw;
            }
        }