Register now to learn Fabric in free live sessions led by the best Microsoft experts. From Apr 16 to May 9, in English and Spanish.
Hello,
I have an application where we want to show PowerBI Premium reports in our application. However, before that happens, we also need to upload PBIX reports using our application. I have been able to get the proper token, and when I run any get requests (like GetReports), on the API, I get the data back just fine.
However, when I run an Import on the api, it always says 400 Error Bad Request. I am going to post my code below and would love any help I could get. I have tried reading in the file from disk, instead of passing it in using FileSelctor tool, I have tried uising different types of requests, made sure I have all permissions assigned to my app through Azure Portal, ect... and nothing has worked. Pretty much I have exausted my thoughts on how to proceed, It is just strange that every read seems to work great, but a post will not.
All the setup seems to be in place. The only setup I have not done is create Workspace Collections in Azure. The reason for this is it was my understanding that this was part of the old embeded style. I do have a new AppWorkspace that was created using my Premium account in PowerBI itself. Not sure if this has something to do with why an import is not working or what, but wanted to give as much info as possible so I can get help ASAP.
Thank you all very much, and I look forward to hearing back from you.
Steven
public async Task Upload([FromUri] int ReportId, [FromUri] string reportName, [FromUri] bool overwrite) { try { if (!Request.Content.IsMimeMultipartContent("form-data")) { throw new HttpResponseException(HttpStatusCode.BadRequest); } var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataStreamProvider>(new InMemoryMultipartFormDataStreamProvider()); //access form data NameValueCollection formData = provider.FormData; //access files IList<HttpContent> files = provider.Files; //Example: reading a file's stream like below HttpContent file1 = files[0]; // Stream file1Stream = await file1.ReadAsStreamAsync(); byte[] fileToSend = await file1.ReadAsByteArrayAsync(); // Create a user password cradentials. var credential = new UserCredential(Username, Password); // Authenticate using created credentials var authenticationContext = new AuthenticationContext(AuthorityUrl); var authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, ClientId2, credential); token = authenticationResult.AccessToken; var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer"); var nameConflict = "Overwrite"; string responseContent = string.Empty; //Configure dashboards request System.Net.WebRequest request = System.Net.WebRequest.Create("https://api.powerbi.com/v1.0/myorg/imports?datasetDisplayName=TestImport&nameConflict=Overwrite") as System.Net.HttpWebRequest; request.Method = "POST"; // Set the content type of the data being posted. request.ContentType = "multipart/form-data"; // Set the content length of the string being posted. request.ContentLength = fileToSend.Length; request.Headers.Add("Authorization", String.Format("Bearer {0}", token)); using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(fileToSend, 0, fileToSend.Length); requestStream.Close(); } //Get dashboards response from request.GetResponse() using (var response = request.GetResponse() as System.Net.HttpWebResponse) { //Get reader from response stream using (var reader = new System.IO.StreamReader(response.GetResponseStream())) { responseContent = reader.ReadToEnd(); } } } catch (Exception ex) { var junk = true; } } }
Solved! Go to Solution.
For new reports just remove the name conflict parameter
@Anonymous
I see you are appending the nameConflict parameter to the URL, one most probably reason I can think of for your 400 error is that the url with nameConflict parameter would throw error if the report you'd like to import doesn't already exist.
https://api.powerbi.com/v1.0/myorg/imports?datasetDisplayName=TestImport&nameConflict=Overwrite
By the way, I don't find /groups/{groupid} in the URL. Do note the that you can only embed the reports from a created app workspace.
For better troubleshooting, I'd suggest you add an extra catch block.
catch (WebException wex) { if (wex.Response != null) { using (var errorResponse = (HttpWebResponse)wex.Response) { using (var reader = new StreamReader(errorResponse.GetResponseStream())) { string errorString = reader.ReadToEnd(); dynamic respJson = JsonConvert.DeserializeObject<dynamic>(errorString); Console.WriteLine(respJson.ToString()); //TODO: use JSON.net to parse this string and look at the error message } } } }
Also you can reference my import pbix file demo.
using System; using System.Net; //Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612 using Microsoft.IdentityModel.Clients.ActiveDirectory; //Install-Package Newtonsoft.Json using Newtonsoft.Json; using System.IO; using System.Threading.Tasks; namespace ConsoleApplication39 { class Program { //Step 1 - Replace {client id} with your client app ID. //To learn how to get a client app ID, see Register a client app (https://msdn.microsoft.com/en-US/library/dn877542.aspx#clientID) private static string clientID = "{client id}"; //Resource Uri for Power BI API private static string resourceUri = "https://analysis.windows.net/powerbi/api"; //OAuth2 authority Uri private static string authorityUri = "https://login.windows.net/common/oauth2/authorize"; private static string token = String.Empty; //Uri for Power BI datasets private static string pbiEndpoint = "https://api.powerbi.com/v1.0/myorg"; //Example dataset name and group name private static string groupId = "{group id}"; static void Main(string[] args) { //Import sample string pbixPath = @"C:\test\KPI.pbix"; string datasetDisplayName = "mydataset"; Task t = Import(string.Format("{0}/groups/{1}/imports?datasetDisplayName={2}&nameConflict=Overwrite", pbiEndpoint, groupId, datasetDisplayName), pbixPath); t.Wait(); Console.ReadKey(); } public static async Task<string> Import(string url, string fileName) { string responseStatusCode = string.Empty; string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n"); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.ContentType = "multipart/form-data; boundary=" + boundary; request.Method = "POST"; request.KeepAlive = true; var credential = new UserCredential(yourPbiAccount, Password); // Authenticate using created credentials var authenticationContext = new AuthenticationContext(authorityUri); var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUri, clientID, credential); token = authenticationResult.AccessToken; request.Headers.Add("Authorization", String.Format("Bearer {0}", token.ToString())); using (Stream rs = request.GetRequestStream()) { rs.Write(boundarybytes, 0, boundarybytes.Length); string headerTemplate = "Content-Disposition: form-data; filename=\"{0}\"\r\nContent-Type: application / octet - stream\r\n\r\n"; string header = string.Format(headerTemplate, fileName); byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header); rs.Write(headerbytes, 0, headerbytes.Length); using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { byte[] buffer = new byte[4096]; int bytesRead = 0; while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) { rs.Write(buffer, 0, bytesRead); } } byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n"); rs.Write(trailer, 0, trailer.Length); } try { using (HttpWebResponse response = request.GetResponse() as System.Net.HttpWebResponse) { responseStatusCode = response.StatusCode.ToString(); Console.WriteLine("Import pbix file is {0}", responseStatusCode); } } catch (WebException wex) { if (wex.Response != null) { using (var errorResponse = (HttpWebResponse)wex.Response) { using (var reader = new StreamReader(errorResponse.GetResponseStream())) { string errorString = reader.ReadToEnd(); dynamic respJson = JsonConvert.DeserializeObject<dynamic>(errorString); Console.WriteLine(respJson.ToString()); //TODO: use JSON.net to parse this string and look at the error message } } } } return responseStatusCode; } } }
When I try to upload a file that previously exists using the URL with ?datasetDisplayName={2}&nameConflict=Overwrite at the end, it works great. However when the report is a new report, and I try to upload it using...
https://api.powerbi.com/v1.0/myorg/groups/{GroupID}/imports
It does not update. In the code that you gave me to catch the error, all I see is the word Message: "". It does not tell me anything more. Previously you said that if you try to import a report, that does not exist, and use the ?datasetDisplayName={2}&nameConflict=Overwrite at end of URL, that it will not work. Well it seems even without that it does not work. Any thoughts why?
Thanks,
Stizz001
For new reports just remove the name conflict parameter
Hi @Anonymous ,
I am getting <Response [400]>, when I run this code. Any idea what is wrong ?
Thanks in advance.
import requests
values = """
-----BOUNDARY
Content-Disposition: form-data; name="mypbix"; filename="mypbix.pbix"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
{PBIX binary data}
-----BOUNDARY"""
headers = {
'Content-Type': 'multipart/form-data; boundary=---BOUNDARY',
'Authorization': 'Bearer <access_token>'
}
request = requests.post('https://api.powerbi.com/v1.0/myorg/groups/<group_id>/imports?datasetDisplayName=ProductOrder', data=values, headers=headers)
I apprecaite your response and am sorry I did not get back to you earlier. I decided for the time being to work on the front end stuff and get the PowerBI reports to show up in the app. I have pretty much finished that now, and am going back to finalizing the import.
I tried your below code, and and was still getting an error as I had deleted the uploaded report and thus removed the part about overwritting the data from the end of the url. This broke. But when I put that back on, it all seems to have worked great.
With that said though, I have 2 last questions. What if I already had the file in memory, and did not want to save it locally? In my app I use a tool that finds the tile and in the web request I get the file. I can convert this to byets or a stream, but I am confused as to what would go in these lines then....
string headerTemplate = "Content-Disposition: form-data; filename=\"{0}\"\r\nContent-Type: application / octet - stream\r\n\r\n"; string header = string.Format(headerTemplate, fileName);
Secondly, is the only way to know if said report already exists to check call the API to get the reports and check to see if one with that name already exists? I ask because it seems to me that I need to modify the URL depnding on if the report exists previously or not. So is that the best way to handel this, since you said having the overwirte and dataSetDisplayName on the URL if the report does not exist throws an error?
Either.....
string.Format("{0}/groups/{1}/imports?datasetDisplayName={2}&nameConflict=Overwrite", pbiEndpoint, groupId, datasetDisplayName), pbixPath
OR
string.Format("{0}/groups/{1}/imports?", pbiEndpoint, groupId), pbixPath
Thanks a ton.
Stizz001
Covering the world! 9:00-10:30 AM Sydney, 4:00-5:30 PM CET (Paris/Berlin), 7:00-8:30 PM Mexico City
Check out the April 2024 Power BI update to learn about new features.
User | Count |
---|---|
12 | |
2 | |
2 | |
1 | |
1 |