外部API呼び出しを定期実行する処理をAzure Functionsで実装する(C#)

タイトルの通りです。サーバがあれば実行ファイルを置いてcronで実行させるのが手っ取り早いですが、クラウドのサービスを使って手軽にできないか試したら意外と簡単だったのでその時の備忘録です。1時間に1回の時報をslackに通知する、というケースで書いています。

必要なもの

  • Azureアカウント
  • Visual Studio
    • 今回は Visual Studio 2022を使いました。

1. Azureポータルで関数アプリを作成する

  • Azureポータルにアクセス
  • Azureサービスの一覧から [関数アプリ] を選択
  • [+作成] をクリック
  • 以下の画像の通りに必要な情報を入力
    • 今回のケースでは関数アプリ名は [jihou] とした
  • [作成] をクリック

2. Visual Studioでコードを書く

  • 新しいプロジェクトを作成
  • プロジェクト名を入力
  • [Timer trigger] を選択
    • 各時間の0分に通知してほしいので [Schedule] には「0 0 * * * *」と書く(記法について
  • コードを書く。
    • 17行目の変数 [url] にAPIのURLを代入
    • 18行目の変数 [payload] にPOSTするデータを代入
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;

namespace Jihou
{
    public class JihouHour
    {
        [FunctionName("JihouHour")]
        public void Run([TimerTrigger("0 0 * * * *")]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

            var url = "https://hooks.slack.com/services/*******************************";
            var payload = new Payload
            {
                text = DateTime.Now.ToString("yyyy/MM/dd HH") + "時です。"
            };

            var json = JsonSerializer.Serialize(payload);

            var client = new HttpClient();
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var res = client.PostAsync(url, content).Result;
            log.LogInformation(res.ToString());
        }
    }

    public class Payload
    {
        public string text { get; set; }
    }
}

尚、その場で動作を確認したい場合はVisual Studioのデバッグ([F5]キー)機能が使えます。その際は実行間隔を短く設定すればすぐに動作を確認できます。例えば [TimerTrigger("0 */1 * * * *")] とすれば1分ごとに実行してくれます。

3. コードをAzureにアップロードする

  • Visual Studio のプロジェクト名を右クリックして [発行] をクリック
  • ターゲットとして [Azure] を選択
  • [Azure Funciton App (Windows)] を選択
  • 冒頭で作成した関数アプリ [jihou] を選択
  • 公開の準備が完了
  • [発行] を押すとAzureにデプロイされる

確認

1時間に1回slackに通知が来ます。

参考

AutoMapperで詰め替えるDTOクラスにはinitアクセサーが使えそう(C#)

プログラム内のオブジェクトを外部に転送するためにDTO (Data Transfer Object) を使うことがありますが、C#であればAutoMapperという便利なライブラリがあります。

これまでAutoMapperでDTOに詰め替えする際、DTOクラスはプロパティは①getアクセサーのみでコンストラクタで代入するのが一般的でした。もしくは簡単に②setアクセサーを設定する方法もありました。

それぞれの方法の特徴は以下の通りです。

①getアクセサーのみでコンストラクタで代入

  • DTO生成後に意図せず値を書き換えられない(良いこと)
  • コンストラクタを作るのが面倒

②setアクセサーを設定する

  • DTO生成後に値を書き換えられてしまう
  • コンストラクタを作らなくていいので楽

それぞれ良いところと悪いところがありましたが、C#9.0 の新機能に initアクセサーが登場したことでこの2つのいいとこどりができるようになりました。特徴をまとめておきます。

③initアクセサーを使う

  • DTO生成後に値を書き換えられない(良いこと)
  • コンストラクタが不要

参考のため各方法のサンプルコードを載せておきます。

サンプルコード

①getアクセサーのみでコンストラクタで代入

using AutoMapper;
using System;

namespace AutoMapperTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => cfg.CreateMap<Item, ItemDto>());
            var mapper = config.CreateMapper();

            var item = new Item(1, "商品1", 100);
            ItemDto itemDto = mapper.Map<ItemDto>(item);

            Console.WriteLine("Item:");
            Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Price: {item.Price}");

            Console.WriteLine("ItemDto:");
            Console.WriteLine($"Id: {itemDto.Id}, Name: {itemDto.Name}, Price: {itemDto.Price}");

        }
    }

    class Item
    {
        public Item(int id, string name, int price)
        {
            Id = id;
            Name = name;
            Price = price;
        }

        public int Id { get; }
        public string Name { get; private set; }
        public int Price { get; private set; }
    }

    class ItemDto
    {
        public ItemDto(Item item) : this(item.Id, item.Name, item.Price)
        {
        }

        public ItemDto(int id, string name, int price)
        {
            Id = id;
            Name = name;
            Price = price;
        }

        public int Id { get; }
        public string Name { get; }
        public int Price { get; }
    }
}

②setアクセサーを設定する

using AutoMapper;
using System;

namespace AutoMapperTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => cfg.CreateMap<Item, ItemDto>());
            var mapper = config.CreateMapper();

            var item = new Item(1, "商品1", 100);
            ItemDto itemDto = mapper.Map<ItemDto>(item);

            Console.WriteLine("Item:");
            Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Price: {item.Price}");

            Console.WriteLine("ItemDto:");
            Console.WriteLine($"Id: {itemDto.Id}, Name: {itemDto.Name}, Price: {itemDto.Price}");

        }
    }

    class Item
    {
        public Item(int id, string name, int price)
        {
            Id = id;
            Name = name;
            Price = price;
        }

        public int Id { get; }
        public string Name { get; private set; }
        public int Price { get; private set; }
    }

    class ItemDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
    }
}

③initアクセサーを使う

using AutoMapper;
using System;

namespace AutoMapperTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => cfg.CreateMap<Item, ItemDto>());
            var mapper = config.CreateMapper();

            var item = new Item(1, "商品1", 100);
            ItemDto itemDto = mapper.Map<ItemDto>(item);

            Console.WriteLine("Item:");
            Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Price: {item.Price}");

            Console.WriteLine("ItemDto:");
            Console.WriteLine($"Id: {itemDto.Id}, Name: {itemDto.Name}, Price: {itemDto.Price}");

        }
    }

    class Item
    {
        public Item(int id, string name, int price)
        {
            Id = id;
            Name = name;
            Price = price;
        }

        public int Id { get; }
        public string Name { get; private set; }
        public int Price { get; private set; }
    }

    class ItemDto
    {
        public int Id { get; init; }
        public string Name { get; init; }
        public int Price { get; init; }
    }
}