[Salesforce]ContentAssetとは：静的リソースでは容量が足りないときに便利！

# SalesforceにファイルをUploadしたいとき、どうするか？
Salesforceに静的な資源(画像なりPDFなりCSSなり)を置いて、LWC等でその資源を利用するというシーンはよくあると思います。
そういった時に、どんなファイルストレージを使うべきでしょうか？

一般的には静的リソースとSalesforce Filesが有名だと思いますが、マイナーながら実は **ContentAsset** という第三の選択肢が存在しており、これがなかなか便利なのでこの記事で紹介します。

ContentAssetとは、ズバリ**Salesforce Filesをメタデータとして管理できるようにしたもの**です。強みと弱みも、基本はSalesforce Filesのものを引き継ぎます。しかし、大きな違いとして、**ファイルをメタデータとしてデプロイできる**ということが挙げられます。

これらの違いを、まずは表にまとめてみます。

| 方式 | 強み | 弱み | 主な用途 |
| ---- | ---- | ---- | ---- |
| 静的リソース | <ul><li>メタデータとしてデプロイが可能なので資源管理とリリース手順がシンプルになる。</li><li>Salesforceおよびネットワーク上の中間階層で、複数ユーザで共用のキャッシュを持たせて高速表示することができる。</li> | <ul><li>1ファイルあたり5MBまで。</li><li>組織あたり250MBまで。</li></li><li>バージョニングが不可。</li><li>共有制御が不可。</li></ul> | <ul><li>画像</li><li>CSS</li><li>JavaScriptライブラリ</li></ul>|
| Salesforce Files<br />(ContentVersion & ContentDocument) | <ul><li>1ファイルあたり最大2GBまでいける。<li>ユーザライセンスに大量にバンドルでついてくるファイルストレージが許す限り、大量に格納可能。</li><li>バージョニングが可能。</li><li>細かな共有制御が可能。</li></ul> | <ul><li>メタデータとしてデプロイできないため資源管理とリリース手順が煩雑になる。 </li><li>Salesforceおよびネットワーク上の中間階層でキャッシュされない。</li></ul> | <ul><li>業務で利用するデータ(動画)</li><li>CSS</li><li>JavaScriptライブラリ</li></ul>|
| **ContentAsset** | <ul><li>メタデータとしてデプロイが可能なので資源管理とリリース手順がシンプルになる。</li><li>1ファイルあたり最大2GBまでいける。<li>ユーザライセンスに大量にバンドルでついてくるファイルストレージが許す限り、大量に格納可能。</li><li>バージョニングが可能。</li><li>細かな共有制御が可能。</li></ul> | <ul><li>Salesforceおよびネットワーク上の中間階層でキャッシュされない。</li></ul> | <ul><li>**静的かつ大容量なファイルなんでも(動画、画像、PDF、zip…etc)**</li></ul>|

キャッシュの有無を気にしなければ、静的リソースとSalesforceの良いとこどりですね！

※なお、かつては「ドキュメント」や「Attachment」にファイルを格納することもありましたが、現在ではファイルにとって代わられているため上記の表では割愛します。

#どんな時に便利？

ユースケースの例：

- LWCに貼ったリンクをクリックするとマニュアル(数十MBのPDF)を表示したい
- LWCでボタンをクリックすると動画を再生したい

…余談ですが、私は郵便番号入力時の住所自動入力に使えるコンポーネントを作ろうとした際に使いました。
仕組みとしては、[曰くつきの有名な郵便番号のzip](https://www.post.japanpost.jp/zipcode/download.html)をContentAssetに置いて、LWC初期化時にそれをロードして[JSZip](https://stuk.github.io/jszip/)を使ってJavaScriptで解凍し、郵便番号がテキスト欄に入力されたらzipの中にあるCSVの行を正規表現でひっかけて一致する住所を取ってくるというものです。毎月の郵便番号データ最新化をzip置き換えだけで済ませたかったのとApexガバナ制限を回避したかったのとで、zipをクライアント側にダウンロードしてJavaScriptで解凍して走査するという変わった方式を採りました。なかなかキビキビ動いてくれて、実用に耐えうるものになりました。ただ、その後、[lightning-input-address](https://developer.salesforce.com/docs/component-library/bundle/lightning-input-address)に郵便番号のルックアップ機能があることに気づいたのでこのコンポーネントは捨てることにしました。(要は車輪の再発明でした。)

# どうやって使うの？

### 1. ファイルのUpload
Uploadには以下の2種類があります。

- 方式①：ApexでInsertする

````apex
Blob yourFile = xxxxx; //ここでファイルのBlobを生成
ContentVersion cvForKenAll = new ContentVersion(
  VersionData = yourFile,
  Title = 'Your file',
  PathOnClient = 'yourFile.zip',
  IsAssetEnabled = true //ここが重要！IsAssetEnabledをtrueにしてContentVersionをInsertすることでContentAsset扱いでUploadされる。
);
````

- 方式②：メタデータをデプロイする

````:ファイル階層
force-app/main/default/contentassets
 - yourfile.asset ←ここにバイナリを格納
 - yourfile.asset-meta.xml ←ここに定義(下記ファイル)を格納
````

````yourfile.asset-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<ContentAsset xmlns="http://soap.sforce.com/2006/04/metadata">
    <isVisibleByExternalUsers>true</isVisibleByExternalUsers>
    <language>ja</language>
    <masterLabel>yourfile</masterLabel>
    <versions>
        <version>
            <number>1</number>
            <pathOnClient>yourfile.zip</pathOnClient>
        </version>
    </versions>
</ContentAsset>
````

### 2. ファイルの利用
LWCで以下のように定義する：

````JavaScript:controller
import YOURFILE_URL from '@salesforce/contentAssetUrl/yourfile';
export default class AssetFileExample extends LightningElement {
    yourFileURL = YOURFILE_URL;
}
````
これで「yourFileURL」変数にファイルのURLが格納されるので、あとはfetchするなりHTMLの各種タグのsrcに使うなり、お好きにどうぞ！

# FAQ

####Q: 外部ユーザにも表示させられる？
A: ContentAssetオブジェクトの「isVisibleByExternalUsers」をtrueにセットすれば表示させられる。

####Q: 削除するにはどうすればよいの？
A: ContentAssetが紐づくContentDocumentを削除すれば、ContentDocumentのみならず、紐づくContentAsset / ContentVersionもろとも消えてくれる。ContentAssetが紐づくContentDocumentのIdは、[SELECT ContentDocumentId FROM ContentAsset WHERE DeveloperName='xxxx']で取得可能。

####Q: 既にLWCで参照を張っているContentAssetを削除しようとするとどうなる？
A: エラーになって削除できないようになっている。

####Q: ファイルのバージョンを上げるには？
A: ContentVersionと同じ方法でOK。つまり、新規ContentVersionを、[ContentDocumentId = {ContentAssetが紐づくContentDocumentのId}]を指定しつつInsertすればよい。

# 参考になるヘルプページ
[ContentAsset](https://developer.salesforce.com/docs/atlas.ja-jp.api_meta.meta/api_meta/meta_contentasset.htm)
[コンテンツアセットファイルへのアクセス](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/lwc/create_content_assets)