CodeLive: Creating, Finding and Publishing Files

For the past two weeks, CodeLive has been focused on using Salesforce Files. Whether it’s generating invoice pdfs, or manipulating images, knowing how to use files is an important skill for Salesforce Developers. If you missed the live sessions, don’t fret; you can view them on demand here: Part 1, Part 2.

Understanding the object model

Salesforce Files has a number of objects backing its features. First and foremost, is the ContentVersion object. This is the object that actually stores the contents of the file. ContentVersion records belong to a single ContentDocument record, however, a ContentDocument record has 1..N ContentVersion records associated with it. This allows you to upload, or create a number of file versions. ContentDocumentLink records associate ContentVersion records with other sObject records. For instance, if you want a newly created invoice pdf to appear in the Files related list of an Opportunity, you need to create a ContentDocumentLink record specifying the LinkedEntityId (OpportunityId) and the ContentVersionId (the version of the file you want associated). Finally, creating a ContentDistribution object for a ContentVersion record allows the file to be downloaded, or viewable in a browser by third parties. Below is a simplified UML diagram for these objects:

 

Creating and linking Files

When it comes to creating a file with Apex, you might assume you’d need to create a ContentDocument object. But inserting a ContentDocument fails with a “DML Operation Insert not allowed on ContentDocument.” Instead, creating a document starts with defining a ContentVersion record. Creating a ContentVersion record creates a ContentDocument record if no ContentDocumentId is specified. While that’s a bit counter intuitive, it does allow for having multiple versions of the same file, based on its ContentDocumentId.

ContentDocumentLink records create that link between a File, and another sObjects’ records. That sObject record can be an Account, or Contact or … any object with Files enabled. Just creating a ContentVersion record isn’t enough. Without a corresponding ContentDocumentLink record, the file will exist, but it will never show up in the UI. Thankfully there are a couple of ways of creating those associations. First, is to create a ContentDocumentLink record. Creating a document link requires specifying the ContentVersionId, as well as a LinkedEntityId. Of the two, LinkedEntityId is the one to pay attention to. The Id you specify here, is the record where the file will show up in the related list. The second way of creating that association is my favorite. When you create a ContentVersion record you can specify the FirstPublishLocationId field. If you populate that field with a valid recordId, the system will automatically create a ContentDocumentLink record for you. Let us take a look at a code snippet that handles the boilerplate code necessary for creating and linking a File.

public static Database.SaveResult createFileAttachedToRecord(
        Blob fileContents,
        Id attachedTo,
        String fileName
    ) {
        ContentVersion fileToUpload = new ContentVersion();
        fileToUpload.ContentLocation = 'S'; // Salesforce. The other option is: 'E'
        fileToUpload.pathOnClient = fileName;
        fileToUpload.title = fileName;
        fileToUpload.versionData = fileContents;
        fileToUpload.firstPublishLocationId = attachedTo;
        Database.SaveResult saveResult;
        try {
            saveResult = Database.insert(fileToUpload);
        } catch (DmlException DMLE) {
            system.debug(
                'Failed to insert fileToUpload, error is: ' + dmle.getMessage()
            );
        }
        return saveResult;
    }

A couple of quick notes about this code, that I discovered while creating it. The PathOnClient field is critical. The system determines the FileType of the document from the PathOnClient field, which is critical for searching! However, the FileType specified is not a direct match to the files extension. During testing I create a file with a .docx extension. However, if I searched for a file who’s ContentDocument.FileType was .docx, I got zero results. Turns out, the associated FileType is: is ‘WORD_X’. It’s quirky, but the two things to remember are:

  1. The ContentVersion’s PathOnClient field determines what FileType Salesforce assigns.
  2. The FileType is not a 1:1 to the file’s extension.

Publishing Files

One of the very handy aspects to Files is the ability to share that file with unauthenticated third parties. Meaning you can share a link to the file with that opportunities contacts, without needing to let them login to your org to view it. Similar to linking a file to an associated record, publishing the file is as simple as creating a new record. This is where the ContentDistribution object comes into play. When you create a ContentDistribution record, you have several options related to formats and permissions. Look at this method from the Files recipe class that we worked on:

public static Database.SaveResult publishContent(ContentDocumentLink cdl) {
    ContentDistribution dist = new ContentDistribution();

    dist.name = 'new distributrion of content';
    dist.PreferencesAllowOriginalDownload = true;
    dist.PreferencesAllowPDFDownload = true;
    dist.PreferencesAllowViewInBrowser = true;
    dist.RelatedRecordId = cdl.LinkedEntityId;
    dist.ContentVersionId = cdl.ContentDocument.LatestPublishedVersionId;

    if (CanTheUser.create(new ContentDistribution())) {
        try {
            return Database.insert(dist);
        } catch (DmlException dmle) {
            system.debug(dmle.getMessage());
            throw new FilesRecipesException(dmle.getMessage());
        }
    } else {
        throw new FilesRecipesException(
            'User has no privleges to create content distribution records');
    }
}

Note the fields starting with Preferences*. These are your various format and download options. The key field, however, is the ContentVersionId. Note that it’s a ContentVersion, not a ContentDocument. You can, if you’d like create multiple ContentDistribution records with different permissions associated with the same ContentVersion.

What are you going to do with Files?

Salesforce Files is a super flexible solution for storing and sharing everything from images to documents. The data model may be a bit unintuitive at first, but when you understand how the four objects work together the power and flexibility really shines. The question remains, however, now that you know how Files work, how will you use them? What about your company’s business processes can you automate by creating, linking and publishing files?

About the author