rksoftware

Visual Studio とか C# とかが好きです

Dataverse の WebAPI で Dataverse のテーブルに列追加する C# コード その3

目次

Dataverse の WebAPI を C# で叩く系記事たちの目次です。

rksoftware.hatenablog.com

今回の本文

rksoftware.hatenablog.com

前回の続きです。
これまでテーブルに列追加を一気に行うことを考えていってみます。
流れとしては、いったん手で列追加したテーブルがあるとして、そのテーブルと同じ列を別のテーブルに追加する流れです。

なぜそんなことを?

列定義の JSON を作るところをまだ手を出せていないので、既存のテーブルから JSON を持ってくることで、いったん JSON を作るところを省略して追加部分だけを確認する方針です。

■ 動かし方

まずいったん別途 API で列を追加するのとは別のテーブルの定義 JSON を取得してファイルに保存します。
コマンドライン引数で、列が追加される方のテーブルの URL と、先に取得した JSON のファイルパスを指定します。

URL Web の GUI で 「テーブル 定義への API リンク」をした時の URL です。

■ 概要

  1. テーブルの列定義の JSON の value を取り出してループ
  2. その定義から MetadataIdCreatedOnModifiedOn を削除
  3. Post。

簡単ですね。

■ できていないこと

どうも一通りの型を追加してみていますが、今のところ
「はい/いいえ」
「選択肢」
「計算式」
の列が追加できません。

■ 留意・エラーがたくさんです

このロジックでは、テーブルにすでに存在する列名を追加しようとするときにエラーが出ます。
で、この流れだとテーブルにすでに存在する列名を大量に処理しようとします。例えば登録日時や修正日時など。全部エラーになります。これらのエラーになる列を除外するより、エラーになる前提で処理を回してしまった方がコードが手っ取り早いと思うので、そのままエラーを出しながら進めていくロジックです。

■ コード

クラス二つになりました。

using DataverseClientShared;
using System.Text;

if(args.Length < 2)
{
    Console.WriteLine("Usage: DataverseAddAttributes <EntityDefinition URL> <Path to JSON file>");
    Console.WriteLine("Example");
    Console.WriteLine("<EntityDefinition URL> : https://orgxxxxxxxx.crmx.dynamics.com/api/data/v9.2/EntityDefinitions(LogicalName='xxxxx_xxxxxxxx')");
    Console.WriteLine("<Path to JSON file> : C:\example\example.json");

}

string s = args[0] + "/Attributes";
string d = args[1];;
HttpHelper http = (await HttpHelper.Instance.Init(s, new HttpClient())).Item1;

Newtonsoft.Json.Linq.JObject originJson = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(File.ReadAllText(d))!;
var value = originJson.SelectToken("value");
if (value != null)
    foreach (var attribute in value)
    {
        attribute.SelectToken("MetadataId")?.Parent?.Remove();
        attribute.SelectToken("CreatedOn")?.Parent?.Remove();
        attribute.SelectToken("ModifiedOn")?.Parent?.Remove();
        var json = Newtonsoft.Json.JsonConvert.SerializeObject(attribute);
        Console.WriteLine(await PostAsync(http, json));
    }

async Task<string> PostAsync(HttpHelper http, string json)
{
    string? jsonContent = "";
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    if (http.HttpClient == null) return jsonContent;
    var response = await http.HttpClient.PostAsync(http.Path, content);
    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine($"Success: {response.StatusCode}");
        jsonContent = await response.Content.ReadAsStringAsync();
    }
    else
    {
        Console.WriteLine($"Error: {response.StatusCode}");
        jsonContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine(jsonContent);
    }
    return jsonContent ?? "";
}

次のクラスはこれまで毎回書いてきた定型的な部分をクラスでまとめたものです。

namespace DataverseClientShared
{
    public class HttpHelper
    {
        public static HttpHelper Instance { get; } = new HttpHelper();

        public string? Resource { get; private set; }
        public Uri? BaseAddress { get; private set; }
        public string? Path { get; private set; }
        public static string ClientId { get; } = "51f81489-12ee-4a9e-aaae-a2591f45987d";
        public static string RedirectUri { get; } = "http://localhost";

        private HttpHelper() { }

        public Microsoft.Identity.Client.AuthenticationResult? Token { get; private set; }
        public HttpClient? HttpClient { get; private set; }

        public HttpHelper InitPath(string fullPath)
        {
            var uri = new Uri(fullPath);
            Resource = uri.Scheme + "://" + uri.Host;
            BaseAddress = new Uri(Instance.Resource + string.Join("", uri.Segments.Take(4)));
            Path = new string(uri.PathAndQuery.Skip(BaseAddress.AbsolutePath.Length).ToArray());
            return Instance;
        }

        public async Task<Microsoft.Identity.Client.AuthenticationResult> AuthenticateAsync()
        {
            var authBuilder = Microsoft.Identity.Client.PublicClientApplicationBuilder.Create(ClientId)
               .WithAuthority(Microsoft.Identity.Client.AadAuthorityAudience.AzureAdMultipleOrgs)
               .WithRedirectUri(RedirectUri)
               .Build();
            string[] scopes = { Resource + "/user_impersonation" };
            Microsoft.Identity.Client.AuthenticationResult token = await authBuilder.AcquireTokenInteractive(scopes).ExecuteAsync();
            return Token = token;
        }

        public HttpClient SetHttpClient(HttpClient httpClient)
        {
            var client = HttpClient = httpClient ?? HttpClient ?? new HttpClient();
            client.BaseAddress = BaseAddress;
            client.Timeout = new TimeSpan(0, 2, 0);
            System.Net.Http.Headers.HttpRequestHeaders headers = client.DefaultRequestHeaders;
            headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token?.AccessToken);
            headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            return client;
        }

        public async Task<(HttpHelper, HttpClient)> Init(string fullPath, HttpClient httpClient)
        {
            InitPath(fullPath);
            await AuthenticateAsync();
            SetHttpClient(httpClient);
            return (Instance!, HttpClient!);
        }
    }
}

■ GitHub

GitHub リポジトリにコードを置いて試しています。

github.com