#Javascript Remoting の引数の上限文字数は 1,000,000 文字
Visualforce ページから任意のレコードに対し添付ファイルをアップロードしようとすると、「input too long」エラーで怒られてしまう事がよくあります。
Visualforce から非同期で Apex と通信できる Javascript Remoting では、その引数の長さが 1,000,000 文字を超えることが出来ません。
そのため、base64 エンコードしたファイルなどを渡す際は少し工夫する必要があります。
#Apex の String 型変数の上限文字数は 6,000,000 文字
以下にその対処方法の例を記載していますが、これも万能ではありません。
base64 でエンコーディングした文字列は Apex 上で String として扱われます。Apex の String 型変数の上限文字数は 6,000,000 文字となるため、約 5MB 弱のサイズのファイルしか扱えないことに注意してください。
#input too long エラーを回避するサンプルコード
基本的な対応方針は、multipart リクエストのように、送信するデータを分割し末尾再帰的に複数回に分けて送信します。
それに合わせて、Apex コードではデータ文字列を結合する事で復元しながら保存していきます。
<apex:page controller="AttachmentUploadController" >
<input id="js-input-file" type="file" />
<input type="button" value="送信" onclick="submit()" />
<script>
function submit() {
// ファイルを取得
var file = document.getElementById('js-input-file').files[0];
// file を読み込む
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
// 親レコードの ID、ファイル名、ファイルの data uri を指定する。
uploadAttachment('0016F00001puC19', file.name, reader.result);
};
reader.onerror = function (error) {
console.log('ERROR: ', error);
};
}
var maxSizeForApexString = 6000000; // Apex の String 型変数の最大文字数
var chunkSize = 950000; // Javascript Remoting で一度に送信したい文字数の上限
function uploadAttachment(parentId, fileName, dataURI) {
var contentType = dataURI.split(',')[0].split(':')[1].split(';')[0];
var dataString = dataURI.split(',')[1];
var recordId = null;
var curIndex = 0;
if(dataString.length < maxSizeForApexString) {
console.log('Start uploading');
upload(parentId, recordId, fileName, contentType, dataString, curIndex);
} else {
alert("File is too big.");
}
}
function upload(parentId, recordId, fileName, contentType, dataString, curIndex) {
var chunk = dataString.slice(curIndex, curIndex + chunkSize);
curIndex += chunkSize;
AttachmentUploadController.uploadAttachment(
parentId, recordId, fileName, contentType, chunk,
function(result, event) {
if (event.status) {
if (curIndex > dataString.length) {
console.log('Uploading completed');
} else {
console.log('data sent: ' + curIndex + '/' + dataString.length);
upload(parentId, result, fileName, contentType, dataString, curIndex);
}
} else if (event.type === 'exception') {
console.error("EXCEPTION: " + event.message);
} else {
console.error("ERROR: " + event.message);
}
},
{ buffer: false, escape: true, timeout: 30000 }
);
}
</script>
</apex:page>
public class AttachmentUploadController {
@RemoteAction
public static String uploadAttachment(String parentId, String recordId, String fileName, String contentType, String dataString) {
Attachment attachment;
if (String.isEmpty(recordId)) {
attachment = new Attachment(Name=fileName, ContentType=contentType, parentId=parentId, Body=EncodingUtil.base64Decode(dataString));
} else {
attachment = [SELECT Id, Body FROM Attachment WHERE Id =: recordId];
String currentBody = EncodingUtil.base64Encode(attachment.Body);
attachment.Body = EncodingUtil.base64Decode(currentBody + dataString);
}
upsert attachment;
return attachment.Id;
}
}
#添付ファイル以外のファイル送信
Files
public class FileUploadController {
@RemoteAction
public static String uploadFileAndLinkTo(String parentId, String recordId, String fileName, String dataString) {
ContentVersion content;
if (String.isEmpty(recordId)) {
content = new ContentVersion(Title=fileName, VersionData=EncodingUtil.base64Decode(dataString), PathOnClient='/' + fileName);
insert content;
content = [select id, ContentDocumentId from ContentVersion WHERE Id =: content.Id];
ContentDocumentLink link = new ContentDocumentLink(ContentDocumentId=content.ContentDocumentId, LinkedEntityId=parentId, ShareType='V', Visibility='AllUsers');
insert link;
} else {
ContentVersion partialContent = [SELECT Id, ContentDocumentId, VersionData FROM ContentVersion WHERE Id =: recordId];
String currentData = EncodingUtil.base64Encode(partialContent.VersionData);
content = new ContentVersion(Title=fileName, PathOnClient='/' + fileName, ContentDocumentId=partialContent.ContentDocumentId);
content.VersionData = EncodingUtil.base64Decode(currentData + dataString);
insert content;
}
return content.Id;
}
}