3

I'm trying to find a way how to upload content document and link it to Salesforce object (say, an opportunity) programmatically (using SOAP API). That's what I tried:

  1. Insert new ContentVersion with base64 encoded VersionData, Title and PathOnClient
  2. Find ContentDocument related to ContentVersion just created "Select ContentDocumentId FROM ContentVersion WHERE Id = taken from step 1"
  3. Insert new ContentDocumentLink with ContentDocumentId = "taken from step 2", Share Type = "I" (inherited from Opportunity), LinkedEntityId = "my opportunity id"

All the operations completed successfully. I can see the Opportunity with linked file in Lightning Experience, however, can't see the file in SF Classic and I can't see it in my private library either. But I can navigate to the Content using its Id (so it's definitely created in Salesforce).

  • Pic1 shows (LEX) Opportunity and linked files.
  • Pic2 shows (SF Classic) same opportunity but no files are visible.

Did I miss any important step?

Content document

Opportunity

enter image description here

The source code (C#, Web Reference using Partner WSDL. Login, error handling is omitted):

// create content version
XmlDocument docCreateContentVersion = new XmlDocument();
XmlElement[] xmlCreateContentVersion = new XmlElement[3];
xmlCreateContentVersion[0] = docCreateContentVersion.CreateElement("VersionData");
xmlCreateContentVersion[0].InnerText = System.Convert.ToBase64String(Encoding.ASCII.GetBytes("Hello, World")); 
xmlCreateContentVersion[1] = docCreateContentVersion.CreateElement("Title");
xmlCreateContentVersion[1].InnerText = "My Test Document";
xmlCreateContentVersion[2] = docCreateContentVersion.CreateElement("PathOnClient");
xmlCreateContentVersion[2].InnerText = "My Test Document";
sObject createContentVersion = new sObject();
createContentVersion.type = "ContentVersion";
createContentVersion.Any = xmlCreateContentVersion;
SaveResult[] createContentVersionResults = binding.create(new sObject[] { createContentVersion });

// obtain content documents
QueryResult contentDocuments = binding.query("SELECT ContentDocumentId, PublishStatus, OwnerId FROM ContentVersion WHERE Id = '" + createContentVersionResults[0].id + "'");
String contentDocumentId = contentDocuments.records[0].Any[0].FirstChild.Value;
Console.WriteLine("ContentDocumentId="+contentDocumentId);

// create content document link
XmlDocument docCreateContentDocumentLink = new XmlDocument();
XmlElement[] xmlCreateContentDocumentLink = new XmlElement[3];
xmlCreateContentDocumentLink[0] = docCreateContentDocumentLink.CreateElement("ContentDocumentId");
xmlCreateContentDocumentLink[0].InnerText = contentDocumentId;
xmlCreateContentDocumentLink[1] = docCreateContentDocumentLink.CreateElement("LinkedEntityId");
xmlCreateContentDocumentLink[1].InnerText = "006j000000SYHGu";
xmlCreateContentDocumentLink[2] = docCreateContentDocumentLink.CreateElement("ShareType");
xmlCreateContentDocumentLink[2].InnerText = "I";
sObject createContentDocumentLink = new sObject();
createContentDocumentLink.type = "ContentDocumentLink";
createContentDocumentLink.Any = xmlCreateContentDocumentLink;
SaveResult[] createContentDocumentLinkResults = binding.create(new sObject[] { createContentDocumentLink });
Console.WriteLine("ContentDocumentLinkId=" + createContentDocumentLinkResults[0].id);
8
  • Do you have an Contact that has been linked via the UI you could compare it with? Commented Feb 27, 2017 at 22:29
  • Looking at ContentDocumentLink I see there is also a Visibility field. Maybe set that to AllUsers or SharedUsers? Commented Feb 27, 2017 at 22:34
  • Try setting Origin = 'H' for ContentVersion
    – o-lexi
    Commented Feb 28, 2017 at 1:48
  • @minige do you mind sharing your code?
    – Mahmood
    Commented Mar 6, 2017 at 19:54
  • @Mahmood, the source id attached
    – Mike Raven
    Commented Mar 6, 2017 at 22:29

3 Answers 3

2

I used the Salesforce UI to add a File to an Opportunity. Then checked the resulting ContentDocumentLink records with SQOL.

Select Id,LinkedEntityId,ContentDocumentId,IsDeleted,ShareType,Visibility from ContentDocumentLink where ContentDocumentId = '0694000000548HHAAY'

ContentDocumentLink

Note how there are two records, one to the Opportunity and one to my user record (this will be why you can't see it in your private library). The one to the Opportunity has the ShareType V (Viewer permission) and Visibility AllUsers

2
  • 1
    I tried to change Visibility field and that didn't help. Also another ContentDocumentLink (linked to a User object) is created automatically with ContentDocument, so we don't need to create it.
    – Mike Raven
    Commented Feb 28, 2017 at 2:41
  • I'll try again when I get a chance to see what happens if I create the records via the API. Commented Feb 28, 2017 at 3:44
2

The Visibility field has affect if Communities are enabled. A visibility of AllUsers means both 'internal' uses and community users can see the file if they have access to the record, whereas a visibility of InternalOnly means only internal users can see it. Shared user visibility isn't applicable to record shares. It applies to shares to a user if User Sharing is set to private in an org.

Your steps look correct. How did you verify "... Content element is not linked to the opportunity and I can't see it in my private library?"

After step 1, the file would appear in the 'private library' filter because it is private. After step 3, it should no longer appear in the 'private library' filter if it is shared with the opportunity, because it is no longer private. If inserting the CDL worked, and it isn't showing in the 'private library' filter, it seems like the file is shared with the opportunity.

I would suggest querying the ContentDocumentLink table as Daniel did, and if you see 2 CDLs, one to the owner and one to the opportunity, it looks correct to me.

Another test is to go to the File detail page (going to salesforce.com/069...) and opening up the 'sharing settings' dialog to see if it shows it Shared with the opportunity that way.

Please report back the results of the CDL query and the 'sharing settings' dialog.

Also, you can add both the "Notes and Attachments" and "Files" related lists to the opportunity and the file should appear in both if it is linked.

I would suggest looking at the sharing settings dialog and the related lists in the Classic UI (although it should work the same in Lightning).

If you see an inconsistency in one of these places, please let me know, as there may be a bug.

2
  • Thank for feedback, Neil. I updated my post with pictures (Content Document, Opportunity in Classic, Opportunity in Lightning). Answering to your question: yes, I can navigate to Content Document (and it's marked as linked to my Opportunity) and I see it in Lightning but don't see it in Classic (both pictures are attached)
    – Mike Raven
    Commented Mar 6, 2017 at 22:53
  • @minige you need to check this answer, and I don't see files related list on your SF Classic picture screenshot. Do you have it on your page layout?
    – Mahmood
    Commented Mar 7, 2017 at 7:25
0

Using Enterprise WSDL / SOAP to Attach a File / ContentDocument to an object I needed to Create a ContentVersion, which then Salesforce creates the ContentDocument then create a ContentDocumentLink to attach it to a object.

 Byte[] bytes = File.ReadAllBytes(filePath);
            
 ContentVersion cv = new ContentVersion();
 cv.Title = Path.GetFileName(filePath);
 cv.PathOnClient = filePath;
 cv.VersionData = bytes;
 cv.Origin = "H";

SaveResult[] createResults = _sfClient.create(new sObject[] { cv });

 if (createResults.Count() > 0 && createResults[0].success)
                {
                    cv.Id = createResults[0].id;

                    QueryResult queryResult = _sfClient.query(string.Format("select Id, ContentDocumentId from ContentVersion where Id = '{0}'", cv.Id));

                    if (queryResult.records == null) return null;

                    sObject[] records = queryResult.records;

                    //we can loop through the returned records
                    if (records.Length > 0)
                    {
                        List<ContentVersion> cvs = new List<ContentVersion>(queryResult.records.Cast<ContentVersion>());
                        ContentDocumentLink contentLink = new ContentDocumentLink();
                        contentLink.LinkedEntityId = sfCase.Id;
                        contentLink.ContentDocumentId = cvs.First().ContentDocumentId;
                        contentLink.ShareType = "V"; 
                        contentLink.IsDeleted = false;
                

                        SaveResult[] createResults1 = _sfClient.create(new sObject[] { contentLink });

                        if (createResults1.Count() > 0 && createResults1[0].success)
                        {

                            return createResults1[0].id;
                        }
                        else if (createResults1.Count() > 0)
                        {
                            return "Error. Case Content Document Failed. Case Number: [" + Helpers.Trim(sfCase.Case_Number__c) + "] had an error inserting. The error reported was: " + createResults1[0].errors[0].message;
                        }
                    }

                }
                else if (createResults.Count() > 0)
                {
                    return "Error. Case Content Version Failed. Case Number: [" + Helpers.Trim(sfCase.Case_Number__c) + "] had an error inserting. The error reported was: " + createResults[0].errors[0].message;
                }
            }
            catch (Exception ex)
            {
                return string.Format("Upsert {0}", ex.Message);
        }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .