13

I am attempting to get the content of a visualforce page that is rendered as a PDF. I make some changes to the object that the visualforce page is displaying data from, update that object, make the content call and then attach the attachment to the correct object. My problem is that the two fields I am updating before I make the DML call are not coming through in the getContent call. After I attach the attachment I forward the user to the visualforce page I am trying to attach. When the user views the page through visualforce the fields render correctly, it is only when attaching the page that they do not show correctly. Here is the code where I am making the content call and returning the page reference:

public PageReference submitApplication(){

    application.application.Status__c = 'Submitted';
    application.application.Application_Submitted_Date__c = DateTime.now();

    update application.application;
    update application.currentProject;

    String organizationName = this.application.currentOrganization.Name;
    String projectID = this.application.currentProject.id;
    String currentUserId = this.application.currentUser.Id;
    String currentAccId = this.application.currentOrganization.Id;

    system.debug('Application submitted date: ' + application.application.Application_Submitted_Date__c);
    system.debug('Application Signature: ' + application.application.Applicant_Signature__c);

    PageReference applicationPage = Page.ApplicationPDF;
    Blob body = applicationPage.getContent();

    Attachment applicationpdf = new Attachment();
    applicationpdf.Body = body;
    applicationpdf.ContentType = '.pdf';
    applicationpdf.Name = organizationName + ' Application PDF';
    applicationpdf.Name += '.pdf';
    applicationpdf.ParentId = projectID;
    insert applicationpdf;

    return applicationPage;

}

The application variable is just a custom controller that stores objects that the subsequent extensions use. Every single other field shows and displays correctly. It is just the 'Status__c' and 'Application_Submitted_Date__c' that are not showing up on the attached PDF. Just to reiterate - when the user actually views the returned PageReference (the same one used to generate the Content Blob) - both of those fields display the value correctly. Also the debug log prints out the values correctly before making the getContent call. I am just wondering if there is something simple that I am missing or if anyone has come across an issue like this before. Here is how the fields are displaying on the visualforce page:

<div class="formLeft">
     <div class="formLeft-content">
         <div class="field">
             <span class="label"><apex:outputLabel value="Applicant Signature:" />
              </span> <span class="value"><apex:outputField value="{!application.Applicant_Signature__c}" /></span>
         </div>
     </div>
</div>
<div class="formRight">
    <span class="label"><apex:outputLabel value="Date Signed:" />
    </span> <span class="value"><apex:outputField value="{!application.Application_Submitted_Date__c}" />
    </span>
</div>

Thanks

EDIT**

Making the changes proposed by @Andrew Fawcette creates an error when trying to view the attachment that get attached. The visualforce page is set to rednder as PDF and I have also tried using the getContentAsPDF method and this attaches a white page. If I use this code outside of a future method it attaches correctly except for the issue described above. The controller for the PDF uses UserInfo information to start querying data and I am wondering if this could be causing the issue.

1
  • The reasoning for this still stands, as regards the proposed solution. I've now updated my answer following our chat, my original idea @future gave a false positive in my initial testing, it seems that getContent fails silently in a future context. Answer now updated with attempt 2! :) Dec 19, 2013 at 20:42

2 Answers 2

14

Cause: Transaction Scope

I've hit this before, and concluded this is due to how getContent is likely implemented internally. As per the Apex Documentation on transaction handling...

all changes are committed to the database only after all operations in the transaction finish executing

So the problem is until your database changes are committed, effectively not until after submitApplication method completes, no other code executions will see the changes to the record. I suspect the platform getContent method must run in a separate transaction context and thus cannot see the changes your code is making.

Solution: Chaining Execution Contexts

The solution is to invoke your logic that calls getContent and creates the attachment in a subsequent execution context (providing this one completes successfully). As it seems all async Apex features (Batch, @future) block getContent or fail silently.

We can create a new execution context via apex:actionFunction, which is invoked once the command button AJAX is complete. This example performs some DML (in my test case insert), then sets a controller member variable to pass some context to the second method invoked by the actionFunction.

public with sharing class CreateRecordAndAttachPDF {

    public Id parentId {get;set;}

    public PageReference createRecord()
    {
        Test__c test = new Test__c();
        test.A_Number__c = 42;
        insert test;            
        parentId = test.id;         
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, 'Please wait while the attachment is made...'));            
        return null;                
    }

    public PageReference attachPDF()
    { 
        Attachment applicationpdf = new Attachment();
        applicationpdf.Body = Page.mypdf.getContentAsPdf();
        applicationpdf.ContentType = '.pdf';
        applicationpdf.Name = 'Application PDF';
        applicationpdf.Name += '.pdf';
        applicationpdf.ParentId = parentId;
        insert applicationpdf;          
        parentId = null;            
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, 'PDF has been attached!'));   
        return null;            
    }   
}

This is the Visualforce page i used...

<apex:page controller="CreateRecordAndAttachPDF">
    <apex:form id="myform">
        <apex:pageMessages />
        <apex:commandButton 
                action="{!createRecord}"
                value="Create Record and Attach PDF" 
                rendered="{!ISNULL(ParentId)}" 
                onComplete="attachPDF();" 
                reRender="myform"/>
        <apex:actionFunction 
                name="attachPDF" 
                action="{!attachPDF}" 
                reRender="myform"/>
    </apex:form>
</apex:page>

It shows this initially...

enter image description here

Then when clicked this...

enter image description here

Then once attached this...

enter image description here

Hope this helps!

13
  • Ha, beat me to it by 30 seconds. :) Dec 19, 2013 at 18:01
  • 1
    @DanielBallinger hah sorry dude, well being in the UK and on this occasion, i'm pretty much done with SE for today, so you can worry about one less SE addicted user when writing up your answers... ;-) Dec 19, 2013 at 18:07
  • Or, alternatively, they can make sure that the pdf page uses the same controller and extensions, in which case it can share view state (another interesting glitch, I think). Or, you could probably even use a static variable, though I've never experimented with that.
    – sfdcfox
    Dec 19, 2013 at 18:15
  • Yeah i remember trying the static variable trick, the code on the VF page didn't see those either. I suspect its a brand new execution context / thread they are spinning up to service the getContent call (i've also suspected this underpins some of the current restrictions on calling it in most batch contexts btw). As regards using the same controller and extension, i've not tried that, but suspect as per this trick is documented, its all within the same execution context. This has reminded me about one other approach i had success with though, i'll update! Dec 19, 2013 at 18:19
  • 1
    Final update to my answer, new version without actionPoller, now uses actionFunction. Thanks Chris for the thought to investigate the onComplete on apex:commandButton never used that before! :) Dec 19, 2013 at 23:58
0

I got the same problem but some times we can not run asynchronously so I made it in different way.

I have loaded that page while constructor is executing but this solution is not fit for dynamic process.

if page loading is non dynamic you can go with this.

1
  • Not true, my page is dynamic and this works for me
    – Chris
    Nov 26, 2014 at 5:28

You must log in to answer this question.

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