サイボウズ Garoon API を C# から使ってみた
ガルーンとは
サイボウズ社のGaroon(ガルーン) は、主に中堅~大企業をターゲットにしたグループウェアです。
電子メールの機能はあるもののSMTPサーバを持たないため、外部プログラムから社内メールを送信するにはGaroonのWeb APIを叩く必要がありました。
.NETアプリ(C#)でガルーンから社内メールを一括送信させてみたので、開発手順を紹介します。
Garoon API について
Garoon APIでは、従前からあるSOAPと、新しいRESTの両方をサポートします。
詳細は cybozu developer network 内の公式ドキュメントを参照して下さい。
SOAPとRESTの違いについて簡単に触れておきます。
SOAPは、Webサービスの初期に定義された古くからあるプロトコル(規格)で、HTTPのPOSTメソッドでXMLデータをやり取りします。WSDLと呼ばれるインターフェース仕様(XMLで記述されている)から、ネイティブAPIのコードを自動生成して使うことが多いです。ステートフルです。JavaやC#と相性が良く、WebサービスといえばSOAPといわれるくらい昔は主流でした。
RESTは、プロトコルというより概念とか設計思想に近いです。HTTPのGET/POST/PUT/DELETEメソッドで主にjsonデータをやり取りします。フォーマットが厳密に決まっているわけでは無いです。ステートレスです。実装がシンプルなので容易に始められます。
APIというと、筆者はRPCとかCORBAを叩きまくっていた世代ですが、今日、たんにAPIといえばRESTを指すことが一般的です。
開発手順
GaroonのREST APIは基本的にクラウド版しか提供されないので、本記事ではSOAP APIで作成していきます。
開発環境
- Visual Studio 2019(C#)
- サイボウズ Garoon(パッケージ版)
プロキシクラスの生成
WSDLの定義情報を元に、Webサービスにアクセスするためのプロキシクラスを自動生成します。
オブジェクトをXMLデータにシリアライズしたり、逆にデシリアライズするのは、このプロキシクラスがやってくれます。
サービス参照の追加
赤枠内を順にクリックします。
Web参照の追加
URLの欄にWSDLの場所を入力します。
WSDLの場所は、公式ドキュメントGaroon SOAP APIの共通仕様に記載があります。
プロキシクラスの修正
ソリューションエクスプローラ上部にある [すべてのファイルを表示] ボタンをクリックし、自動生成されたReference.cs
を表示します。
Reference.cs
にGaroonのSOAPヘッダを追加します。手順は【VisualStudio2017でガルーンAPIを使ってメッセージを送ってみる】が詳しいので、ここでは割愛します。
リクエストクラスの作成
プロキシクラスの下記APIを呼ぶリクエストクラスを作成します。
- BaseGetUsersByLoginName - ログイン名からユーザーを取得
- MessageCreateThreads - メッセージを送信
Garoon共通のSOAPヘッダをメンバ変数に定義し、コンストラクタで設定するようにしました。認証情報もコンストラクタで受け取ります。
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;
}
}
}
ユーザ情報を管理するクラスは次のようにしました。これだけなら構造体で良いかもしれません。
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, タイトル, 本文, 添付ファイル名);
}
Discussion