C#でガルーンAPI SOAPリクエストが不正です。

# 概要
C#でのgaroon SOAP APIのリクエストについて
当初の実装でちょっとハマったのでメモ

# 公式ドキュメント

[Garoon（ガルーン） SOAP API の概要](https://developer.cybozu.io/hc/ja/articles/202228424)


# エラー内容

> GRN_UTIL_API_65001
> SOAPリクエストが不正です。
> "Header" がSOAPリクエストに存在しません。
> サイボウズオフィシャルパートナー、または販売元にお問い合わせください。

[VisualStudio2017でガルーンAPIを使ってメッセージを送ってみる]
(https://qiita.com/HAGITAKO/items/b684f4e31552e5a5db69)
こちらの記事を参考にメッセージ送信はできたけど、
他のAPI使おうとしたらエラーになる！ｷﾞｴｴｴｴ

# 原因
**Reference.csの修正箇所が足りませんでした。**
ちゃんと理解しないまま進めると痛い目見る好例。

Reference.csでは下記の構造になっている
各機能(全般/スケジュール/掲示板など):Bindingクラス
各API(ユーザ情報取得など):Bindingクラス内の各メソッド

参考にした記事はメッセージ送信なのでMessageBindingクラスのMessageCreateThreadsメソッドにSOAPヘッダーを追加している。
他のAPIにはヘッダー付いてないので、そのまま使おうもんならそりゃあヘッダーないですエラーでるよね〜

---

[Garoon SOAP APIの共通仕様
](https://developer.cybozu.io/hc/ja/articles/202228464)
> Garoon SOAP APIを使用するには、ガルーンに登録したユーザーで認証する必要があります。認証には、Cookieを使用した方式と、WebServicesSecurity（WS-Security）を使用した方式があります。

> WS-Securityを用いた認証方式
> ログイン名とパスワードを SOAP ヘッダに含めることによって認証を行います。 この認証方式では、リクエストごとに認証を行う必要があります。

Cookieを使用しない場合はWS-Securityを用いた認証方式が必要。
（参考記事の実装もこっち）
認証用ログイン名、パスワードがSOAPヘッダに必要だけど、
それが存在しないから認証エラーになってるっぽい

# 対応内容
例として[ログイン名からユーザーを取得するAPI](https://developer.cybozu.io/hc/ja/articles/202283324#step2)を使用。

- 使いたいAPIが含まれるBindingクラスにSOAPヘッダメンバを追加。

```C#:Reference.cs
public partial class BaseBinding : System.Web.Services.Protocols.SoapHttpClientProtocol {

    /* ここから */
    public ActionElement action;
    public SecurityElement security;
    public TimestampElement timeStamp;
    /* ここまで */

    private System.Threading.SendOrPostCallback BaseGetUserVersionsOperationCompleted;
```

- 使用するサービスメソッドにSOAPヘッダ属性追加。

```C#:Reference.cs
/// <remarks/>
/* ここから */
[System.Web.Services.Protocols.SoapHeaderAttribute("action", Direction = SoapHeaderDirection.InOut)]
[System.Web.Services.Protocols.SoapHeaderAttribute("security", Direction = SoapHeaderDirection.InOut)]
[System.Web.Services.Protocols.SoapHeaderAttribute("timeStamp", Direction = SoapHeaderDirection.InOut)]
/* ここまで */
[System.Web.Services.Protocols.SoapRpcMethodAttribute("BaseGetUsersByLoginName", RequestNamespace="http://wsdl.cybozu.co.jp/base/2008", ResponseNamespace="http://wsdl.cybozu.co.jp/base/2008", Use=System.Web.Services.Description.SoapBindingUse.Literal)]
[return: System.Xml.Serialization.XmlArrayAttribute("returns")]
[return: System.Xml.Serialization.XmlArrayItemAttribute("user", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
public UserType[] BaseGetUsersByLoginName([System.Xml.Serialization.XmlArrayItemAttribute("login_name", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)] string[] parameters) {
```

- 呼び出し箇所でヘッダー情報を追加

```C#:呼び出し.cs

// base区分のAPIクライアント
BaseBinding baseAPI = new BaseBinding();

/* ここから */
//--------------------------------------------------------
// ヘッダ情報追加
//--------------------------------------------------------

// Garoon共通のSOAPヘッダー
ActionElement actionElement = new ActionElement();
UsernameTokenElement userNameTokenElement = new UsernameTokenElement();
SecurityElement securityElement = new SecurityElement();
TimestampElement timeStampElement = new TimestampElement();

// 呼び出すAPI名
actionElement.actionValue = "BaseGetUsersByLoginName";

// アクセス者
userNameTokenElement.Username = "xxxxx"; // GaroonのログインID
userNameTokenElement.Password = "xxxxx"; // ログインパスワード
securityElement.usernameToken = userNameTokenElement;

// タイムスタンプ
timeStampElement.Created = DateTime.UtcNow;
timeStampElement.Expires = timeStampElement.Created.AddDays(8);

// ヘッダー設定
baseAPI.action = actionElement;
baseAPI.security = securityElement;
baseAPI.timeStamp = timeStampElement;

/* ここまで */

//--------------------------------------------------------
// リクエスト(Body)作成
//--------------------------------------------------------

// このAPIだとログインID配列のみ
string[] param = { "yyyyy" };

try
{
    //--------------------------------------------------------
    // SOAPリクエスト
    //--------------------------------------------------------
    UserType[] userTypes = baseAPI.BaseGetUsersByLoginName(param);

    Console.WriteLine("成功");
}
catch (System.Web.Services.Protocols.SoapException ex)
{
    // ユーザーIDに一致するユーザーがいない場合もここに来る
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}


```


# 結果

```xml:修正前のリクエスト
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <BaseGetUsersByLoginName xmlns="http://wsdl.cybozu.co.jp/base/2008">
      <parameters xmlns="">
        <login_name>yyyyy</login_name>
      </parameters>
    </BaseGetUsersByLoginName>
  </soap:Body>
</soap:Envelope>
```

```xml:修正後のリクエスト
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Header>
    <Action xmlns="http://wsdl.cybozu.co.jp/base/2008">BaseGetUsersByLoginName</Action>
    <Security xmlns="http://wsdl.cybozu.co.jp/base/2008">
      <UsernameToken>
        <Username>xxxxx</Username>
        <Password>xxxxx</Password>
      </UsernameToken>
    </Security>
    <Timestamp xmlns="http://wsdl.cybozu.co.jp/base/2008">
      <Created>2018-08-27T00:43:07.6447372Z</Created>
      <Expires>2018-09-04T00:43:07.6447372Z</Expires>
    </Timestamp>
  </soap:Header>
  <soap:Body>
    <BaseGetUsersByLoginName xmlns="http://wsdl.cybozu.co.jp/base/2008">
      <parameters xmlns="">
        <login_name>yyyyy</login_name>
      </parameters>
    </BaseGetUsersByLoginName>
  </soap:Body>
</soap:Envelope>
```

ちゃんとヘッダ設定したらエラー解消。ハレルヤ。

# まとめ
使いたいAPI各メソッドにヘッダ追加処理が必要。
うーむめんどくさい。
どうにかうまいやり方ないかなぁ…
というかReference.csの自動生成時にヘッダ入ってれば楽なのに、入れてないのは何か理由があるんだろうか。