2

is it possible to use external storage to store Attachments?

As a company we have large files that sales need to upload and associate to opportunities. We also want to save on the Salesforce storage costs.

To the sales user it would appear that the attachment is being uploaded into SFDC but behind the scenes it would saved on something like Sharepoint, or Dropbox. SFDC Links to Edit/View/Del the attachment would be redirected to do the operation on the file in external storage.

1

For the salesforce side: you need to add the related list of notes and attachemets on the template layout.

For the external side: I do the same as Amr, I use Amazon sdk JS. Below a generic instructions and below this generic instructions I added more details.

  1. On security credential I add a new user.
  2. When creating this save the access key and secret key.
  3. Create the bucket.
  4. Add in properties for everyone to upload and download.
  5. You need to edit bucket policy and CORS Use SDK in VF Page.

Details: Security credentials and new user:

enter image description here enter image description here

Save keys: enter image description here

Create bucket and modify its properties (property, policy and CORS) enter image description here

Bucket policy, I am using something like: (make sure you replace [MY NEW BUCKET NAME] to the bucket name)

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::[MY NEW BUCKET NAME]/*"
        }
    ]
}

CORS

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

The visualforce will need the following script

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.8.min.js"></script>

I additional add (they are optional but if you copy and paste below you will need to modify the JS)

<script src="//code.jquery.com/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.66.0-2013.10.09/jquery.blockUI.min.js"></script>

The VF Page, make sure you replace the bucket names, policies, region with the right information

<script type="text/javascript">
    j$ = jQuery.noConflict(); 

        function msieversion() {
            var ua = window.navigator.userAgent;
            var msie = ua.indexOf("MSIE ");
            if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)){
                // If Internet Explorer, return version number
                //document.getElementById('{!$Component.formId}').style.display = 'none';
                //alert(parseInt(ua.substring(msie + 5, ua.indexOf(".", msie))));
            } else {
                // If another browser, return 0
                //alert('otherbrowser');
                document.getElementById('noInternetExplorer').style.display = 'none';
            }
            return false;
        }

        function blockme() {
            // Sarissa.XHR = XMLHttpRequest;
            // XMLHttpRequest = Sarissa.originalXMLHttpRequest;

            var stringvalue='{!RandomFileName}';
            AWS.config.update({accessKeyId: 'XXXXXXXX', secretAccessKey: 'YYYYYYY'});
            AWS.config.region = 'ZZZZZZZZ';
            //AWS.config.region = 'ap-southeast-2';

            var bucket = new AWS.S3({params: {Bucket: 'XXXXXXXXX'}});
            var fileChooser = document.getElementById('file-chooser');             
            var file = fileChooser.files[0];

            if (file) {
                j$.blockUI({ css: {
                    border: 'none',
                    padding: '15px',
                    backgroundColor: '#000',
                    '-webkit-border-radius': '10px',
                    '-moz-border-radius': '10px',
                    opacity: .5,
                    color: '#fff'
                }
                });

                var finenamefull=file.name;
                finenamefull = finenamefull.replace(/,/gi, ""); 
                var arrayOfStrings = finenamefull.split('.');
                var noofStrings=arrayOfStrings.length;
                var extension=arrayOfStrings[noofStrings-1];
                var filenamewithext=stringvalue+'.'+extension;
                var params = {Key: filenamewithext, ContentType: file.type, Body: file};
                var r = finenamefull+','+file.type;
                var url = 'https://s3-ap-southeast-2.amazonaws.com/s3-manza-sf/'+stringvalue+'.'+extension;
                console.log(url);
                document.getElementById("awsId").value = r;
                document.getElementById("urlAmazon").value = url;

                bucket.putObject(params, function (err, data) {
                    if(err){
                        alert('Something is wrong and your file was not uploaded');
                    }else{
                        var z = 'status'
                        document.getElementById("awsIdStatus").value = z;
                        console.log(err);
                        console.log(data);
                        j$.unblockUI();
                    }
                });



            } 
            // XMLHttpRequest = Sarissa.XHR  
        }//END FUNCTION

        window.onload=function(){
            //CHECK IF IT IS IE, AND GIVE A WARNING TO PLEASE USE A DIFFERENT BROWSER
            msieversion();  
        };

</script>

<apex:form id="form">
    <apex:pageMessages />
    <apex:pageBlock title="Images" mode="edit">
        <apex:pageBlockButtons >
            <apex:commandButton action="{!save}" value="Save"/>
            <apex:commandButton action="{!cancel}" value="Cancel"/>
        </apex:pageBlockButtons>
        <apex:pageBlockSection title="Image Detail" columns="1">
            <apex:pageBlockSectionItem >
                <input type="file" id="file-chooser" onchange="blockme()" /> 
            </apex:pageBlockSectionItem>
            <input type="hidden" value="awsId" id="awsId" name="awsId"/>
            <input type="hidden" value="awsIdStatus" id="awsIdStatus" name="awsIdStatus"/>
            This input is hidden but for sake of excercise is displayed
            <input type="text" value="" id="urlAmazon" name="urlAmazon"/>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:form>

There is more work that you can do to improve the JS

I also have a random string been generated by the controller and assignt the name of the file this string this way it is save in amazon with a random name instead of the original.

Also you will need to modify the save method as in this case it pass the URL to a hidden field. I add a method to check if the browser is a IE, as i think the SDK playes up a bit when using IE. So in my page it displays an error advising to use other browser and not showing the upload form.

I hope this helps

0

We are doing the same approach with hosting files in AWS S3. What you might want to do is add some security around this. Have a hash key in the files URL that is stored in Salesforce against a record or custom object (if you have many records/ files attached to a single record). Salesforce will be only serving direct links of files to authorized users.

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