Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embed Power BI Report In ASP.Net Core Website

I have an ASP.Net core 1.1 website and I want to embed power BI reports into the site.

Azure Hosted Data documentation: https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-embed-sample-app-owns-data/

Using the App Owns Data sample found at https://github.com/Microsoft/PowerBI-Developer-Samples I have a working embedded reports solution using the sample. However, the sample project is running on .Net Framework 4.5.2 and When trying to migrate the solution into my .Net core application I have migrated the code however the ActiveDirectorylibrary (Microsoft.IdentityModel.Clients.ActiveDirectorylibrary) in .Net Core does not contain a method for UserPasswordCredential

        var credential = new UserPasswordCredential(Username, Password);

The solution that I found recommended online for ASP.Net core is to use a tag helper however now that the Power BI Embedded and Power BI service have converged with the new arrival of Power BI Premium I dont think think this solutions is possible due to the depencency on token authentication for the App Hosted data.

Full Report Controller Method:

            public class ReportController : Controller
    {
        private static readonly string Username = ConfigurationManager.AppSettings["pbiUsername"];
        private static readonly string Password = ConfigurationManager.AppSettings["pbiPassword"];
        private static readonly string AuthorityUrl = ConfigurationManager.AppSettings["authorityUrl"];
        private static readonly string ResourceUrl = ConfigurationManager.AppSettings["resourceUrl"];
        private static readonly string ClientId = ConfigurationManager.AppSettings["clientId"];
        private static readonly string ApiUrl = ConfigurationManager.AppSettings["apiUrl"];
        private static readonly string GroupId = ConfigurationManager.AppSettings["groupId"];

    public async Task<ActionResult> EmbedReport()
    {
        // Create a user password cradentials.
        var credential = new UserPasswordCredential(Username, Password);

        // Authenticate using created credentials
        var authenticationContext = new AuthenticationContext(AuthorityUrl);
        var authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, ClientId, credential);

        if (authenticationResult == null)
        {
            return View(new EmbedConfig()
            {
                ErrorMessage = "Authentication Failed."
            });
        }

        var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");

        // Create a Power BI Client object. It will be used to call Power BI APIs.
        using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
        {
            // Get a list of reports.
            var reports = await client.Reports.GetReportsInGroupAsync(GroupId);

            // Get the first report in the group.
            var report = reports.Value.FirstOrDefault();

            if (report == null)
            {
                return View(new EmbedConfig()
                {
                    ErrorMessage = "Group has no reports."
                });
            }

            // Generate Embed Token.
            var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
            var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(GroupId, report.Id, generateTokenRequestParameters);

            if (tokenResponse == null)
            {
                return View(new EmbedConfig()
                {
                    ErrorMessage = "Failed to generate embed token."
                });
            }

            // Generate Embed Configuration.
            var embedConfig = new EmbedConfig()
            {
                EmbedToken = tokenResponse,
                EmbedUrl = report.EmbedUrl,
                Id = report.Id
            };

            return View(embedConfig);
        }
    }
}

What I have tried:

Adding a reference to .Net Framework 4.6.1 in the .Net Core project to expose the .Net Framework and allow the use of the .Net equivalent of the IdentityModel.Clients.ActiveDirectory but that did not seem to help.

How can I fix the library issue and embedding in .Net core?

EDIT - Answer

Based off of the answer provided by @Fei Xue I wrote an HTTP helper class that performed a post to the API. Based off of the returned JSON I create an object and used the now available authentication token

Helper Class:

  #region Settings

    public static string BaseUrl
    {
        get
        {
            return "https://login.microsoftonline.com/common/oauth2/token";
        }
    }

    #endregion

    public static async Task<HttpResponseMessage> MakeAsyncRequest(string url, Dictionary<string, string> content)
    {
        var httpClient = new HttpClient
        {
            Timeout = new TimeSpan(0, 5, 0),
            BaseAddress = new Uri(url)
        };

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

        if (content == null)
        {
            content = new Dictionary<string, string>();
        }

        var encodedContent = new FormUrlEncodedContent(content);

        var result = await httpClient.PostAsync(httpClient.BaseAddress, encodedContent);

        return result;
    }

Object:

 public class AAD
{
    public string token_type { get; set; }
    public string scope { get; set; }
    public string expires_in { get; set; }
    public string ext_expires_in { get; set; }
    public string expires_on { get; set; }
    public string not_before { get; set; }
    public string resource { get; set; }
    public string access_token { get; set; }
    public string refresh_token { get; set; }
}

Call from the controller:

            var url = APIHelper.BaseUrl;
        var content = new Dictionary<string, string>();
        content["grant_type"] = "password";
        content["resource"] = "https://analysis.windows.net/powerbi/api";
        content["username"] = "<username>";
        content["password"] = "<password>";
        content["client_id"] = "<clientid>";

        var response = await APIHelper.MakeAsyncRequest(url, content);
        var result = response.Content.ReadAsStringAsync().Result;
        var AAD = JsonConvert.DeserializeObject<AAD>(result);
like image 461
Chad Bonthuys Avatar asked Aug 03 '17 09:08

Chad Bonthuys


People also ask

Is it possible to create Power BI reports from ASP NET platform?

No dear its not possible to create power bi reports from .Net platform however you can integrate power Bi reports to your .Net Application via iframe after publish online, you can find this procedure here, i just recently post answer here, How to integrate PowerBI in ASP.NET applications to generate PowerBI reports?

How do I embed Power BI reports in Azure AD?

In order to embed Power BI content (such as reports and dashboards), your app needs to get an Azure AD token. To get the token, you need a configuration object. The code in this section uses the .NET Core dependency injection pattern.

How do I embed Power BI reports and dashboards into a Div?

To do this easily, you use the Power BI client libraries that work for any browser application (which also includes embedding a Web view into a desktop or native mobile app), and Power BI renders your reports and dashboards into the content of a div that you identify within your pages. Let's dive right into building a sample.

Can Power BI Embedded reporting bring interactive and paginated reports to applications?

As you move beyond the Report Viewer and transition to using the Power BI embedded capabilities, application developers can use a single set of APIs to bring both interactive and paginated reports to their modern applications, far surpassing the capabilities ever offered to date.


2 Answers

The resource owner password credentials flow is not supported for the .Net core for Active Directory Authentication Library.

As a workaround, you can compose the HTTP request directly. Here is a example for your reference:

POST https://login.microsoftonline.com/{tenant}/oauth2/token 
Content-Type: application/x-www-form-urlencoded

grant_type=password
&resource={resource}
&username={username}
&password={password}
&client_id={clientid}
&client_secret={client_secret}//if it is confidential app
like image 53
Fei Xue - MSFT Avatar answered Oct 09 '22 09:10

Fei Xue - MSFT


Not sure how many more folks will come across this question in regards to adapting the PowerBI embedded App-Owns-Data sample code to .Net core, but I was able to successfully get things up and running by way of melding Chad's sample classes with the code from this post from the powerBI community.

The key (for me at least) was following the suggestion within the PowerBI post to swap this code in the original sample:

var credential = new UserPasswordCredential(Username, Password);
// Authenticate using created credentials
var authenticationContext = new AuthenticationContext(AuthorityUrl);
var authenticationResult = await 
authenticationContext.AcquireTokenAsync(ResourceUrl, ClientId, credential);

with this bit (AAD being the returned object from Chad's sample):

var authenticationResult = AAD;

which also provides an actual embed token without calling UserPasswordCredential.

Full controller code below, hopefully it can save someone else a few hours of digging. Thanks Chad and Fei Xue for the sample! Cheers.

public class DashboardController : Controller
{

    private static readonly string Username = "yourUN";
    private static readonly string Password = "yourPW";
    private static readonly string AuthorityUrl = "https://login.windows.net/common/oauth2/authorize/";
    private static readonly string ResourceUrl = "https://analysis.windows.net/powerbi/api";
    private static readonly string ClientId = "yourClientId";
    private static readonly string ApiUrl = "https://api.powerbi.com";
    private static readonly string GroupId = "yourGroupId";
    private static readonly string ReportId = "yourReportId";


    public class APIHelper 
    {
        #region Settings

        public static string BaseUrl
        {
            get
            {
                return "https://login.microsoftonline.com/common/oauth2/token";
            }
        }

        #endregion

        public static async Task<HttpResponseMessage> MakeAsyncRequest(string url, Dictionary<string, string> content)
        {
            var httpClient = new HttpClient
            {
                Timeout = new TimeSpan(0, 5, 0),
                BaseAddress = new Uri(url)
            };

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

            if (content == null)
            {
                content = new Dictionary<string, string>();
            }

            var encodedContent = new FormUrlEncodedContent(content);

            var result = await httpClient.PostAsync(httpClient.BaseAddress, encodedContent);

            return result;
        }    
    }


    public class AAD
    {
        [JsonProperty("token_type")]
        public string TokenType { get; set; }
        [JsonProperty("scope")]
        public string Scope { get; set; }
        [JsonProperty("experies_in")]
        public int ExpiresIn { get; set; }
        [JsonProperty("ext_experies_in")]
        public int ExtExpiresIn { get; set; }
        [JsonProperty("experies_on")]
        public int ExpiresOn { get; set; }
        [JsonProperty("not_before")]
        public int NotBefore { get; set; }
        [JsonProperty("resource")]
        public Uri Resource { get; set; }
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }
        [JsonProperty("refresh_token")]
        public string RefreshToken { get; set; }
    }




    public async Task<ActionResult> Index(string username, string roles)
    {
        var url = APIHelper.BaseUrl;
        var content = new Dictionary<string, string>();
        content["grant_type"] = "password";
        content["resource"] = "https://analysis.windows.net/powerbi/api";
        content["username"] = Username;
        content["password"] = Password;
        content["client_id"] = ClientId;

        var response = await APIHelper.MakeAsyncRequest(url, content);
        var tokenresult = response.Content.ReadAsStringAsync().Result;
        var AAD = JsonConvert.DeserializeObject<AAD>(tokenresult);

        var result = new EmbedConfig();
        try
        {
            result = new EmbedConfig { Username = username, Roles = roles };
            var error = GetWebConfigErrors();
            if (error != null)
            {
                result.ErrorMessage = error;

                return View(result);
            }

            // Create a user password cradentials.
            var authenticationResult = AAD;

            if (authenticationResult == null)
            {
                result.ErrorMessage = "Authentication Failed.";
                return View(result);
            }

            var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");

            // Create a Power BI Client object. It will be used to call Power BI APIs.
            using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
            {
                // Get a list of reports.
                var reports = await client.Reports.GetReportsInGroupAsync(GroupId);

                Report report;
                if (string.IsNullOrEmpty(ReportId))
                {
                    // Get the first report in the group.
                    report = reports.Value.FirstOrDefault();
                }

                else
                {
                    report = reports.Value.FirstOrDefault(r => r.Id == "3a447f03-ad31-4e78-a3f1-2a6c008fcd8e");
                }

                if (report == null)
                {
                    result.ErrorMessage = "Group has no reports.";
                    return View(result);
                }

                var datasets = await client.Datasets.GetDatasetByIdInGroupAsync(GroupId, report.DatasetId);
                result.IsEffectiveIdentityRequired = datasets.IsEffectiveIdentityRequired;
                result.IsEffectiveIdentityRolesRequired = datasets.IsEffectiveIdentityRolesRequired;
                GenerateTokenRequest generateTokenRequestParameters;
                // This is how you create embed token with effective identities

                generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");



                var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(GroupId, report.Id, generateTokenRequestParameters);

                if (tokenResponse == null)
                {
                    result.ErrorMessage = "Failed to generate embed token.";
                    return View(result);
                }

                // Generate Embed Configuration.
                result.EmbedToken = tokenResponse;
                result.EmbedUrl = report.EmbedUrl;
                result.Id = report.Id;

                return View(result);

            }
        }
        catch (HttpOperationException exc)

        {
            result.ErrorMessage = string.Format("Status: {0} for ID: ({1})\r\nResponse: {2}\r\nRequestId: {3}", exc.Response.StatusCode, (int)exc.Response.StatusCode, exc.Response.Content, exc.Response.Headers["RequestId"].FirstOrDefault());
        }
        catch (Exception exc)
        {
            result.ErrorMessage = exc.ToString();
        }

        return View(result);

    }
}
like image 43
HunterTheGatherer Avatar answered Oct 09 '22 08:10

HunterTheGatherer