Tech Note

Azure, Office 365, Power Platform, etc... の勉強手記

Azure Functions で Serilog を実装する

azure-functions-startup-serilog

Serilog を実装した Azure Functions プロジェクトのサンプルです

出力イメージ

  • コンソール出力

  • ファイル出力

テンプレートから実装した機能

  • パッケージの追加

    • Serilog.AspNetCore
    • Serilog.Exceptions
  • 以下は必要に応じて

    • Serilog.Exceptions.EntityFrameworkCore
  • DI 機能の追加

  • Startup クラスの追加

[assembly: FunctionsStartup(typeof(Startup))]

namespace FunctionApp4;

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
    }
}
  • Serilog の構成
var logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("Worker", LogEventLevel.Warning)
    .MinimumLevel.Override("Host", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Error)
    .MinimumLevel.Override("Function", LogEventLevel.Error)
    .MinimumLevel.Override("Azure.Storage.Blobs", LogEventLevel.Error)
    .MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
    .WriteTo.Console()
    .WriteTo.Debug()
    .WriteTo.File(
        $"logs\\{nameof(MyFunctionWithSerilog)}.txt",
        LogEventLevel.Information,
        rollingInterval: RollingInterval.Hour)
    .CreateLogger();
  • Serilog のカスタムログプロバイダーを追加
builder.Services.AddLogging(loggingBuilder =>
{
    loggingBuilder.AddSerilog(logger, true);
});
private readonly ILogger<Function1> _logger;

public Function1(ILogger<Function1> logger)
{
    _logger = logger;
}

GitHub

github.com

OAuth 2.0 と OpenID Connect とは

OAuth 2.0 と OpenID Connect について雰囲気で分かってたつもりだったので、調べて備忘録と言語化のために記事を書きます。

認証と認可

前提知識として『認証』と『認可』についてです。

  • 認証 は、クライアントが誰(Who)であるかを特定すること。
    英語でいうと Authentication。AuthN と表現されることもあります。

  • 認可 は、クライアントがリソースに対して何(What)ができるのかを特定すること。
    英語でいうと Authorization。AuthZ や AuthR と表現されることもあります。

OAuth 2.0

OAuth 2.0 は『認可』の仕組みです。
OAuth 2.0 は、アクセストークンの要求とその応答を標準化したものです。

アクセストーク

クライアントが、リソースオーナーのデータを利用することを許可されていることを示すもの。
(データの利用許可書のようなものかな)

OAuth 2.0 の登場人物(ロール)

  • リソースオーナー(resource owner)
    • データの本来の持ち主
  • リソースサーバー (resource server)
    • リソースオーナーのデータを預かってる
  • クライアント (client)
    • Web アプリケーションやクライアントアプリケーションなど、リソースサーバーが持っているデータの要求する者
  • 認可サーバー (authorization server)

OAuth 2.0 のプロトコルフローの概要

認可サーバーは、クライアントからの認可要求をリソースオーナーにリダイレクトした後、その許可要求の許諾を受けて、アクセストークンを生成して、クライアントにアクセストークンを渡す。

OpenID Connect

OAuth 2.0 をベースにした『認証』の 仕組みです。
OpenID Connect は、ID トークンの要求とその応答を標準化したものです。

ID トーク

どの OpenID プロバイダーから、
どのユーザーの情報を、
どのアプリケーション(利用者)に対して、
いつまでの有効期限で、
いつ発行されたのか、

という情報を必ず含んだもの。

ID トークンは、ユーザーが認証されたことの証なので、それを利用することで ID 連携が実現できます。

OAuth 2.0 と OpenID Connect の関係性

OpenID Connect が OAuth 2.0 をベースとしているため、プロトコルフローが似ています。
そのため、OpenID Connect で登場した OpenID プロバイダー が OAuth 2.0 の 認可サーバー を兼任することが多くなりました。
(アクセストークン発行と ID トークン発行を同時にやっちゃいましょうという流れですね。)

OpenID Connect のウェブサイトでは、次のように述べられてます。

(Identity, Authentication) + OAuth 2.0 = OpenID Connect
https://openid.net/connect/faq/

参考情報

レガシーコードをリファクタリングしてみた

発端

先日、TTDBC のコミュニティ内でレガシーコードをリファクタリングするライブコーディングに参加させていただきました。 実際に自分でも手を動かしてみようと思い、C# のサンプルでリファクタリングを実践してみました。

サンプルコード

今回リファクタリングのサンプルで使用したリポジトリはこちらです。 様々なプログラミング言語で用意されているので、お好きなものから選べます。

github.com

ちなみに、リポジトリ名の Kata は空手などの という意味だそうです。

実際にリファクタリングしてみたコード

私が C#リファクタリングを実践した結果はこちらです。

github.com

元々用意されていたテストフレームワークNUnit だったので、別途 xUnit のテストプロジェクトを用意してそちらにテストコードを書きました。 (その際、テストプロジェクトフォルダをルートフォルダに作ってしまった…)

所感

ライブコーディングでは一気にテストを書くのではなく、ある程度テストコードを書いたらリファクタリングを進めて、慎重にリファクタリングをしたい部分があれば、その範囲内を対象としてテストコードを追加で記述するといった感じでした。

流暢にテスト&リファクタリングを進めてて感動しました。

私もそれに倣って簡単なテストコードを書いてグリーンにしてから、まずは IDEリファクタリング機能を全面に信じて、IDEリファクタリング機能のみを利用してリファクタリングを進めました。

もうこれ以上は IDE によるリファクタリングができない、つまり本番コードに手を加える必要が出てきた際に、テストコードを書きました。私はここで一気に思いつくテストコードを書きました。

本番コードに手を加えるリファクタリングは、歩幅を広げすぎない程度に少しずつやった方が良いとは思っていたのですが、それがなかなか難しくて一気に本番コードに手を加えてしまいました。反省…。 (Factory パターンを適用する形で一気にコードを書き換えた)

I/F を定義して、それを実装したクラスを作成して、いくつか同じ I/F を実装したクラスが登場してきたら Factory メソッド作りますかとなって…という流れが理想なのかなと。

リポジトリもリファクタリングの教材となるものが沢山あるみたいなので、反省点を振り返って実践したいと思います。

テストデータを自動生成するライブラリ

データベースに投入するデータや、単体テスト時に利用するデータを作るのはとても面倒です。 今回紹介する Bogus というライブラリを使うと容易にデータが作れます。

動作環境

サンプルコード

github.com

単純なクラス

public class BillingDetails
{
    public string CustomerName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string AddressLine { get; set; }
    public string City { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; }
}

private static List<BillingDetails> GenerateRandomBillingDetails(int numberOfRecordsPerBatch)
{
    var faker = new Faker<BillingDetails>()
        // .StrictMode(true)
        .RuleFor(x => x.CustomerName, x => x.Person.FullName)
        .RuleFor(x => x.Email, x => x.Person.Email)
        .RuleFor(x => x.Phone, x => x.Person.Phone)
        .RuleFor(x => x.AddressLine, x => x.Address.StreetAddress())
        .RuleFor(x => x.City, x => x.Address.City())
        .RuleFor(x => x.PostCode, x => x.Address.ZipCode())
        .RuleFor(x => x.Country, x => x.Address.Country());

    var billingDetails = faker.Generate(numberOfRecordsPerBatch);

    return billingDetails;
}

人に関するプロパティは、Facker.Person クラスのプロパティを紐づけたり、住所に関するプロパティは、Facker.Address クラスのプロパティを紐づけると、イイ感じのデータを自動生成してくれます。

クラスが入れ子になってるクラス

public class Order
{
    public Guid Id { get; set; }
    public decimal Price { get; set; }
    public string Currency { get; set; }
    public BillingDetails BillingDetails { get; set; }
}

private static List<Order> GenerateRandomOrders(int numberOfRecordsPerBatch)
{
    var billingDetailsFaker = new Faker<BillingDetails>()
        .RuleFor(x => x.CustomerName, x => x.Person.FullName)
        .RuleFor(x => x.Email, x => x.Person.Email)
        .RuleFor(x => x.Phone, x => x.Person.Phone)
        .RuleFor(x => x.AddressLine, x => x.Address.StreetAddress())
        .RuleFor(x => x.City, x => x.Address.City())
        .RuleFor(x => x.PostCode, x => x.Address.ZipCode())
        .RuleFor(x => x.Country, x => x.Address.Country());

    var orderFaker = new Faker<Order>()
        .RuleFor(x => x.Id, Guid.NewGuid)
        .RuleFor(x => x.Currency, x => x.Finance.Currency().Code)
        .RuleFor(x => x.Price, x => x.Finance.Amount(5, 100))
        .RuleFor(x => x.BillingDetails, x => billingDetailsFaker);

    var orders = orderFaker.Generate(numberOfRecordsPerBatch);

    return orders;
}

入れ子になってるクラスの Facker オブジェクトをまずは作っておいてから、親クラスのプロパティに Facker オブジェクトを割り当てる感じに書きます。

実行する度に生成されるデータを変えさせない方法

既定では、実行するたびに自動生成されるデータの中身はランダムに変わりますが、下記のようにあらかじめ Seed 値を設定することで生成されるデータを固定することができます。

Randomizer.Seed = new Random(123456789);

Power BI でセッションリストを作成してみた

Power BI でセッションリストを作成する過程で知り得た知見を、備忘録として残そうと思います。

Power BI でセッションリストを作るキッカケとなったイベントはこちら。

msdevjp.connpass.com

Power BI でセッションリストを作成する際、参考にさせていただいた Power BI MVP 清水優吾さんの YouTube 動画はこちら。

www.youtube.com

作ったセッションリスト

Power Query

Power Query を触ったときの気づき。

  • 列の分布、列の品質、数式バーの表示は、[ 表示 ] タブから設定する。

f:id:shibatea:20200809030340p:plain

  • レコードを展開するとき、「元の列名をプレフィックスとして使用します」にチェックを入れたままだと、次のような列名になる。(冗長的な感じ)

f:id:shibatea:20200809025211p:plain

  • 上記のチェックを外した場合、展開したときの列名が使われる。

f:id:shibatea:20200809025535p:plain

f:id:shibatea:20200809025645p:plain

  • データが109個に対して、一意データが109個なので SessionID がキーとして使えることがわかる。

f:id:shibatea:20200809025731p:plain

  • [ 他の列の削除 ]を行うと、選択した列以外の列が削除される。残った列は、Ctrl + 左クリックで選択した順に並ぶ。

f:id:shibatea:20200809030446p:plain

  • マスターテーブルを作成したい場合、そのテーブルのID列とタイトル列の順に選択して、他の列の削除を行う。その後、ID 列を右クリックして、重複の削除を行う。ID 列を昇順に並び替えたいので、[ ホーム ] タブ > 並び替え > A->Z を選択する。

f:id:shibatea:20200809030504p:plain f:id:shibatea:20200809030510p:plain

  • 基となった JSON はビジュアルする際に利用しないので、読み込みを無効化する。(右クリック > 読み込みを有効にする を選択して、チェックを外すとそのクエリ名が斜体に変わる)

f:id:shibatea:20200809030727p:plain

Power BI Desktop

Power BI Desktop を触ったときの気づき。

  • データ取得先が URL ならば、Web から取得すること。その際、アクセス方式について問われる。(今回のケースでは「匿名」を選択した) それ以降はキャッシュ?で持ってるせいかアクセス方式についてダイアログが表示されない。(キャッシュクリアしても表示されなかった。やり方が間違ってると思うので、必要になったらまた調べる)

  • Power Query 適用後、モデルビューでリレーションのラインにマウスオーバーしてリレーションを確認する。想定通りでない場合は編集すること。

f:id:shibatea:20200809031319p:plain

  • ディメンションテーブルの絞り込みをファクトテーブルに反映させるために、双方向にすること。(ディメンションテーブルからファクトテーブルへの方向に矢印が向けられていること)

f:id:shibatea:20200809031810p:plain

  • ビジュアライズに利用しないテーブル、フィールドは非表示にすること。

  • Title 列の並び順を id 順にしたい場合(レポートに使われる Title 列の昇順ではなく)、Title 列を選択してから、[ 列ツール ] タブ > 列で並べ替え > id を選択する。

f:id:shibatea:20200809031132p:plain

CSOM が .NET Standard に対応したらしいので試してみた

CSOM が .NET Standard に対応したとのことです。
使用方法は次のドキュメントで解説されているので、それに沿って進めていきます。

docs.microsoft.com

環境情報

Microsoft.SharePointOnline.CSOM をインストール

コンソール アプリケーション(.NET Core)プロジェクトを作成します。
NuGet で Microsoft.SharePointOnline.CSOM Version 16.1.20211.12000 をインストールします。

f:id:shibatea:20200624015649p:plain

ソースコードは弄らずにそのままビルドしたところ、警告はありませんでした。

f:id:shibatea:20200624020205p:plain

ちなみに、ひとつ前のバージョン(16.1.20211.12000)だと次のような警告が出てました。 最新バージョンではちゃんと .NET Standard に対応してるみたいですね。

f:id:shibatea:20200624020058p:plain

認証処理について

今までのようにユーザーIDとパスワードを用いた基本認証は、CSOM .NET Standard 版ではもうできないようです。代わりに、OAuth アクセストークンを用いた認証方式を使用します。
SharePoint Online のアクセストークンを取得するための推奨アプローチは、Azure AD アプリケーションを登録し、SharePoint のアクセス許可を与えること、とのこと。

というわけで、Azure AD アプリケーションの登録方法は次の通りです。

Azure AD アプリケーションの登録

Azure Active Directory > アプリの登録 からアプリケーションを登録します。
作成したアプリケーションのアプリケーションIDは控えておきましょう。

f:id:shibatea:20200624025523p:plain

API のアクセス許可から SharePoint を選択します。

f:id:shibatea:20200624025723p:plain

委任されたアクセス許可 を選択します。

f:id:shibatea:20200624030005p:plain

今回は、AllSites.Manage を選択します。

f:id:shibatea:20200624030104p:plain

管理者の同意を与えます をクリックします。

f:id:shibatea:20200624030205p:plain

サイドナビゲーションから「認証」をクリックします。

f:id:shibatea:20200624030444p:plain

「既定のクライアントの種類」の選択肢を「はい」に変更します。

f:id:shibatea:20200624030616p:plain

ソースコード

サンプルコードは Program.cs と AuthenticationManager.cs の 2 ファイルがあります。 Program.cs は一部記載が省略されている部分があったので補足しています。

using System;
using System.Security;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var site = new Uri("https://contoso.sharepoint.com/sites/hoge");
            var user = "shibatea@consoto.onmicrosoft.com";
            var password = GetSecureString($"Password for {user} : ");

            // Note: The PnP Sites Core AuthenticationManager class also supports this
            using var authenticationManager = new AuthenticationManager();
            using var context = authenticationManager.GetContext(site, user, password);

            context.Load(context.Web, p => p.Title);
            await context.ExecuteQueryAsync();
            Console.WriteLine($"Title: {context.Web.Title}");
        }

        static SecureString GetSecureString(string message)
        {
            SecureString sStrPwd = new SecureString();
            try
            {
                Console.Write(message);

                for (ConsoleKeyInfo keyInfo = Console.ReadKey(true); keyInfo.Key != ConsoleKey.Enter; keyInfo = Console.ReadKey(true))
                {
                    if (keyInfo.Key == ConsoleKey.Backspace)
                    {
                        if (sStrPwd.Length > 0)
                        {
                            sStrPwd.RemoveAt(sStrPwd.Length - 1);
                            Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                            Console.Write(" ");
                            Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                        }
                    }
                    else if (keyInfo.Key != ConsoleKey.Enter)
                    {
                        Console.Write("*");
                        sStrPwd.AppendChar(keyInfo.KeyChar);
                    }

                }
                Console.WriteLine("");
            }
            catch (Exception e)
            {
                sStrPwd = null;
                Console.WriteLine(e.Message);
            }

            return sStrPwd;
        }
    }

}

AuthenticationManager.cs は、サンプルコードのままコピペすると画像の通り FormDigestHandlingEnabled プロパティが見つからなくてコンパイルエラーになります。

f:id:shibatea:20200624043152p:plain

Microsoft.SharePointOnline.CSOM の一つ前のバージョンとソースコードを逆コンパイルして比較したら、FormDigestHandlingEnabled プロパティは削除されていました。
( FormDigestHandlingEnabled = false 相当と同じ動きになるように修正されている )

コンパイルエラーが発生している行をコメントアウトすれば動作確認ができました。

f:id:shibatea:20200624042307p:plain
左が最新バージョン、右が一つ前のバージョン

( おまけ ) SharePointPnPCoreOnline を試しにインストール

すると、大量に警告が表示されました。SharePointPnPCoreOnline が依存してるライブラリの中に、.NET Framework しか対応してないものが含まれてるようです。

f:id:shibatea:20200624023506p:plain

最後に

ひとまず .NET Standard で CSOM が動くことがわかりました。これで SharePoint CSOM を使うために Azure Functions V1 を使っていたものが V3 に移行できそうですね。(PnP を使ってるとダメですが…)

それと以前から疑問に思っていたのが…、Microsoft.SharePointOnline.CSOM のソースコードは公開されていないんでしたっけ?
探し方が下手なのか見つからないので、私は逆コンパイルして中身を確認しています。

Power Automate で指定した日付の予定表のイベント一覧を取得する

コネクタ

今回使うコネクタは イベントの取得 (V4) [ Get events (V4) ] です。

コネクタの説明にもある通り、内部的には Microsoft Graph API が呼ばれています。
コネクタでわからないことがある場合は Microsoft Graph API のドキュメントを見に行くと良いでしょう。

docs.microsoft.com

やりたいこと

  • まずは予定表のイベント一覧を取得する
  • さらに指定した日付に絞り込んでイベント一覧を取得する(例として 6/22 のイベント一覧を取得します)
  • 取得したイベント一覧をイベントの開始時刻順に並べる

設定内容

結論から書くと、6/22 のイベント一覧を取得するために設定したパラメーターは以下の通りです。

f:id:shibatea:20200622203625p:plain

パラメーター
予定表ID 取得したい予定表ID
フィルター クエリ start/dateTime ge '2020-06-21T15:00:00' and start/dateTime lt '2020-06-22T15:00:00'
並べ替え順 start/dateTime
上から順に取得 (なし)
スキップ数 (なし)

6/22 のイベント一覧を取得する場合は、イベントの開始時刻を『6/22 0時0分 以上』、『6/23 0時0分 未満』で絞り込む必要があります。

しかし、Power Automate で日付を扱うときはタイムゾーンUTC にする必要があります。その為、フィルター クエリに設定する時刻は次のような UTC 時刻を設定する必要があります。

  • '2020-06-21T15:00:00' 以上(ge)
  • '2020-06-22T15:00:00' 未満(lt)

ODATA フィルタークエリについては、以下のドキュメントが参考になります。

docs.microsoft.com

苦労したこと

フィルター クエリに指定するプロパティを探し当てるのに苦労しました。

フィルター クエリなしでイベント一覧を取得したときの出力結果は以下の通りでした。(詳細は省略)

[
  {
    "subject": "夕会",
    "start": "2020-06-22T08:00:00.0000000",
    "end": "2020-06-22T08:30:00.0000000",
  },
  {
    "subject": "朝会",
    "start": "2020-06-22T00:00:00.0000000",
    "end": "2020-06-22T00:30:00.0000000",
  }
]

開始時刻のプロパティは "start" なんだなぁと思って、フィルター クエリに "start" でフィルターしたところエラーになりました。

f:id:shibatea:20200622203831p:plain

Microsoft Docs でイベントのプロパティを確認すると、開始時刻は dateTimeTimeZone という型らしい。 Microsoft Graph API『イベントを取得する』の応答結果の例を確認するとわかりやすいと思います。

docs.microsoft.com docs.microsoft.com

以下のように入れ子になってる場合は、スラッシュを交えて指定する必要があります。今回のケースで言うと "start/dateTime" となります。

f:id:shibatea:20200622205839p:plain

まとめ

フィルター クエリの指定方法、特に日付を指定する場合はハマりポイントかなと思い記事を書きました。

Microsoft Graph API に対する理解が深まれば、Power Automate の勘所も備わってくると思います。 何か躓いたら Microsoft Graph API のドキュメントを見ると良いでしょう。