function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
kevindotcarkevindotcar 

Download attachments redux

I've searched the forums for info and have tried cobbling together a solution for my needs, but it still doesn't work.   I'm developing a button that is trying to download all attachments for a case. 

The Apex page:

<apex:page standardController="Case" extensions="AllAttachmentsController">
  <h1>Download attachments for case: "{!Case.Id}" </h1>
   {!AttNames}
</apex:page>

And the controller method:

public String[] getAttNames() {
        Id caseid = ApexPages.currentPage().getParameters().get('Id');
        
        String[] attNames = new List<String>();
        String strURL;
        string session = UserInfo.getSessionId();        
        HttpRequest req = new HttpRequest();
        HttpResponse res;
        Http h = new Http();
        String strResult ;
                
        ATT=[SELECT Id, Name FROM Attachment WHERE parentId = :ApexPages.currentPage().getParameters().get('Id')]; 
        integer j = ATT.size();
        
        for(integer i=0; i<j; i++) {                      
            strURL= 'https://' + ApexPages.currentPage().getHeaders().get('Host') 
                + '/servlet/servlet.FileDownload?file=' + ATT[i].Id;
            
            strURL = strURL.replace('visual', 'content');
            req.setHeader('Content-Type', 'application/octet-stream');
            req.setHeader('Authorization', 'OAuth ' + session);
            req.setHeader('Content-Disposition', 
                    'attachment; filename=' + ATT[i].Name);                        
            req.setHeader('Host', ApexPages.currentPage().getHeaders().get('Host'));
            req.setMethod('GET'); 
            req.setEndpoint(strURL);            
            attNames.add(strURL);
            res = h.send(req);       
            
            //...Gotta do it again w/ the return URL...
            strURL =  res.getHeader('Location'); 
            
            System.debug('HTTP REDIRECT (' + session + '): ' + strURL ); 
            req.setEndpoint(strURL);
            res = h.send(req); 
            strResult = res.getBody();              
            System.debug('HTTP Body:' + strResult );
            }
        return attNames;
        }       

 

...But the debug log looks strange to me - in that the "moved" URL is exactly the same as the successful endpoint URL:

15:06:55.095 (95548000)|CALLOUT_REQUEST|[33]|System.HttpRequest[Endpoint=https://c.na4.content.force.com/servlet/servlet.FileDownload?file=00P60000008OlopEAC, Method=GET]
15:06:55.148 (148429000)|CALLOUT_RESPONSE|[33]|System.HttpResponse[Status=Found, StatusCode=302]
15:06:55.148 (148464000)|SYSTEM_METHOD_EXIT|[33]|System.Http.send(APEX_OBJECT)
15:06:55.148 (148500000)|SYSTEM_METHOD_ENTRY|[36]|System.HttpResponse.getHeader(String)
15:06:55.148 (148519000)|SYSTEM_METHOD_EXIT|[36]|System.HttpResponse.getHeader(String)
15:06:55.148 (148536000)|SYSTEM_METHOD_ENTRY|[38]|System.debug(ANY)
15:06:55.148 (148554000)|USER_DEBUG|[38]|DEBUG|HTTP REDIRECT (00D300000000iGb!AQsAQNINwJAlI9D_N1TCE5M438xHt_XNSHaTH2jSvkxiLqdhPh5PkIMvBZ.TnAKbNgXCFsdFQWkLRbcIPhP6DJFNdSYEdNyZ): https://na4.salesforce.com/servlet/servlet.FileDownload?file=00P60000008OlopEAC
15:06:55.148 (148567000)|SYSTEM_METHOD_EXIT|[38]|System.debug(ANY)
15:06:55.148 (148583000)|SYSTEM_METHOD_ENTRY|[39]|System.HttpRequest.setEndpoint(String)
15:06:55.148 (148600000)|SYSTEM_METHOD_EXIT|[39]|System.HttpRequest.setEndpoint(String)
15:06:55.148 (148617000)|SYSTEM_METHOD_ENTRY|[40]|System.Http.send(APEX_OBJECT)
15:06:55.151 (151594000)|CALLOUT_REQUEST|[40]|System.HttpRequest[Endpoint=https://na4.salesforce.com/servlet/servlet.FileDownload?file=00P60000008OlopEAC, Method=GET]
15:06:55.206 (206214000)|CALLOUT_RESPONSE|[40]|System.HttpResponse[Status=Found, StatusCode=302]
15:06:55.206 (206244000)|SYSTEM_METHOD_EXIT|[40]|System.Http.send(APEX_OBJECT)
15:06:55.206 (206282000)|SYSTEM_METHOD_ENTRY|[41]|System.HttpResponse.getBody()
15:06:55.206 (206293000)|SYSTEM_METHOD_EXIT|[41]|System.HttpResponse.getBody()
15:06:55.206 (206307000)|SYSTEM_METHOD_ENTRY|[42]|System.debug(ANY)
15:06:55.206 (206318000)|USER_DEBUG|[42]|DEBUG|HTTP Body:The URL has moved <a href="https://na4.salesforce.com/servlet/servlet.FileDownload?file=00P60000008OlopEAC">here</a>

 

Any help is greatly appreciated.

 

 

 

 

 

 

 

<apex:page standardController="Case" extensions="AllAttachmentsController">
  <!-- Begin Default Content REMOVE THIS -->
  <h1>Download attachments for case: "{!Case.Id}" </h1>
   {!AttNames}
  <!-- select  Id from Attachment Where parentId = "{!Case.Id}" !-->
  <!-- SELECT Name, Body From Attachment  WHERE Id = "{!This.Id}" !-->
  <!-- (dump  Body to browser in base64 encode format... !-->
  <!-- End Default Content REMOVE THIS -->
</apex:page>
Best Answer chosen by Admin (Salesforce Developers) 
kevindotcarkevindotcar

Well, after Simon helpfully told me how not not to do my task and why my methodology would not work,,

it helped steer me to a solution - I'm posting this so that maybe someone else can use it.

 

 

The Apex markup, which is inserted as a custom button on the Case page:
----------------------------------------------------------------
<apex:page standardController="Case" extensions="AllAttachmentsController"> <h1>Link Farm: </h1> <!-- apex:outputLabel value="{!links}"></apex:outputLabel !--> <apex:repeat value="{!attLinks}" var="ad"> <li><a href="{!ad}">CLICK ME</a></li> </apex:repeat> </apex:page>

And next, the controller:
-------------------------
public class AllAttachmentsController {
    public List<Attachment> ATT {get;set;}
    
    public AllAttachmentsController (ApexPages.StandardController stdController) {        
        }
    
     public List<String> getAttLinks() {
        Id caseid = ApexPages.currentPage().getParameters().get('Id');
        
        List<String> attLinks = new List<String>();
        String strURL;
        String strResult ;
                
        ATT=[SELECT Id, Name FROM Attachment
WHERE parentId = :ApexPages.currentPage().getParameters().get('Id')];
        integer j = ATT.size();
        
        for(integer i=0; i<j; i++) {                      
            
            strURL= 'https://' + ApexPages.currentPage().getHeaders().get('Host')
                + '/servlet/servlet.FileDownload?file='  + ATT[i].Id;    
            attLinks.add(strURL);    
            }
        return attLinks ;
        }        

 }

There's a lot of cleanup, but the basic functionality is there.

 

 

 

 

 

 

All Answers

SuperfellSuperfell

Why don't you just query the body field on attachment directly, why are you trying to go via the download servlet ?

kevindotcarkevindotcar

Hi Simon,

 

Thank you for the suggestion.

But isn't having the FileDownload servlet more compact than getting the body (which is either .rtf, .doc, .docx, .pdf and lots of other formats), figuring out what it is, and then writing the stream to the browser with the right MIME type?

 

Also, the attachments are usually in the 3 to 5 MB range.

 

TIA for any info.

SuperfellSuperfell

Perhaps you can backup a little bit and explain exactly what you're trying to acheive, currently your controller code appears to want to download the attachments in the apex code (and so on the server side where you could just query the body directly), its not clear what you planning to with them from there.

kevindotcarkevindotcar

Hi-

 

When the user pushes a single button on the Apex page, I want the attachment(s) to be prompted -

much like when a user right-clicks and selects "save as" when the standard case view page displays the list of attachments with the "View" link.

 

That's why I'm trying (at least) to do it from the Apex side.

 

Thx again for any info.

 

 

SuperfellSuperfell

I would think you'd have to do all the client side with a series of popups or something, you can't return multiple files from a single http request without packing them into a zip file or similar.

kevindotcarkevindotcar

Bummer.

But, moving on, I'm now trying to modify my "All Attachments" button to just display a list of links.   The new controller code builds the links as a string:

 

public class AllAttachmentsController {
    public List<Attachment> ATT {get;set;}
    
    public AllAttachmentsController (ApexPages.StandardController stdController) {        
        }
    
     public String getAttLinks() {
        Id caseid = ApexPages.currentPage().getParameters().get('Id');
        
        String attLinks;
        String strURL;
        string session = UserInfo.getSessionId();        
        HttpRequest req = new HttpRequest();
        HttpResponse res;
        Http h = new Http();
        String strResult ;
                
        ATT=[SELECT Id, Name FROM Attachment WHERE parentId = :ApexPages.currentPage().getParameters().get('Id')];
        integer j = ATT.size();

        for(integer i=0; i<j; i++) {                      
            
            strURL= '<a href=https://' + ApexPages.currentPage().getHeaders().get('Host')
                + '/servlet/servlet.FileDownload?file=' + ATT[i].Id + '>' + ATT[i].Id + '</a><br>';    
            attLinks +=   strURL;     
            }
        return attLinks ;
        }       
 }

 

... but the page appears to be rendering the braces as XML (&lt; or &gt;)  which makes the links not work.

Is there a method by which my controller can return a "link farm" without getting it turned into XML text?

 

Thanks Simon,

 

kevindotcarkevindotcar

Well, after Simon helpfully told me how not not to do my task and why my methodology would not work,,

it helped steer me to a solution - I'm posting this so that maybe someone else can use it.

 

 

The Apex markup, which is inserted as a custom button on the Case page:
----------------------------------------------------------------
<apex:page standardController="Case" extensions="AllAttachmentsController"> <h1>Link Farm: </h1> <!-- apex:outputLabel value="{!links}"></apex:outputLabel !--> <apex:repeat value="{!attLinks}" var="ad"> <li><a href="{!ad}">CLICK ME</a></li> </apex:repeat> </apex:page>

And next, the controller:
-------------------------
public class AllAttachmentsController {
    public List<Attachment> ATT {get;set;}
    
    public AllAttachmentsController (ApexPages.StandardController stdController) {        
        }
    
     public List<String> getAttLinks() {
        Id caseid = ApexPages.currentPage().getParameters().get('Id');
        
        List<String> attLinks = new List<String>();
        String strURL;
        String strResult ;
                
        ATT=[SELECT Id, Name FROM Attachment
WHERE parentId = :ApexPages.currentPage().getParameters().get('Id')];
        integer j = ATT.size();
        
        for(integer i=0; i<j; i++) {                      
            
            strURL= 'https://' + ApexPages.currentPage().getHeaders().get('Host')
                + '/servlet/servlet.FileDownload?file='  + ATT[i].Id;    
            attLinks.add(strURL);    
            }
        return attLinks ;
        }        

 }

There's a lot of cleanup, but the basic functionality is there.

 

 

 

 

 

 

This was selected as the best answer
LogeshLogesh

Hi Simon/ Kevin,

 

I'm having a scenario where I need exactly what you mentioned. I need to download all the attachment for a particular custom object as a single zip file on click of a button. Here I categorize attachment based on attachment description field.

Consider there are categories such as A, B & C. Under category A, there are 5 attachments (which means these 5 attachment will have description as A) When I click on a button, I need to download all the 5 attachments belonging to A category as a single zip file. Can you guide me??

 

Thanks & Regards,

Logesh

sfdc analyticssfdc analytics
I have exact all sets of KRYTERION SCREENPRINT dump for Salesforce Certified Administrator (ADM201). Contact me - sfdcanalyticsinfoat22@gmail.com