+ Start a Discussion
Rahul Kumar DeyRahul Kumar Dey 

Return files in url format associate with record (Example- Case) using REST API webservice?

Hi All,

I have created a rest api webservice to insert contentVersion into salesforce but now I want to return the list of files to external system using rest api webservice. And I don't want to send exact files associated with case instead I want to send url so, external user can easily access  files from this url (list of url).
Any help how to proceed?
And any sample code really appreciate...


Best Regards,
Rahul 
Best Answer chosen by Rahul Kumar Dey
Sachin HoodaSachin Hooda
Hi Rahul,
Can you please try creating a public link manually.
1. Get either of ContentDocument Id or ContentVersion Id of the file associated with your case.
2. Navigate to https://<your-instance>.my.salesforce.com/contentId where contentId is the one you got from step one.
3. Now create a public link for the file.

Now try calling the API you created with the same case Id.
If it works, 
Create a new instance of ContentDistrubution and add ContentVersionId & ContentDocumentId to it & insert it.
I hope it helps you.
___________
Regards,
Sachin
(:

All Answers

Sachin HoodaSachin Hooda
Hi Rahul,
If you're talking about the public link of the ContentVersion file. You can achieve this using ContentDistributionUrl field of ContentDistribution SObject.
To get the public link of the file, You need to query over ContentDistribution SObject using ContentDocumentId of the file you inserted.
ContentVersion cv = [select id,ContentDocumentId from ContentVersion where id =: conVer.id];
//Create ContentDocumentLink
ContentDocumentLink cDe = new ContentDocumentLink();   
cDe.ContentDocumentId = cv.contentDocumentId;
cDe.LinkedEntityId = parentRecId;
// you can use objectId,GroupId etc        
cDe.ShareType = 'V';
// Inferred permission, checkout description of ContentDocumentLink object for more details         //Insert content document link        
insert cDe ;                       
//ContentDistribution: Represents information about sharing a document externally           ContentDistribution cd = new ContentDistribution();        
cd.Name = 'Test File';         
cd.ContentVersionId = conVer.Id;                      
//insert          
 insert cd;                    
ContentDistribution distribution = [select Name,ContentDownloadUrl from ContentDistribution where id=: cd.id];
//return ContentDistributionUrl
return distribution.ContentDistributionUrl;

Please follow this (https://sfdclesson.com/2019/03/17/show-the-uploaded-image-on-the-detail-page/" target="_blank)link to achieve the functionality.

Or if you want download link of the ContentVersion File, you simply need to get the host URL & append ContentVersion Id with it.
This is the link where your file will be stored.
https://<your-instance>.lightning.force.com/sfc/servlet.shepherd/version/download/'+cv.Id+'?operationContext=S1
 
ContentVersion cv = new ContentVersion();
cv.title = 'test.png';
cv.PathOnClient = '/test.png';
cv.VersionData = Blob.ValueOf(b64String);
try{
INSERT cv;
String url = URL.getSalesforceBaseUrl().toexternalForm();
String downUrl= url.substring(0,url.indexOf('.my.lightning.force.com'));
return (downUrl +'--c.documentforce.com/sfc/servlet.shepherd/version/download/'+cv.Id+'?operationContext=S1');
}
catch(Exception ex){
System.debug(ex.getMessage());
}


In case you still have any doubts. Feel free to post them here.
______
Regards,
Sachin
(:
Rahul Kumar DeyRahul Kumar Dey
Hi Sachin,

Thanks for reply--

Please check my code below-----
 
@RestResource(urlMapping='/myCaseHistoryList/*')
global with sharing class myCaseHistoryList {
    @HttpGet
    global static requestDetailsWrapper getCasesHistory(){
        
        RestRequest request = RestContext.request;    
        System.debug('===> requestURI'+request.requestURI);
        String currentcaseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);       
        system.debug('currentcaseId'+currentcaseId);
        
        Set<Id> currentcaseIds = new Set<Id>();    
        for (Case cc : [select Id from case where casenumber =: currentcaseId])
        {         
            currentcaseIds.add(cc.id); 
        } 
        Set<String> FieldValue = new Set<String>();  
        List<XcelerateCaseHistFields__c> Testi =  XcelerateCaseHistFields__c.getAll().values();
        for(XcelerateCaseHistFields__c NewField : Testi)
        {  
            FieldValue.add(NewField.Name);
        }
        system.debug('FieldValue'+FieldValue);
        Map<String, XcelerateCaseHistFields__c> allstates = XcelerateCaseHistFields__c.getAll();
        system.debug('currentcaseIds'+currentcaseIds);
        List<CaseHistory> caseList = [select Field, NewValue, createdby.Name, CreatedDate from CaseHistory
                                      where CaseId =: currentcaseIds and Field =:FieldValue order by Field,CreatedDate DESC];
        system.debug('caseList'+caseList);
        requestDetailsWrapper reqDelRap       = new requestDetailsWrapper();
        List<caseHistoryWrapper> caseHistory  = new List<caseHistoryWrapper>();
        
        for(CaseHistory a : caseList)
        {   
            CaseHistoryWrapper caseHistWrap = new CaseHistoryWrapper();
            caseHistWrap.Field              = allstates.get(a.Field).XcelerateFields__c;
            String NewValues                =  String.valueOf(a.NewValue);
            System.debug('NewValues'+NewValues);
            System.debug('a.Field'+a.Field);
            
            if (String.isBlank(NewValues) || NewValues.startsWithIgnoreCase('012') && a.Field == 'RecordType')
            {          
                continue;
            }
            Else
            {
                caseHistWrap.NewValue       = NewValues;
            }
            
            caseHistWrap.ChangedbyUser      = a.createdby.Name;
            caseHistWrap.ChangedDate        = a.CreatedDate;
            caseHistory.add(caseHistWrap);
        }
        
        List<CaseComment> commentList =  [SELECT CommentBody,CreatedBy.Name,CreatedDate FROM CaseComment where ParentId =: currentcaseIds order by CreatedDate DESC];
        
        for(CaseComment cc : commentList)
        {        
            caseHistoryWrapper commentWrap   = new caseHistoryWrapper();
            commentWrap.NewValue             = cc.CommentBody;
            commentWrap.Field                = 'Comment';
            commentWrap.ChangedDate          = cc.CreatedDate;
            commentWrap.ChangedbyUser        = cc.createdby.Name;
            caseHistory.add(commentWrap);
        }
        List<ContentDocumentLink> AttachmentList;
        try
        {
            AttachmentList     = [SELECT ContentDocument.title FROM ContentDocumentLink WHERE LinkedEntityId =: currentcaseIds];
            
            system.debug('AttachmentList'+AttachmentList);
            List<attachmentWrapper> attachments = new List<attachmentWrapper>();          
            for (ContentDocumentLink attach: AttachmentList)
            {
                attachmentWrapper attachWrap = new attachmentWrapper();
                attachWrap.AttachmentName    = attach.ContentDocument.title;
                attachments.add(attachWrap);
            }
            
            reqDelRap.caseHistory  = caseHistory; 
            reqDelRap.attachments  = attachments;
        }catch(QueryException e) 
        {
            
            System.debug(e.getMessage());
        }
        return reqDelRap;
    }
    
    global class requestDetailsWrapper{
        public List<caseHistoryWrapper> caseHistory;
        public List<attachmentWrapper> attachments;  
    }
    global class caseHistoryWrapper {
        public String Field;  
        public String NewValue;
        public String ChangedbyUser;
        public DateTime ChangedDate;
        
    }
    
    global class attachmentWrapper {
        Public String AttachmentName; 
    }
}

User-added image

Right now I only return attachment name for testing purposes but we want to implement that, return url which is publicly accessable file and download.
Please note I created httpPost method in separate api. So, I'm not creating file again here.

Any luck based on my code? How to achieve...

Thanks,
Rahul 
 
Sachin HoodaSachin Hooda
Hi Rahul,
I've updated your code.
List<ContentDocumentLink> AttachmentList;
        try
        {
            AttachmentList     = [SELECT ContentDocument.title, ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId =: currentcaseIds];
            ContentDistribution distribution = [Select Name,ContentDownloadUrl,DistributionPublicUrl from ContentDistribution where id=: currentcaseIds];
            system.debug('AttachmentList'+AttachmentList);
            List<attachmentWrapper> attachments = new List<attachmentWrapper>();          
            for (ContentDocumentLink attach: AttachmentList)
            {
                attachmentWrapper attachWrap = new attachmentWrapper();
				attachWrap.atachmentId = attach.ContentDocumentId;
                attachWrap.AttachmentName    = attach.ContentDocument.title;
				attachWrap.attachUrl = distribution.DistributionPublicUrl;
				attachWrap.attachDownUrl = distribution.ContentDownloadUrl;
                attachments.add(attachWrap);
            }
            
            reqDelRap.caseHistory  = caseHistory; 
            reqDelRap.attachments  = attachments;
        }catch(QueryException e) 
        {
            
            System.debug(e.getMessage());
        }
        return reqDelRap;
    }
    
    global class requestDetailsWrapper{
        public List<caseHistoryWrapper> caseHistory;
        public List<attachmentWrapper> attachments;  
    }
    global class caseHistoryWrapper {
        public String Field;  
        public String NewValue;
        public String ChangedbyUser;
        public DateTime ChangedDate;
        
    }
    
    global class attachmentWrapper {
        Public String AttachmentName; 
		public String attachUrl; // here preview will be shown & file will be downloaded after click.
		public String attachDownUrl; // this will be direct download link for the file.
    }
Please update the code from line 65 to 103 at your end.
If any problems occur, please post them here.
________
Regards,
Sachin
(:
Rahul Kumar DeyRahul Kumar Dey
Hi Sachin,

After I snipped your code, it returns null value both for case History and attachments.

User-added image
But I have 2 files associated with this case...
Rahul Kumar DeyRahul Kumar Dey
Basically it will go to query exception not in try block- 
USER_DEBUG [87]|DEBUG|List has no rows for assignment to SObject
Sachin HoodaSachin Hooda
Hi Rahul,
Its because we've made a mistake during querying ContentDistribution.
Please update the query to 
List<ContentDistribution> distributionList = [Select Name,ContentDownloadUrl,DistributionPublicUrl from ContentDistribution where RelatedRecordId =: currentcaseIds];
& let me know if it worked. If you've any other issues, please post them here.
_____________
Regards,
Sachin
(:
Sachin HoodaSachin Hooda
Additionally, you need to iterate over the ContentDistribution list to fill the wrapper.
Please update the code like this.
for (ContentDocumentLink attach: AttachmentList)
            {
            for(ContentDistribution cd : distributionList){
                attachmentWrapper attachWrap = new attachmentWrapper();
                attachWrap.AttachmentName    = attach.ContentDocument.title;
                attachWrap.attachUrl = cd.DistributionPublicUrl;
                attachWrap.attachDownUrl = cd.ContentDownloadUrl;
                attachments.add(attachWrap);
             }
               
}
Rahul Kumar DeyRahul Kumar Dey
Now I got caseHistory back but attachments still null.
User-added image

And my try block look like this-
List<ContentDocumentLink> AttachmentList;
        try
        {
            AttachmentList     = [SELECT ContentDocument.title, ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId =: currentcaseIds];
            
            List<ContentDistribution> distributionList = [Select Name,ContentDownloadUrl,DistributionPublicUrl from ContentDistribution where RelatedRecordId =: currentcaseIds];
            
            system.debug('AttachmentList'+AttachmentList);
            List<attachmentWrapper> attachments = new List<attachmentWrapper>();          
            
            for (ContentDocumentLink attach: AttachmentList)
            {
                for(ContentDistribution cd : distributionList){
                    attachmentWrapper attachWrap = new attachmentWrapper();
                    attachWrap.AttachmentName    = attach.ContentDocument.title;
                    attachWrap.attachUrl = cd.DistributionPublicUrl;
                    attachWrap.attachDownUrl = cd.ContentDownloadUrl;
                    attachments.add(attachWrap);
                }
                
            }
            
            reqDelRap.caseHistory  = caseHistory; 
            reqDelRap.attachments  = attachments;
        }catch(QueryException e) 
        {
            
            System.debug(e.getMessage());
        }
        return reqDelRap;

where we do wrong ...
Rahul Kumar DeyRahul Kumar Dey
I put debug after distributionList and this list contains empty
User-added image
But in AttachmentList contains list of files
Sachin HoodaSachin Hooda
Hi Rahul,
Can you please try creating a public link manually.
1. Get either of ContentDocument Id or ContentVersion Id of the file associated with your case.
2. Navigate to https://<your-instance>.my.salesforce.com/contentId where contentId is the one you got from step one.
3. Now create a public link for the file.

Now try calling the API you created with the same case Id.
If it works, 
Create a new instance of ContentDistrubution and add ContentVersionId & ContentDocumentId to it & insert it.
I hope it helps you.
___________
Regards,
Sachin
(:
This was selected as the best answer
Rahul Kumar DeyRahul Kumar Dey
Hi Sachin,

Your last solution really working fine, but unfortunately we don't go with it because of security reason. The thing is we never give external user any salesforce instance access to download files via url. So, we decide to go with 3rd party services to store files over there and we only store the url in salesforce which is generate by 3rd party.
But i'm going to give your last answer as best because, if there is no concern about security it means external user also have salesfore access. Thats properly fine...

Thanks,
Rahul
Sachin HoodaSachin Hooda
Hi Rahul,
You don't need anyone to access your org. I told to create it manually because I was confused about whether ContentDistribution gets created itself like ContentDocument when ContentVersion is added.
So, the answer is NO.
One thing I noticed is, the ContentDistribution gets created when the user clicks on generate public URL(like I told you to do this manually) or you need to create one through your apex code. So you do have an option to create it using Apex. You can do it like.
ContentDistribution cdl = new ContentDistribution();
cdl.Name = 'file title'; // title of the file or cd.title;
cdl.ContentVersionId = cd.Id; //your ContentVersion id
INSERT cdl;
ContentDistribution ecdl = [Select Id, ContentDownloadUrl,DistributionPublicUrl From ContentDistribution Where Id =cdl.Id]; // once you query ContentDistribution with the Id of you just created. You can see your Download & public download links
System.debug(ecdl);
In case you've any other doubt, please post them.

_________
Regards,
Sachin
(: