Power BI API データプッシュを試してみた
Power BIを扱う機会が増えてきているので、APIを試してみました。
Power BI ダッシュボードにデータをプッシュする | Microsoft Power BI
上記のガイド通りに進めていけば何も問題ないですが、
あまりにも香ばしい箇所があり、きっと誰も使ってないよ!
と思われる部分があったので、ガイドとは変えてみました。
特にこのへんです。サンプルでもそれなりに書いてほしいな
このへんを、Newtonsoft.Jsonを使ったり、HttpClientを使うように変えています。
ガイドと同様に、下記の手順ですすめます。
アプリをAzureADに登録する
手順 1: アプリを Azure AD に登録する | Microsoft Power BI
AzureADによる認証機能を使うために行います。PowerBIに限ったことではないですね。
ガイドでは、Power BI専用のAzureAD登録サイトから行うように誘導されますが、
AzureADに直接登録しても同じです。慣れているならこっちの方が早い。
リダイレクトURLは、ガイドでも指定されている、
この値をhttps://login.live.com/oauth20_desktop.srf入力します。
クライアントIDと、リダイレクトURLは認証する際に使うため、
メモ帳などに貼りつけておきます。
アクセス許可は、これだけ付けておけば良いです。
AzureADへの登録が終わりましたら、アプリケーションを作っていきます。
認証アクセストークンを取得する
手順 2: 認証アクセス トークンを取得する | Microsoft Power BI
ここでの注意は、Microsoft.IdentityModel.Clients.ActiveDirectoryのバージョンです。
ガイドと同じバージョンでないと、認証に使うメソッドがありません。
ガイド通りパッケージマネージャコンソールに、下記コマンドを入力します。
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
Microsoft.IdentityModel.Clients.ActiveDirectoryパッケージをインストールしたら、
認証用のクラスを作成していきます。
役割はアクセストークンを取得するだけです。
using System; using System.Configuration; using Microsoft.IdentityModel.Clients.ActiveDirectory; namespace PBIApiDataPushExample { public class AccessToken { /// <summary> /// OAuth2 authority Uri /// </summary> private const string AuthorityUrl = "https://login.windows.net/common/oauth2/authorize"; /// <summary> /// Resource Uri for Power BI API /// </summary> private const string ResourceUrl = "https://analysis.windows.net/powerbi/api"; /// <summary> /// AzureADから発行されたクライアントID /// </summary> private static readonly string ClientID = ConfigurationManager.AppSettings["ClientID"]; /// <summary> /// AzureADに指定したRedirectUrl /// </summary> private const string RedirectUrl = "https://login.live.com/oauth20_desktop.srf"; /// <summary> /// アクセストークンを取得します /// </summary> public static string GetToken() { var authContext = new AuthenticationContext(AuthorityUrl); return authContext.AcquireToken(ResourceUrl, ClientID, new Uri(RedirectUrl)).AccessToken; } } }
ガイドがメソッドで作成しているのに対して、ここでは、クラスにしているだけです。
AcquireToken()が、Microsoft.IdentityModel.Clients.ActiveDirectoryの
最新のバージョンだと無くなっています。
試しに実行してみます。
コンソールアプリケーションで作っていますので、
Mainメソッドから作成したGetoToken()を呼びます。
namespace PBIApiDataPushExample { class Program { /// <summary> /// PBIApiDataPushExampleアプリのエンドポイント /// </summary> /// <param name="args">コマンドライン引数</param> static void Main(string[] args) { string token = AccessToken.GetToken(); } } }
認証用のダイアログが表示されますので、
アプリを登録したAzureADで利用している組織アカウントを選びます。
認証が成功すると、アクセストークンが取得できます。
次はデータセットを作ります。
データセットを作成する
手順 3: Power BI ダッシュボードにデータセットを作成する | Microsoft Power BI
このへんから、リテラルでJSON書いてたり、リクエストをストリームに書き出したりと
香ばしくなってきますので、微妙に対応していきます。
まず、JSONを扱うので、
Nugetパッケージの管理からNewtonsoft.Jsonをインストールします。
Newtonsoft.Jsonに含まれる
JsonConvert.SerializeObject()を使って、オブジェクトからJSON文字列を作成するために、
良い感じにクラス分けする必要があります。
Power BIのデータセットは、下記図のような構成です
同じ構成にするために、下記クラスに分けました。
- データセットクラス
- テーブルクラス
- 列クラス
- 列のデータ列挙型(列挙型から文字列に変換する拡張クラス付き)
VSのコードマップで見ると、こんな感じです。
3クラス+1列挙型+列挙型の拡張クラスにしています。
まずデータセットクラス
using System.Collections.Generic; using Newtonsoft.Json; namespace PBIApiDataPushExample { /// <summary> /// PowerBIデータセットを表します /// </summary> public class PBIDataset { /// <summary> /// データセット名 /// </summary> [JsonProperty("name")] public string Name { get; set; } /// <summary> /// テーブルリスト /// </summary> [JsonProperty("tables")] public List<PBITable> Tables { get; set; } /// <summary> /// PowerBIDatasetオブジェクトを生成します /// </summary> /// <param name="name">データセット名</param> /// <returns>PowerBIDatasetオブジェクト</returns> public static PBIDataset Create(string name) { return new PBIDataset() { Name = name, Tables = new List<PBITable>() }; } } }
テーブルクラス
using System.Collections.Generic; using Newtonsoft.Json; namespace PBIApiDataPushExample { /// <summary> /// PowerBIのテーブルを表します /// </summary> public class PBITable { /// <summary> /// テーブル名 /// </summary> [JsonProperty("name")] public string Name { get; set; } /// <summary> /// 列リスト /// </summary> [JsonProperty("columns")] public List<PBIColumn> Columns { get; set; } /// <summary> /// PowerBITableオブジェクトを生成します /// </summary> /// <param name="name">テーブル名</param> /// <returns>PowerBITableオブジェクト</returns> public static PBITable Create(string name) { return new PBITable() { Name = name, Columns = new List<PBIColumn>() }; } } }
列クラス
using Newtonsoft.Json; namespace PBIApiDataPushExample { /// <summary> /// PowerBIの列を表します /// </summary> public class PBIColumn { /// <summary> /// 名称 /// </summary> [JsonProperty(PropertyName = "name", Order = 1)] public string Name { get; set; } /// <summary> /// 型 /// </summary> /// <remarks> /// JSONにする時は、無視します。 /// </remarks> [JsonIgnore] public PBIColumnDataTypes PBIDataType { private get; set; } /// <summary> /// 型文字列 /// </summary> /// <remarks> /// 設定は、PBIDataTypeプロパティを使います /// JSONにする時は、このプロパティを使います /// </remarks> [JsonProperty(PropertyName = "dataType", Order = 2)] public string PBIDataTypeString { get { return this.PBIDataType.ToDataTypeString(); } } /// <summary> /// PowerBIColumnオブジェクトを生成します /// </summary> /// <param name="name">名称</param> /// <param name="dataType">データ型</param> /// <returns>PBIApiDataPushExample</returns> public static PBIColumn Create(string name, PBIColumnDataTypes dataType) { return new PBIColumn() { Name = name, PBIDataType = dataType }; } } }
列のデータ列挙型と、列挙型の拡張クラス
using System; using System.Collections.Generic; namespace PBIApiDataPushExample { /// <summary> /// APIでサポートされるデータ型を列挙しています。 /// </summary> /// <remarks> /// https://powerbi.microsoft.com/ja-jp/documentation/powerbi-developer-walkthrough-push-data/ /// </remarks> public enum PBIColumnDataTypes { PBI_Int64, PBI_Double, PBI_Bool, PBI_Datetime, PBI_String, } /// <summary> /// PBIColumnDataTypes列挙型の拡張機能を提供します /// </summary> public static class PBIColumnDataTypesExtensions { /// <summary> /// 列挙型と型文字列の対応 /// </summary> private readonly static Dictionary<PBIColumnDataTypes, string> _PBIColumnDataTypeString = new Dictionary<PBIColumnDataTypes, string>() { { PBIColumnDataTypes.PBI_Int64, "Int64" }, { PBIColumnDataTypes.PBI_Double, "Double" }, { PBIColumnDataTypes.PBI_String, "string" }, { PBIColumnDataTypes.PBI_Bool, "bool" }, { PBIColumnDataTypes.PBI_Datetime, "DateTime" }, }; /// <summary> /// 列挙値と型文字列に変換します /// </summary> /// <param name="candidate">対象</param> /// <returns>型文字列</returns> public static string ToDataTypeString(this PBIColumnDataTypes candidate) { if (!_PBIColumnDataTypeString.ContainsKey(candidate)) { throw new ArgumentException("型リストに未登録の型を指定しています。"); } return _PBIColumnDataTypeString[candidate]; } } }
試しに、データセットオブジェクトを作って、JSON文字列にしてみます。
using System; using Newtonsoft.Json; namespace PBIApiDataPushExample { class Program { /// <summary> /// PBIApiDataPushExampleアプリのエンドポイント /// </summary> /// <param name="args">コマンドライン引数</param> static void Main(string[] args) { string token = AccessToken.GetToken(); // テーブルオブジェクトを作って、 var pbi_table = PBITable.Create("テーブル"); // テーブルオブジェクトに列オブジェクトを追加していきます pbi_table.Columns.Add(PBIColumn.Create("名前", PBIColumnDataTypes.PBI_String)); pbi_table.Columns.Add(PBIColumn.Create("日時", PBIColumnDataTypes.PBI_Datetime)); // データセットオブジェクトを作って、 var pbi_dataset = PBIDataset.Create("データセット"); // テーブルオブジェクトを追加しています pbi_dataset.Tables.Add(pbi_table); // データセットオブジェクトをJSON文字列に変換します string jsonString = JsonConvert.SerializeObject(pbi_dataset); Console.WriteLine(jsonString); } } }
データセットオブジェクトからJSON文字列に変換できたので、
Power BIのAPIに送信してみます。
ガイドでは、リクエストストリームに書き込んでましたが、HttpClientを使います。
パッケージマネージャコンソールを開いて、下記サイトのコマンドを入力してください。
www.nuget.org
データセットクラスに、ApiへJson文字列をPostするメソッドを追加します。
/// <summary> /// Apiへ送信します /// </summary> /// <param name="token">トークン</param> /// <returns>レスポンス</returns> public HttpResponseMessage SendApi(string token) { using (var httpClient = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Post, PBIDatasetApiUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(JsonConvert.SerializeObject(this), Encoding.UTF8, "application/json"); return httpClient.SendAsync(request).Result; } }
試してみます。先ほどMainメソッドで作成したpbi_datasetオブジェクトの
SendApiメソッドを呼びます
using System; //using Newtonsoft.Json; namespace PBIApiDataPushExample { class Program { /// <summary> /// PBIApiDataPushExampleアプリのエンドポイント /// </summary> /// <param name="args">コマンドライン引数</param> static void Main(string[] args) { string token = AccessToken.GetToken(); // テーブルオブジェクトを作って、 var pbi_table = PBITable.Create("テーブル"); // テーブルオブジェクトに列オブジェクトを追加していきます pbi_table.Columns.Add(PBIColumn.Create("名前", PBIColumnDataTypes.PBI_String)); pbi_table.Columns.Add(PBIColumn.Create("日時", PBIColumnDataTypes.PBI_Datetime)); // データセットオブジェクトを作って、 var pbi_dataset = PBIDataset.Create("データセット"); // テーブルオブジェクトを追加しています pbi_dataset.Tables.Add(pbi_table); // 作ったメソッドを呼びます var response = pbi_dataset.SendApi(token); Console.WriteLine(response.StatusCode); //// データセットオブジェクトをJSON文字列に変換します //string jsonString = JsonConvert.SerializeObject(pbi_dataset); //Console.WriteLine(jsonString); } } }
まずは、アプリケーションを実行します。成功したようです。
Power BI Serviceを確認します。指定した通りに、作成されていました。
データセット | テーブル、カラム |
---|---|
行を追加するデータセットを取得する
手順 4: Power BI テーブルに行を追加するためにデータセットを取得する | Microsoft Power BI
ここもガイドには香ばしさがあふれています。
特に、ここですね。Odata形式でvalue句に入ってくるのは理解できますが、
絶対1番目にないでしょ!
//Get the first id datasetId = results["value"][0]["id"];
というわけで、レスポンスを見ると、IDとNameがあるみたいです。
IDとNameがあるのは分かりましたが、POST時とGET時で
エンティティが違うのって、どうなんでしょうね。
しょうがないので、GET時用のクラスを作成しました。
public class PBIDatasetsGetMethodOnly { [JsonProperty("value")] public PBIDatasetGetMethodOnly[] Values { get; set; } } public class PBIDatasetGetMethodOnly { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("id")] public string ID { get; set; } }
データセットクラスには、IDプロパティを追加します。
[JsonIgnore] public string ID { get; private set; }
ID取得用のメソッドを追加します。
/// <summary> /// IDを読み込みます /// </summary> /// <param name="token">トークン</param> public void LoadId(string token) { using (var httpClient = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Get, PBIDatasetApiUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("appication/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = httpClient.SendAsync(request).Result; var results = JsonConvert.DeserializeObject<PBIDatasetsGetMethodOnly>(response.Content.ReadAsStringAsync().Result); this.ID = (from d in results.Values where d.Name == this.Name select d).FirstOrDefault().ID; } }
試してみますが、何個もデータセット作成されるのは嫌なので、
データセットを作成するメソッド部分はコメントにしておきます。
/// <summary> /// PBIApiDataPushExampleアプリのエンドポイント /// </summary> /// <param name="args">コマンドライン引数</param> static void Main(string[] args) { string token = AccessToken.GetToken(); // テーブルオブジェクトを作って、 var pbi_table = PBITable.Create("テーブル"); // テーブルオブジェクトに列オブジェクトを追加していきます pbi_table.Columns.Add(PBIColumn.Create("名前", PBIColumnDataTypes.PBI_String)); pbi_table.Columns.Add(PBIColumn.Create("日時", PBIColumnDataTypes.PBI_Datetime)); // データセットオブジェクトを作って、 var pbi_dataset = PBIDataset.Create("データセット"); // テーブルオブジェクトを追加しています pbi_dataset.Tables.Add(pbi_table); // 作ったメソッドを呼びます //var response = pbi_dataset.SendApi(token); //Console.WriteLine(response.StatusCode); // IDを取得します pbi_dataset.LoadId(token); Console.WriteLine(pbi_dataset.ID); //// データセットオブジェクトをJSON文字列に変換します //string jsonString = JsonConvert.SerializeObject(pbi_dataset); //Console.WriteLine(jsonString); } }
無事データセットのIDが取れました。
テーブルに行を追加する
手順 5: Power BI テーブルに行を追加する | Microsoft Power BI
ようやくデータを追加するとこまで来ました。
ガイドでは、ここも香ばしいので、
HttpClientを使ったり、オブジェクトからJSON文字列を作るように変えます。
新しいクラスを2個追加します。
作成したデータセットに沿った行クラスと、行を束ねるクラスです。
今までに作成したクラスには、なんの関連もありません。
クラスの内容がこちら。
using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using Newtonsoft.Json; namespace PBIApiDataPushExample { /// <summary> /// 作成したデータセットの行群 /// </summary> public class ExampleDatasetRows { /// <summary> /// 行群 /// </summary> [JsonProperty("rows")] public List<ExampleDatasetRow> Rows { get; set; } /// <summary> /// データセットID /// </summary> [JsonIgnore] public string DataSetID { get; private set; } /// <summary> /// テーブル名 /// </summary> [JsonIgnore] public string TableName { get; private set; } /// <summary> /// 作ったデータセットの行群オブジェクトを生成します /// </summary> /// <param name="datasetId">データセットID</param> /// <param name="tableName">テーブル名</param> /// <returns>作ったデータセットの行群オブジェクト</returns> public static ExampleDatasetRows Create(string datasetId, string tableName) { return new ExampleDatasetRows() { DataSetID = datasetId, TableName = tableName, Rows = new List<ExampleDatasetRow>() }; } /// <summary> /// データを追加します /// </summary> /// <param name="token">トークン</param> /// <returns>Apiコール結果</returns> public HttpResponseMessage AddData(string token) { string powerBIApiAddRowsUrl = String.Format("https://api.powerbi.com/v1.0/myorg/datasets/{0}/tables/{1}/rows", this.DataSetID, this.TableName); using (var httpClient = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Post, powerBIApiAddRowsUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(JsonConvert.SerializeObject(this), Encoding.UTF8, "application/json"); return httpClient.SendAsync(request).Result; } } } /// <summary> /// 作成したデータセットの行 /// </summary> public class ExampleDatasetRow { [JsonProperty("名前")] public string Name { get; set; } [JsonProperty("日時")] public DateTime AddDate { get; set; } } }
Mainメソッドに処理を追加して、試してみます。
/// <summary> /// PBIApiDataPushExampleアプリのエンドポイント /// </summary> /// <param name="args">コマンドライン引数</param> static void Main(string[] args) { string token = AccessToken.GetToken(); // テーブルオブジェクトを作って、 var pbi_table = PBITable.Create("テーブル"); // テーブルオブジェクトに列オブジェクトを追加していきます pbi_table.Columns.Add(PBIColumn.Create("名前", PBIColumnDataTypes.PBI_String)); pbi_table.Columns.Add(PBIColumn.Create("日時", PBIColumnDataTypes.PBI_Datetime)); // データセットオブジェクトを作って、 var pbi_dataset = PBIDataset.Create("データセット"); // テーブルオブジェクトを追加しています pbi_dataset.Tables.Add(pbi_table); //// データセットオブジェクトをJSON文字列に変換します //string jsonString = JsonConvert.SerializeObject(pbi_dataset); //Console.WriteLine(jsonString); //// 作ったメソッドを呼びます //var response = pbi_dataset.SendApi(token); //Console.WriteLine(response.StatusCode); // IDを取得します pbi_dataset.LoadId(token); //Console.WriteLine(pbi_dataset.ID); // 追加するデータを用意します var dataRow = ExampleDatasetRows.Create(pbi_dataset.ID, pbi_table.Name); for(int i = 0; i < 30; i++) { if(i % 5 ==0 ) { dataRow.Rows.Add(new ExampleDatasetRow() { Name = "サンプル1", AddDate = DateTime.Now }); } else { dataRow.Rows.Add(new ExampleDatasetRow() { Name = "サンプル2", AddDate = DateTime.Now.AddDays(-i) }); } } // データを追加します var response = dataRow.AddData(token); Console.WriteLine(response); }
成功しました。
Power BI Service側でデータがあるか確認します。
ちゃんとデータが登録されていました。
作れることは分かったので、API経由で作ったデータセットと
Power BI Desktopでレポートと共にアップしたデータセットと
比較してみないとですね。
作ったクラスはこちらで全部です。
(ちょっと上にある図と同じです。Main部分は除いています。)
データセットクラスだけは最終形を載せていないので載せておきます。
それ以外のクラスは、最後に出てるとこから取ればOKです。
using System.Collections.Generic; using Newtonsoft.Json; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Linq; namespace PBIApiDataPushExample { /// <summary> /// PowerBIデータセットを表します /// </summary> public class PBIDataset { /// <summary> /// データセットApiUrl /// </summary> private const string PBIDatasetApiUrl = "https://api.powerbi.com/v1.0/myorg/datasets"; /// <summary> /// データセット名 /// </summary> [JsonProperty("name")] public string Name { get; set; } /// <summary> /// テーブルリスト /// </summary> [JsonProperty("tables")] public List<PBITable> Tables { get; set; } [JsonIgnore] public string ID { get; private set; } /// <summary> /// PowerBIDatasetオブジェクトを生成します /// </summary> /// <param name="name">データセット名</param> /// <returns>PowerBIDatasetオブジェクト</returns> public static PBIDataset Create(string name) { return new PBIDataset() { Name = name, Tables = new List<PBITable>() }; } /// <summary> /// Apiへ送信します /// </summary> /// <param name="token">トークン</param> /// <returns>レスポンス</returns> public HttpResponseMessage SendApi(string token) { using (var httpClient = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Post, PBIDatasetApiUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(JsonConvert.SerializeObject(this), Encoding.UTF8, "application/json"); return httpClient.SendAsync(request).Result; } } /// <summary> /// IDを読み込みます /// </summary> /// <param name="token">トークン</param> public void LoadId(string token) { using (var httpClient = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Get, PBIDatasetApiUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("appication/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = httpClient.SendAsync(request).Result; var results = JsonConvert.DeserializeObject<PBIDatasetsGetMethodOnly>(response.Content.ReadAsStringAsync().Result); this.ID = (from d in results.Values where d.Name == this.Name select d).FirstOrDefault().ID; } } } public class PBIDatasetsGetMethodOnly { [JsonProperty("value")] public PBIDatasetGetMethodOnly[] Values { get; set; } } public class PBIDatasetGetMethodOnly { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("id")] public string ID { get; set; } } }