📩

【C#】サーバー側からプッシュ通知の送信リクエストを送る

2022/02/01に公開

Flutterで作ったスマホアプリに、プッシュ通知の機能を付けることになりました。

【やりたいこと、条件など】

  • アプリユーザーの中で、条件に合致するユーザーにのみ通知を送りたい。
  • 管理者が通知を送るためのWebシステムを作る。
  • プッシュ通知に必要なトークンはDB上に保存する。

そこでまずは、サーバーからスマホアプリに通知を送る、簡単なテスト用コードを作成してみることにしました。

※ Firebaseへの登録と、受信するアプリ側の実装は終わっていることを前提に書いていきます。


筆者の環境

  • Windows 10 pro
  • .NET Core 3.1
  • Firebase Cloud Messaging (FCM)

1.プロジェクトの作成

今回はテストなのでコンソールアプリケーションを作成します。
新しいプロジェクト > コンソールアプリケーション を選択します。

プロジェクト名は何でも良いです。ここでは CloudMessagingSendTestとしました。

2. 環境設定

NuGetパッケージのインストール

まず、FirebaseAdminパッケージをインストールします。

認証設定

秘密鍵が必要なようです。jsonファイルでダウンロードできます。

  1. 設定 > プロジェクトの設定をクリックします。

  1. サービスアカウント をクリックします。

  1. 「新しい秘密鍵の生成」をクリックすると、jsonファイルがダウンロードされます。

  1. プロジェクトのディレクトリ > bin > Debug > netcoreapp3.1 にダウンロードしたjsonファイルを入れておきます。

公式サイトを見ると、(1) 環境変数を設定する方法と、(2) OAuth 2.0 更新トークンを使用する方法が書かれています。
今回はテストなので(2)の方法を使ってjsonファイルを直接読み込みます。

これで認証の準備ができました。コードは次のステップに書いてあります。

環境設定は、下のサイトを参考に書いています。もっと詳しく知りたい場合はそちらをご確認ください。
サーバーに Firebase Admin SDK を追加する

3. 特定の端末(トークン)へ送信する場合

Program.csファイルが自動で作成されていると思うので、そこにコードを書いていきます。
下記が実際に書いたコードです。

using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace CloudMessagingSendTest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("メッセージを送信します。");

            //Jsonファイルの読み込み
            FirebaseApp.Create(new AppOptions()
            {
                Credential = GoogleCredential.FromFile("project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json"),
            });

            // 送信先のトークン(ベタ書きしてるが、本番はDBから読み込む予定)
            var registrationToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

            var message = new Message()
            {
                // プッシュ通知のタイトルと本文
                Notification = new Notification
                {
                    Title = "イベントのお知らせ",
                    Body = "20XX年5月5日にイベントを開催します。来てね!",
                    ImageUrl = "http://hogehoge.jpg",
                },
                //プッシュ通知で送るデータ
                Data = new Dictionary<string, string>()
                {
                    { "url", "https://xxxevent.co.jp" },
                    { "id", "123456789" },
                },
                Token = registrationToken,
            };

            // トークンに対応するデバイスにメッセージを送る
            string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);

            // 送信成功したメッセージIDを出力
            Console.WriteLine("メッセージの送信に成功しました。 メッセージID: " + response);
            Console.ReadLine();
        }
    }
}

ザックリ解説

下記でダウンロードしたJsonファイルを読み込んでいます。
公式解説では推奨されていない方法ですが、テストなので簡単な方法を選択しました。

//Jsonファイルの読み込み
FirebaseApp.Create(new AppOptions()
{
	Credential = GoogleCredential.FromFile("project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json"),
});

送信先のトークンは、あらかじめアプリ側で取得しておきましょう。

トークン取得方法は、Flutterで作成する場合FlutterFireの公式サイトが参考になります。
FlutterFire Firebase Cloud Messaging

下記で送信先のトークンを指定します。実際運用する際はDBから読み込むことが多いと思います。

// 送信先のトークン(ベタ書きしてるが、本番はDBから読み込む予定)
var registrationToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

Titleがプッシュ通知のタイトル、Bodyが本文です。
公式のサンプルコードには載っていないのですが、この2つを書いておかないと届いたときに通知文が出てこないと思います。

// プッシュ通知のタイトルと本文
Notification = new Notification
{
	Title = "イベントのお知らせ",
	Body = "20XX年5月5日にイベントを開催します。来てね!",
},

Dataはオプションのようなものです。プッシュ通知のメッセージと一緒に、受け取り側には見えないデータも送ることができます。
例えば通知をタップしたときの遷移先であるURLを送ったり、アプリの処理に関わるフラグを送ったりできます。

//プッシュ通知で送るデータ
Data = new Dictionary<string, string>()
{
    { "url", "https://xxxevent.co.jp" },
    { "id", "123456789" },
},

4. トピックに送信する場合

今回トピック機能は使わないことにしましたが、せっかくなので記録を残しておきます。

トピックは、送信先をグループ化できる機能です。
トピックに登録する際は必ず記録を残してください。現在、トピックに登録したトークンの一覧を出力する方法が無いためです。

まず、トークンがトピックに登録されている必要があります。下記のコードは、3件のトークンを「all」トピックに登録するものです。

//Jsonファイルの読み込み
FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.FromFile("project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json"),
});

var topic = "all";
Console.WriteLine("メッセージを送信します。");

//トピックに登録するトークン
var registrationTokens = new List<string>()
{
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
};

//トークンをトピック名「all」に登録
var topicResponse = await FirebaseMessaging.DefaultInstance.SubscribeToTopicAsync(
    registrationTokens, topic);

//登録成功件数を出力
Console.WriteLine($"{topicResponse.SuccessCount} トークンの登録に成功しました。");

これで、3件のトークンが「all」トピックに登録されました。
次に、トピックに対してメッセージを送信します。

これは 3. 特定の端末(トークン)へ送信する場合とほぼ同じです。違うのは Messageの中で tokenではなくtopicを指定するところです。

下記がコードです。トークンに送信する場合と違う箇所に「★」マークをつけておきました。

using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace CloudMessagingSendTest
{
    class Program
    {
        static async Task Main(string[] args)
        {
	    var topic = "all";   // ★
            Console.WriteLine("メッセージを送信します。");

            //Jsonファイルの読み込み
            FirebaseApp.Create(new AppOptions()
            {
                Credential = GoogleCredential.FromFile("project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json"),
            });

            // 送信先のトークン(DBから読み込む予定)
            var registrationToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

            var message = new Message()
            {
                // プッシュ通知のタイトルと本文
                Notification = new Notification
                {
                    Title = "a title",
                    Body = "a body",
                },
                //プッシュ通知で送るデータ
                Data = new Dictionary<string, string>()
                {
                    { "score", "850" },
                    { "time", "2:45" },
                },
                Topic = topic,  // ★
            };

            // トークンに対応するデバイスにメッセージを送る
            string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);

            // 送信成功したメッセージIDを出力
            Console.WriteLine("メッセージの送信に成功しました。 メッセージID: " + response);
            Console.ReadLine();
        }
    }
}

参考

Discussion