1

I'm using a @RemoteAction to upload files to Salesforce, saving them as ContentVersion (Chatter files). I'm uploading the base64 Blob of the file.
This works great for small (<1MB) files.
I am NOT using lightning components for this. This is our Angular app calling a remote action with a file that the user uploaded.
Specifically the error I am getting is:

POST https://<instance>.visual.force.com/apexremote net:: ERR_CONNECTION_RESET
Visualforce Remoting Exception: Unable to connect to the server (communication failure).

From what I can tell, this is because of the HTTP POST size limit of 1MB.
Now, if you use the Salesforce-y method (directly uploading a file or embedding Chatter in a VF page) then you're good for 2GB.
How can I do that from my Angular app? Do I need to chunkify the blob myself? Is there a way for me to create a ContentVersion piecemeal?

0

Well, I solved this.
Sort of.
My solution was to forgo the use of a @RemoteAction and instead use Salesforce's REST services.
For those following at home home, there are two ways to do this:
1. The easy way, use JSForce.
2. The stupid way (which I ended up using for reasons), roll your own XmlHttpRequest calls.
Both are essentially the same (behind the scenes JSForce uses XmlHttpRequest to do its thing), so the limit seems to be 25MB.
EDIT: For the adventurous, here's some boilerplate code.

let targetUrl = baseUrl + '/services/data/v37.0/sobjects/ContentVersion';
let xhr = new XMLHttpRequest();
xhr.open('POST', targetUrl, true);
xhr.setRequestHeader('Accept', '*/*');
xhr.setRequestHeader('Authorization', 'Bearer ' + sessionId);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = myCallbackFunction;
xhr.send(JSON.stringify({PathOnClient : fileName, VersionData : base64data});

EDIT September 2019

While searching for a solution to a similar problem, I encountered this question, and can now add functionality to allow uploading large files (>25MB).
Essentially, the best thing to do would be to use a multipart/form-data request. However if you blithely do the following you'll encounter a problem:

let formData = new FormData()
let metadata = {
    PathOnClient: fileName, 
    ContentDocumentId: docId, 
    ReasonForChange : changeReason
    };
formData.append('entity_content', JSON.stringify(metadata));
formData.append('VersionData', fileField.files[0]);
...
xhr.send(formData);

You'll get a request built something like this:

--boundary_string
Content-Disposition: form-data; name="entity_content";

{
    "ContentDocumentId" : "069XXXXXXXXXXXX",
    "ReasonForChange" : "Marketing materials updated",
    "PathOnClient" : "Q1 Sales Brochure.pdf"
}

--boundary_string
Content-Type: application/octet-stream
Content-Disposition: form-data; name="VersionData"; filename="Q1 Sales  Brochure.pdf"

Binary data goes here.

--boundary_string--

Now that is very similar to Salesforce's example in the docs except that it's missing a crucial line: Content-Type: application/json in the first part.
You'll get an error like so: {message: "Multipart message must include a non-binary part", errorCode: "INVALID_MULTIPART_REQUEST"} After much searching and anguish it turns out that this isn't Salesforce's fault! It's a general problem with the FormData object.

The solution is to generate your own request and send it. Like so:

<input type="file" #fileField name="fileName" />

let boundary = '----boundaryString';
...
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
xhr.send(new Blob([this.getRequestDataPart1(boundary), this.fileField.files[0], this.getRequestDataPart2(boundary)]));

getRequestDataPart1(boundary: string): string{
    let data =
    `--${boundary}
    Content-Disposition: form-data; name="entity_content";
    Content-Type: application/json

    {
        "ContentDocumentId" : "069XXXXXXXXXXXX", //leave blank for new ContentDocument
        "ReasonForChange" : "Marketing materials updated", // not actually required
        "PathOnClient" : "${this.fileName}"
    }

    --${boundary}
    Content-Type: ${this.mimeType}
    Content-Disposition: form-data; name="VersionData"; filename="${this.fileName}"

    `;
    return data;
  }

  getRequestDataPart2(boundary: string): string{
    let data =
    `

    ----${boundary}--
    `;
    return data;
  }

I really hope that this helps someone. Maybe me in a year's time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.