Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Organization

サイボウズGaroonの社内メールをC#から送信する

はじめに

サイボウズ社のGaroon(ガルーン)は、組織内の情報共有を目的としたグループウェアのひとつだ。主に中堅~大企業をターゲットにしている。
電子メールの機能はあるもののSMTPサーバを持たないため、外部プログラムからメールを送信するにはGaroonのWeb APIを叩く必要がある。

今回、Web APIを利用して、社内メールを一括送信するプログラムをC#で作成したので開発手順を紹介する。

謝辞

開発にあたり、下記の記事を大いに参考にさせていただいた。
深く謝意を表すとともに、本記事とあわせて参照して欲しい。

SOAPとREST

Web APIを語る上で外せない2大用語、SOAPとRESTの違いについて簡単にふれておこう。

SOAPは、Webサービスの初期に定義された古くからあるプロトコル(規格)で、HTTPのPOSTメソッドでXMLデータをやり取りする。WSDLと呼ばれるインターフェース仕様(XMLで記述されている)からネイティブAPIのコードを自動生成して使うことが多い。ステートフルである。JavaC#と相性が良く、WebサービスといえばSOAPといわれるくらい昔は主流だった。

RESTは、プロトコルというより概念とか設計思想に近い。HTTPのGET/POST/PUT/DELETEメソッドで主にjsonデータをやり取りする。フォーマットが厳密に決まっているわけでは無い。ステートレスである。単純な実装なので容易に始められる。

APIといえば、筆者もかつてはRPCとかCORBAを叩きまくっていた世代だが、今日、たんにAPIといえばRESTを指すことが多い。

開発環境

  • Visual Studio 2019(C#)
  • サイボウズ Garoon(パッケージ版)

開発手順

GaroonのREST APIは基本的にクラウド版でしか提供されないため、本記事ではSOAP APIで作成していく。

プロキシクラスの生成

WSDLの定義情報を元に、Webサービスにアクセスするためのプロキシクラスを自動生成する。
オブジェクトをXMLデータにシリアライズしたり、逆にデシリアライズするのは、このプロキシクラスがやってくれる。

サービス参照の追加

赤枠内を順にクリックする。
image.png
image.png
image.png

Web参照の追加

URLの欄にWSDLの場所を入力する。
WSDLの場所は、公式ドキュメントGaroon SOAP APIの共通仕様に記載されている。
wsdl.png

プロキシクラスの修正

ソリューションエクスプローラ上部にある [すべてのファイルを表示] ボタンをクリックし、自動生成されたReference.csを表示する。
image.png
Reference.csにGaroonのSOAPヘッダを追加する。手順は【VisualStudio2017でガルーンAPIを使ってメッセージを送ってみる】が詳しいので、ここでは割愛する。

リクエストクラスの作成

プロキシクラスの下記APIを呼ぶリクエストクラスを作成する。

Garoon共通のSOAPヘッダをメンバ変数に定義し、コンストラクタで設定するようにした。認証情報もコンストラクタで受け取る。

GaroonRequest.cs
using System;
using System.IO;
using System.Linq;
using MindWood.GaroonClientApp.GaroonService;

namespace MindWood.GaroonClientApp
{
    /*
     * Garoonリクエストクラス
     */
    class GaroonRequest
    {
        // Garoon共通SOAPヘッダ
        private ActionElement actionElement;
        private UsernameTokenElement userNameTokenElement;
        private SecurityElement securityElement;
        private TimestampElement timeStampElement;

        // コンストラクタ
        public GaroonRequest(string username, string password)
        {
            actionElement = new ActionElement();
            userNameTokenElement = new UsernameTokenElement();
            securityElement = new SecurityElement();
            timeStampElement = new TimestampElement();

            userNameTokenElement.Username = username;
            userNameTokenElement.Password = password;
            securityElement.usernameToken = userNameTokenElement;

            timeStampElement.Created = DateTime.UtcNow;
            timeStampElement.Expires = timeStampElement.Created.AddDays(8);
        }

        // ログイン名からユーザIDを取得する
        public UserInfo BaseGetGetUsersByLoginName(string login_name)
        {
            actionElement.actionValue = "BaseGetUsersByLoginName";
            BaseBinding api = new BaseBinding {
                action = actionElement,
                security = securityElement,
                timeStamp = timeStampElement
            };
            string[] param = { login_name };

            UserInfo user = new UserInfo();
            try {
                UserType[] resp = api.BaseGetUsersByLoginName(param);
                user.id = resp[0].key;
                user.name = resp[0].name;
                user.email = resp[0].email;
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
            return user;
        }

        // メッセージを送信する
        public string MessageCreateThreads(string user_id, string subject, string body, string fullpath_file)
        {
            actionElement.actionValue = "MessageCreateThreads";
            MessageBinding api = new MessageBinding {
                action = actionElement,
                security = securityElement,
                timeStamp = timeStampElement
            };

            string id_str = null;
            try {
                ThreadType threadType = new ThreadType();
                ThreadTypeAddressee threadTypeAddressee = new ThreadTypeAddressee();
                content content = new content();
                ThreadTypeFolder threadTypeFolder = new ThreadTypeFolder();
                ThreadTypeFollow threadTypeFollow = new ThreadTypeFollow();
                MessageCreateThreadType messageThreadType = new MessageCreateThreadType();
                MessageCreateThreadsRequestType messageThreadsRequestType = new MessageCreateThreadsRequestType();

                threadType.id = "dummy";  // ID
                threadType.version = "dummy";  // スレッドのバージョン
                threadTypeFolder.id = "dummy";  // フォルダID
                threadType.folder = new ThreadTypeFolder[1];
                threadType.folder[0] = threadTypeFolder;
                threadTypeFollow.id = "dummy";
                threadType.follow = new ThreadTypeFollow[1];
                threadType.follow[0] = threadTypeFollow;
                threadType.confirm = false;  // 閲覧状況の確認は不要

                // タイトル
                threadType.subject = subject;

                // 本文
                content.body = body;
                threadType.content = content;

                int i;

                // 宛先
                i = 0;
                string[] ids = user_id.Split(',');
                threadType.addressee = new ThreadTypeAddressee[ids.Length];
                foreach (var id in ids) {
                    threadTypeAddressee = new ThreadTypeAddressee();
                    threadTypeAddressee.user_id = id;
                    threadTypeAddressee.name = "dummy";
                    threadType.addressee[i] = threadTypeAddressee;
                    i++;
                }

                // 添付ファイル
                if (fullpath_file != null) {
                    i = 0;
                    string[] files = fullpath_file.Split(',');
                    MessageCreateThreadTypeFile[] typeFiles = new MessageCreateThreadTypeFile[0];
                    contentFile[] contFiles = new contentFile[0];

                    foreach (var file in files) {
                        // 添付ファイルをバイト配列に読み込む
                        FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
                        byte[] bs = new byte[fs.Length];
                        fs.Read(bs, 0, bs.Length);
                        fs.Close();

                        // ファイル実体
                        MessageCreateThreadTypeFile typeFile = new MessageCreateThreadTypeFile();
                        typeFile.content = bs;
                        typeFile.id = i.ToString();

                        Array.Resize(ref typeFiles, i + 1);
                        typeFiles[i] = typeFile;

                        // ファイル情報
                        contentFile contFile = new contentFile();
                        contFile.id = i.ToString();
                        contFile.size = (ulong)bs.Length;
                        contFile.name = Path.GetFileName(file);
                        contFile.mime_type = System.Web.MimeMapping.GetMimeMapping(contFile.name);

                        Array.Resize(ref contFiles, i + 1);
                        contFiles[i] = contFile;

                        i++;
                    }
                    messageThreadType.file = typeFiles;
                    threadType.content.file = contFiles;
                }

                // メッセージの送信
                messageThreadType.thread = threadType;
                messageThreadsRequestType.create_thread = new MessageCreateThreadType[1];
                messageThreadsRequestType.create_thread[0] = messageThreadType;

                ThreadType[] resp = api.MessageCreateThreads(messageThreadsRequestType);

                if (resp.Any()) {
                    id_str = resp[0].id.ToString();
                }

            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
            return id_str;
        }

    }
}

ユーザ情報を管理するクラスは次の通り。これだけなら構造体で良いかもしれない。

UserInfo.cs
namespace MindWood.GaroonClientApp
{
    class UserInfo
    {
        public string id;
        public string name;
        public string email;
    }
}

リクエストクラスの使い方

インスタンスの作成方法

GaroonRequest req = new GaroonRequest(自ログイン名, パスワード);

メールの送信方法

宛先ログイン名(連絡帳から確認できる)から、システム内のユーザIDを取得し、メールを送信する。
添付ファイルが要らなければ、nullを添付ファイル名に渡せば良い。

UserInfo user = req.BaseGetGetUsersByLoginName(宛先ログイン名);
if (user == null) {
    // エラー時の処理
} else {
    string res = req.MessageCreateThreads(user.id, タイトル, 本文, 添付ファイル名);
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?