SalesforceのAPIを使わずに外部システムのIDから詳細ページのリンクを生成する

# はじめに

Salesforceのオブジェクトの外部IDとして、usersのような会員IDを使っていますでしょうか？
その際に、Salesforce外のシステムでSalesforceのレコード詳細へのリンクを生成したいけど
APIを使わないといけない？？と思っている方への一つの選択肢として今回は`Visualforce` + `Apex`を使ってSalesforceのレコード詳細にアクセスできるリンクを作ろうと思います。

```mermaid
erDiagram

users {
    integer id PK
    string name
  }
```

:::note
検証に使う環境は、Trailheadのハンズオン組織です。
データに関してもデモデータ + 検証で作ったデータしか入っていません
:::

# 使うもの
- Visualforce
- Apex

# 最終的にどうなるの？

こんな感じで、
- 作成したVisualforceページへのリンクに外部IDをパラメータに付与してアクセス
- Apexで外部IDからレコードを検索し、リダイレクト設定
- 外部IDを持つレコード詳細ページにリダイレクト

![無題の図形描画 (22)](https://user-images.githubusercontent.com/39574826/208217298-8992cf64-7997-4277-bf1e-a4d552d19fa6.jpg)

# 事前準備

## 外部ID項目の作成

Account オブジェクトに`ExternalId__c`という外部ID項目を作成しました。

![取引先-Salesforce](https://user-images.githubusercontent.com/39574826/208218594-75b5a09f-b639-4bf5-b32a-c349873a2d56.png)

## データの準備

外部ID 1のデータを生成しました。

![テスト取引先1-Salesforce (2)](https://user-images.githubusercontent.com/39574826/208218785-67439376-c2dc-426c-b488-74465c293860.png)

# Apexの作成

- 受け取るパラーメータ
    - id
        - 外部IDの値
    - type
        - リクエストのタイプ、検索するオブジェクトと外部ID項目のAPI参照名取得に利用

```apex: RedirectObjectViewController.cls
public with sharing class RedirectObjectViewController {
  private Map<String, Map<String, String>> keyMap = new Map<String, Map<String, String>>{
    'users' => new Map<String, String>{
      'object_key' => 'ExternalId__c',
      'object_name' => 'Account'
    }
  };

  public Pagereference doOpenObjectView() {
    try {
      String id = ApexPages.currentPage().getParameters().get('id');
      String type = ApexPages.currentPage().getParameters().get('type');
      String objectName = '';
      String errorMessage = getErrorParameters(id, type);
      if (!String.isBlank(errorMessage)) {
        ApexPages.addMessage(
          new ApexPages.Message(ApexPages.Severity.WARNING, errorMessage)
        );

        return null;
      }

      List<Sobject> records = Database.query(
        'SELECT Id ' +
        'FROM ' +
        String.escapeSingleQuotes(this.keyMap.get(type).get('object_name')) +
        ' ' +
        'WHERE ' +
        String.escapeSingleQuotes(this.keyMap.get(type).get('object_key')) +
        ' = \'' +
        String.escapeSingleQuotes(id) +
        '\' LIMIT 1'
      );

      if (records.isEmpty()) {
        ApexPages.addMessage(
          new ApexPages.Message(
            ApexPages.Severity.WARNING,
            createApexMessage(id, type)
          )
        );
        return null;
      }

      ApexPages.StandardController stdCon = new ApexPages.StandardController(
        records[0]
      );
      Pagereference redirectTo = stdCon.view();
      redirectTo.setRedirect(true);

      return redirectTo;
    } catch (Exception ex) {
      ApexPages.addMessages(ex);
      return null;
    }
  }

  private String getErrorParameters(String id, String type) {
    if (!this.keyMap.containsKey(type)) {
      return '正しいリクエストタイプを指定してください。';
    }
    if (String.isBlank(id)) {
      return '外部IDを指定ください。';
    }

    return '';
  }

  private String createApexMessage(String id, String type) {
    return '該当のレコードはSalesforceに存在しません。\n\n' +
      'オブジェクト: ' + this.keyMap.get(type).get('object_name') + '\n\n' +
      '外部ID: ' + id;
  }
}
```

## コードについて

外部システムのテーブル名と、Salesforceのオブジェクト名・外部ID項目名のマッピングを作っています。

```apex
  private Map<String, Map<String, String>> keyMap = new Map<String, Map<String, String>>{
    'users' => new Map<String, String>{
      'object_key' => 'ExternalId__c',
      'object_name' => 'Account'
    }
  };
```



取得したレコード情報でStandardControllerをインスタンス化

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_pages_standardcontroller.htm

```diff_apex
+ ApexPages.StandardController stdCon = new ApexPages.StandardController(
+   records[0]
+ );
 Pagereference redirectTo = stdCon.view();
 redirectTo.setRedirect(true);

 return redirectTo;
```

Pagereferenceへ詳細ページの PageReference オブジェクトを入れる

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_pages_standardcontroller.htm#apex_ApexPages_StandardController_view

```diff_apex
 ApexPages.StandardController stdCon = new ApexPages.StandardController(
   records[0]
 );
+ Pagereference redirectTo = stdCon.view();
  redirectTo.setRedirect(true);

 return redirectTo;
```

クライアント側のリダイレクトでリダイレクトが実行されるように`setRedirect(true)`

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_System_PageReference_setRedirect.htm

```diff_apex
 ApexPages.StandardController stdCon = new ApexPages.StandardController(
   records[0]
 );
 Pagereference redirectTo = stdCon.view();
+ redirectTo.setRedirect(true);

+ return redirectTo;
```

# Visualforceの作成

- 作詞した`RedirectObjectViewController`をセット
- apex:pageMessagesでApexからのエラーメッセージを表示

```html: doOpenObjectView.page
<apex:page
  controller="RedirectObjectViewController"
  showHeader="true"
  sidebar="true"
  action="{!doOpenObjectView}"
>
  <apex:form>
    <apex:pageMessages escape="false" />
  </apex:form>
</apex:page>
```

# 動かしてみる

## パラメータなし

利用しているSalesforceのドメイン`/apexdoOpenObjectView`にアクセス

![](https://user-images.githubusercontent.com/39574826/208219458-57755f43-04d5-413c-80a9-e7840ad3f1c3.png)

## リクエストタイプのみ指定

利用しているSalesforceのドメイン`/apexdoOpenObjectView?type=users`にアクセス

![](https://user-images.githubusercontent.com/39574826/208219500-96a96d8e-87a3-4de3-8ed4-7cf73241ecbd.png)

## リクエストタイプ&外部ID（レコードなし）

利用しているSalesforceのドメイン`/apexdoOpenObjectView?type=users&id=100`にアクセス

![](https://user-images.githubusercontent.com/39574826/208219645-84234183-7d38-435e-95c1-6be5685b0800.png)

## リクエストタイプ&外部ID（レコードあり）

利用しているSalesforceのドメイン`/apexdoOpenObjectView?type=users&id=1`にアクセス

ユーザID 1のAccountページにリダイレクトされました！

![テスト取引先1-Salesforce (3)](https://user-images.githubusercontent.com/39574826/208219708-2276b288-5e9a-49f2-9565-9665f2f22054.png)

# さいごに

Salesforceと外部アプリケーションを連携している場合に、外部IDを元に外部アプリケーションへのURLを作るのは、
Salesforceでは簡単。外部アプリケーションから関連するSalesforceへのURLを作るときにどうやって実装をしよう？？となると思いますが、
１つの解決策としての Visualforceページ + Apex でリダイレクトパターンでした。
すでに、Salesforceを開いている場合はこのままだとSalesforeがリロードされてしまうので
微妙な点はあるかもですが、初回表示や業務上Visualforceページ にアクセスするタイミングがSalesforce上でなにか作業している途中ではないパターンでは有効だと思います。

# 参考

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_pages_standardcontroller.htm

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_pages_standardcontroller.htm#apex_ApexPages_StandardController_view

https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_System_PageReference_setRedirect.htm

https://www.biswajeetsamal.com/blog/how-to-redirect-to-a-different-vf-page-from-the-constructor-with-parameters-in-salesforce/