SalesforceでSOAP APIを使ったデータ読み込み

C#などの主要な言語でSalesforceのAPIを使ったデータのやり取りを行う方法を紹介します。

SalesforceではSOAP APIを使って簡単にデータの取得、更新ができます。使い慣れたC#などのプログラミング言語を使い、社内のシステムとのデータ連携などが可能です。

例えば、基幹システムなどで管理している売上データをSalesforceの商談に反映させるときなど、APIを使って同期するプログラムを組んだりできます。

Enterprise WSDLの生成

もしVisual Studioをメインの開発環境としているならラッキーです。Salesforceで生成したWSDLのXMLファイルをVisual Studioに読み込むだけで、Salesforceのオブジェクトをクラスとして利用できるようになります。

設定の「カスタムコード」→「API」と進むと「Enterprise WSDL」とありますので生成のリンクをクリックします。

生成されたXMLファイルを保存します。保存するときはブラウザーによっては「Webアーカイブ形式」を選択できますが、ソースコード(単一のファイル)の形式で保存します。

System.Web.Serviceが必要なので、System.Web.Serviceを参照で追加できるテンプレートをプロジェクトで選びます。ここでは「その他」から「コンソール プロジェクト」を選んでおきます1

プロジェクトを右クリックして「追加」から「Web 参照の追加」を選びます。

ダウンロードしたXMLファイルを指定します。ローカルファイルのURL形式がわからなければ、ブラウザーなどに一度ドラッグしてアドレスバーのURLをコピペするといいです。

参照のところに「Sforce」と入力しておきます2

セキュリティトークンを作ろう

他のアプリケーションからログインする場合、セキュリティトークンがないとログインではじかれます。なので、セキュリティトークンを作成しておきましょう。

右上の歯車アイコンの「設定」から「私の個人情報」→「私のセキュリティトークンのリセット」に行き、[セキュリティトークンのリセット] ボタンをクリックします。

メールでセキュリティトークンが飛んできますので、それを使ってログインします。

ログイン

ログインする場合、ユーザ名とパスワードにセキュリティトークンを付与した文字列をloginメソッドに渡してあげるだけです。

public bool Login()
{
    binding = new SforceService();
    binding.Timeout = 60000;
    LoginResult loginResult;

    try
    {
        loginResult = binding.login(userName, password + securityToken);
    }
    catch (SoapException)
    {
        throw;
    }
    catch (Exception)
    {
        throw;
    }

    if (loginResult.passwordExpired)
    {
        return false;
    }
    binding.Url = loginResult.serverUrl;
    binding.SessionHeaderValue = new SessionHeader();
    binding.SessionHeaderValue.sessionId = loginResult.sessionId;

    return true;
}

例外処理は面倒なのでthrowしているけど、実環境ではここでちゃんとメッセージを出すなりログに吐くなりしておきましょう。

パスワード期限切れは別途passwordExpiredで判断して処理してあげる必要があります。インテグレーション用のアプリケーション、もしくはサービスで使用するパスワードは無期限にしておくといいと思います。

ログアウト

ログアウトの処理は、ただログインに使用したSforceServiceインスタンスでlogoutメソッドを実行してあげるだけです。

public void Logout()
{
    try
    {
        binding.logout();
        isLogin = false;
    }
    catch (SoapException)
    {
        throw;
    }
    catch (Exception)
    {
        throw;
    }
}

こちらの例外処理も、本当は同じようにメッセージやログなどでエラーが発生したことを残すようにしておくといいでしょう。

データを取得してみよう

データを取得する部分ですが、Salesforceのオブジェクトのクラスごとに処理を作ってもいいのですが、ジェネリックのメソッドとして作り、その都度SOQL3を引数で渡してあげるほうがコードがシンプルかなと個人的には思います。どちらでもいいと思いますけどね。

public List<T> Get<T>(string query) where T: class
{
    var list = new List<T>();
    QueryResult queryResult;

    try
    {
        queryResult = binding.query(query);
        bool done = false;
        if (queryResult.size > 0)
        {
            while (!done)
            {
                var records = queryResult.records;
                for (int i = 0; i < records.Length; i++)
                {
                    var recordType = records[i] as T;
                    list.Add(recordType);
                }
                if (queryResult.done) done = true;
                else queryResult = binding.queryMore(queryResult.queryLocator);
            }
        }
    }
    catch (SoapException)
    {
        throw;
    }
    catch (Exception)
    {
        throw;
    }

    return list;
}

例外処理はテキトー仕様ですので、実運用に使う場合はちゃんとメッセージを出すなりログを吐くなりしましょう。

完成形コード

以下に完成形のコードを紹介します。Salesforceにログイン後、商談オブジェクトのIDとNameを取得してコンソールに表示するだけのプログラムです。

using System;
using System.Configuration;
using SalesforceAccessConsoleApp.Sforce;

namespace SalesforceAccessConsoleApp
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            string userName = ConfigurationManager.AppSettings["SalesforceUserName"];
            string password = ConfigurationManager.AppSettings["SalesforcePassword"];
            string securityToken = ConfigurationManager.AppSettings["SalesforceSecurityToken"];

            using (var salesforce = new SalesforceAccess(userName, password, securityToken))
            {
                if (!salesforce.Login()) return;
                var opportunities = salesforce.Get<Opportunity>("SELECT Id, Name FROM Opportunity");
                foreach (var opportunity in opportunities)
                {
                    Console.WriteLine("ID: {0}, Name: {1}", opportunity.Id, opportunity.Name);
                }
            }
        }
    }
}

メインのコードです。ここでコンソールへの出力処理を実施しています。ここでは、ユーザ名、パスワード、セキュリティトークンはコード上で見せないように設定ファイルから取得しています。

以下がSalesforceにアクセスするためのメソッドをまとめたクラスです。

using System;
using System.Collections.Generic;
using System.Web.Services.Protocols;
using SalesforceAccessConsoleApp.Sforce;

namespace SalesforceAccessConsoleApp
{
    public class SalesforceAccess : IDisposable
    {
        SforceService binding;
        string userName;
        string password;
        string securityToken;
        bool isLogin = false;

        public SalesforceAccess(string loginUserName, string loginPassword, string loginSecurityToken)
        {
            userName = loginUserName;
            password = loginPassword;
            securityToken = loginSecurityToken;
        }

        public void Dispose()
        {
            Logout();
        }

        public bool Login()
        {
            binding = new SforceService();
            binding.Timeout = 60000;
            LoginResult loginResult;

            try
            {
                loginResult = binding.login(userName, password + securityToken);
            }
            catch (SoapException)
            {
                throw;
            }
            catch (Exception)
            {
                throw;
            }

            if (loginResult.passwordExpired)
            {
                return false;
            }
            binding.Url = loginResult.serverUrl;
            binding.SessionHeaderValue = new SessionHeader();
            binding.SessionHeaderValue.sessionId = loginResult.sessionId;

            isLogin = true;

            return true;
        }

        public void Logout()
        {
            if (!isLogin) return;

            try
            {
                binding.logout();
                isLogin = false;
            }
            catch (SoapException)
            {
                throw;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public List<T> Get<T>(string query) where T: class
        {
            var list = new List<T>();
            QueryResult queryResult;

            try
            {
                queryResult = binding.query(query);
                bool done = false;
                if (queryResult.size > 0)
                {
                    while (!done)
                    {
                        var records = queryResult.records;
                        for (int i = 0; i < records.Length; i++)
                        {
                            var recordType = records[i] as T;
                            list.Add(recordType);
                        }
                        if (queryResult.done) done = true;
                        else queryResult = binding.queryMore(queryResult.queryLocator);
                    }
                }
            }
            catch (SoapException)
            {
                throw;
            }
            catch (Exception)
            {
                throw;
            }

            return list;
        }
    }
}

好みの問題ですが、usingで使えるようにIDisposableというインターフェースを指定しています。そして、Disposeメソッドを実装して、その中でLogoutメソッドをしています。if文でログインして、trueが返ってきたら処理を実行し、if文を抜けるときにLogoutを実行する方法もあります。どっちでもいいと思いますけどね。

まとめ

Salesforceと外部システムとのインテグレーションは、こんなに簡単に組むことができます。そこそこのシステム開発経験があれば、簡単なデータ同期処理なら別途ベンダーに依頼しなくてもいいレベルです。

例えば、Windows Serverを1台用意して、データのやりとりをサービスアプリケーションとして組んでおけば、定期的に同期処理を実行してSalesforceと外部システム(例えば、社内のシステム)とデータ連携をすることも可能です4。サービスアプリケーションじゃなくても、バッチプログラムとタスクスケジューラーを使ってもいいですね。

また、定期的にSalesforceからデータを取得して何か処理したい場合などに、プログラムを組んでおけば、データローダーを使った場合と比べると、取得するオブジェクトの種類などをUIで指定しなくてもいいので効率的ですね。

  1. あとで参照からSystem.Web.Serviceを選んでいます。
  2. APIリファレンスがSforceを使っているので、それに倣ってSforceとしておきます。ここはなんでもいいと思います。
  3. Salesforce Object Query Languageの頭文字をとったもので、Salesforceのオブジェクト用のSQL的なもの。
  4. Windows Serverじゃなくてもいいけどね。

Follow me!

Feedlyで新着記事をチェックしよう!

Feedlyでフォローしておけば、新着記事をチェックすることができます。ぜひ、この機会にFeedlyに追加しておきましょう。