やりたいこと
C# で SOAPAPI を利用して SalesForce 上のオブジェクトを操作
公式ドキュメントはこちら
Enterprise 版
- 強い型付け
- typo に強い
- カスタムオブジェクトや項目が追加・更新された場合は再生成が必要
Partner 版の記事はこちら
事前準備
WSDL 追加
ブラウザでやること
・SalesForce 設定画面の左メニュー「ビルド - 開発 - API」で Enterprise WSDL の作成
・XMLが表示されたURLをコピー
※VSのweb参照追加画面からやるとJSエラーで表示できないため
VisualStudio でやること
参照追加
・ソリューションエクスプローラーで[追加]→[サービス参照]→[詳細設定]→[Web参照の追加]
・コピーしたURLを貼り付けるとSalseForceログイン画面が表示される
・ログインするとWSDLが表示されるので、適当なweb参照名(今回はsf_test)を付けて追加
Reference.cs修正
なんかそのままだとエラーが出る
System.InvalidOperationException
HResult=0x80131509
Message=一時クラスを生成できません (result=1)。
error CS0030: 型 'SFAPI.sf_test.ListViewRecordColumn[]' を型 'SFAPI.sf_test.ListViewRecordColumn' に変換できません。
error CS0030: 型 'SFAPI.sf_test.ListViewRecordColumn[]' を型 'SFAPI.sf_test.ListViewRecordColumn' に変換できません。
error CS0029: 型 'SFAPI.sf_test.ListViewRecordColumn' を型 'SFAPI.sf_test.ListViewRecordColumn[]' に暗黙的に変換できません。
error CS0029: 型 'SFAPI.sf_test.ListViewRecordColumn' を型 'SFAPI.sf_test.ListViewRecordColumn[]' に暗黙的に変換できません。
Reference.cs
内のListViewRecordColumn
に余計な[]
が付いてるっぽいので削除(2箇所)
//private ListViewRecordColumn[][] recordsField; // 削除
private ListViewRecordColumn[] recordsField; // 追加
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute("columns", typeof(ListViewRecordColumn), IsNullable=false)]
//public ListViewRecordColumn[][] records { // 削除
public ListViewRecordColumn[] records { // 追加
get {
return this.recordsField;
}
set {
this.recordsField = value;
}
}
実装
ログイン/ログアウト
何はともあれログイン処理
テスト用に適当なWindowsフォームで実装
本来はログイン→なんらかのAPI処理→ログアウトまでをセットにした方がよさげ
using SFAPI.sf_test;
using System;
using System.Web.Services.Protocols;
using System.Windows.Forms;
namespace SFAPI
{
public partial class Form1 : Form
{
// bindingオブジェクト
private SforceService binding;
public Form1()
{
InitializeComponent();
// 初期化
binding = new SforceService();
binding.Timeout = 30000;
}
/// <summary>
/// ログインボタン
/// </summary>
private void LoginBtn_Click(object sender, EventArgs e)
{
Login();
}
/// <summary>
/// ログイン処理
/// </summary>
/// <returns></returns>
private bool Login()
{
string username = "SalesForceのログインID";
string password = "SalesForceのパスワード";
LoginResult loginResult;
try
{
loginResult = binding.login(username, password);
}
catch (SoapException e)
{
// 失敗
MessageBox.Show(e.Code + ":" + e.Message);
return false;
}
if (loginResult.passwordExpired)
{
// パスワード失効
// binding.setPassword(userId, newPassword) で更新が必要
return false;
}
// エンドポイント更新
string authEndPoint = binding.Url; // 初期化時のログイン認証URL
binding.Url = loginResult.serverUrl; // 認証後のサービス用URL
// セッションヘッダ追加
binding.SessionHeaderValue = new SessionHeader();
binding.SessionHeaderValue.sessionId = loginResult.sessionId;
// 結果表示
GetUserInfoResult userInfo = loginResult.userInfo;
string msg = "";
msg += "UserID: " + userInfo.userId + "\r\n";
msg += "User Full Name: " + userInfo.userFullName + "\r\n";
msg += "User Email: " + userInfo.userEmail + "\r\n";
msg += "\r\n";
msg += "SessionID: " + loginResult.sessionId + "\r\n";
msg += "Auth End Point: " + authEndPoint + "\r\n";
msg += "Service End Point: " + loginResult.serverUrl + "\r\n";
MessageBox.Show(msg);
return true;
}
/// <summary>
/// ログアウトボタン
/// </summary>
private void LogoutBtn_Click(object sender, EventArgs e)
{
Logout();
}
/// <summary>
/// ログアウト
/// </summary>
private bool Logout()
{
try
{
binding.logout();
}
catch (SoapException e)
{
MessageBox.Show(e.Message);
return false;
}
return true;
}
}
}
オブジェクト操作
例として Contact の操作
/// <summary>
/// Contact取得
/// </summary>
private void getContacts()
{
// SOQL発行
// リレーション項目も取得可能(取引先担当者が所属する取引先名Account.Name)
String soqlQuery = "SELECT Id, FirstName, LastName, Account.Name FROM Contact";
try
{
// デフォルトだと500件までしか取れない
QueryResult qr = binding.query(soqlQuery);
string msg = "";
while (true)
{
sObject[] records = qr.records;
for (int i = 0; i < records.Length; i++)
{
Contact con = (Contact)records[i];
msg += con.Id + ":" + con.LastName + " " + con.FirstName + " : " + con.Account.Name + "\r\n";
}
if (qr.done)
{
// おわり
break;
}
else
{
// 500件以上存在する場合、次の500件を取得
qr = binding.queryMore(qr.queryLocator);
}
}
MessageBox.Show(msg);
}
catch (Exception ex)
{
// エラー
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// Contact更新
/// </summary>
public void upsertContacts()
{
// 更新対象配列作成(テストなので適当)
sObject[] upserts = new Contact[1];
Contact c0 = new Contact();
c0.Email = "test@example.com";
c0.LastName = "てすと";
c0.FirstName = "たろう";
c0.AccountId = "001000000000000";
// カスタム項目を扱う場合、指定フラグを立てる必要がある
c0.CustomDate__cSpecified = true;
c0.CustomDate__c = System.DateTime.Now;
upserts[0] = c0;
try
{
// Email一致の場合UPDATE、それ以外の場合INSERT
UpsertResult[] upsertResults = binding.upsert("Email", upserts);
string msg = "";
foreach (UpsertResult result in upsertResults)
{
if (result.success)
{
msg += result.id + " : " + (result.created ? "Insert" : "Update") + "\r\n";
}
else
{
MessageBox.Show("Error!: " + result.errors[0].message);
}
}
MessageBox.Show(msg);
}
catch (SoapException e)
{
MessageBox.Show(e.Message);
}
}
おわりに
Reference.cs 修正しないと動かないのが謎。
別サービスでも似たようなことがあったし流行ってるんですかね…
カスタムオブジェクトや項目の追加、変更があった場合、
WSDL 読み直しになるから面倒。