6

I'm attempting to get current pageReference for a VisualForce page (renderAs=pdf) that is being executed in @future context, but at the moment it is just returning a null. So I assume the problem is that the page isn't being rendered, but how would I force that?

The Exntension Controller:

public without sharing class Invoice_Quote_Controller {
    public Invoice_Quote_Controller( ApexPages.Standardcontroller controller) {
        // Constructor code
    } 

    public PageReference getPageReference(Id invoice_id){
        return Apexpages.currentPage();

    }
}

This is how I'm trying to get the pagereference:

@future
public static void sendEmail(Id invoice_id){

    Invoice__c invoice = //SOQL Removed

    Apexpages.Standardcontroller controller_inv = new Apexpages.Standardcontroller(invoice);
    Invoice_Quote_Controller page_inv = new Invoice_Quote_Controller(controller_inv);

    system.debug(page_inv.getPageReference());
}
8

A @future method has no prior execution context-- the source of the asynchronous call has already ceased to exist by the time it runs. This is true if the function is called from a Visualforce page, trigger, scheduled class, batch class, or execute anonymous block. This is why you can't pass in complex objects, such as SObject lists, because they do not survive outside of their current transaction.

ApexPages.currentPage() only exists in a Visualforce initiated transaction, so it's not available in the @future method. You can't explicitly force this to happen, either, because you can't serialize complex objects. If you would like, you should be able to pass in a Map, as long as the map contains primitives (such as the result from ApexPages.currentPage().getParameters(), which is a Map<String, String>).

Furthermore, you cannot call getContent or getContentAsPDF from a page reference, which means that you can't get the output from the page that initiated the call afterwards, which I presume is what you are trying to do. Instead, the usual means of working with this limitation is mind is to wrap one page inside another; the outer page renders the inner page, which also includes a getContent call in order to save the contents of the page if they like it.

Here's a modified example from our project, which displays a variety of PDF files and allows users to save them to the parent record:

Page:

<apex:page standardController="Some_Object__c" extensions="pdfExtension">
    <apex:form>
        <apex:pageBlock>
        <apex:commandButtons>
            <apex:commandButton value="Save & Return" action="{!savePDF}"/>
            <apex:commandButton value="Cancel" action="{!cancel}"/>
        </apex:commandButtons>
        <apex:iframe src="{!pdfurl}" style="width: 100%; height: 400px"></apex:iframe>
        </apex:pageBlock>
    </apex:form>
</apex:page>

Controller:

public with sharing class pdfExtension {
    ApexPages.standardController controller;

    public string pdfurl { get; set; }

    public pdfExtension(ApexPages.StandardController controller) {
        this.controller = controller;
        // Figure out which URL we are trying to use, assign to pdfurl.
    }

    public ApexPages.PageReference savePdf() {
        ApexPages.PageReference pdf = new ApexPages.PageReference(pdfurl);
        Attachment att = new Attachment(ParentId=controller.getId(), name='Some File.pdf', Body=pdf.getContent(), ContentType='application/pdf');
        insert att;  // Do error checking here
        return controller.cancel();
    }
}

Of course, if the point is to save the file and render it on the screen at the same time, you can recursively call the class-- it's legal to provide an "action" parameter on the page, and it will execute before outputting the PDF. A static variable to avoid infinite recursion should be applied. It would look like this:

Page:

<apex:page controller="pdfController" action="{!saveOnLoad}" renderAs="pdf">
    <!-- page content here, irrelevant for this example -->
    <!-- page is called "myPdfPage" -->
</apex:page>

Controller:

public with sharing pdfController {
    static boolean isRecursive = false;
    public void saveOnLoad() {
        if(!isRecursive) {
            isRecursive = true;
            Attachment record = new Attachment( // Save to some ParentId
                Body = Page.myPdfPage.getContent(),
                ... ); // You know what to do here
        }
    }
}

The page will have to double-up on the amount of work it does (it's rendering twice), so keep that in mind in terms of governor limits (e.g. queries). You can use a caching algorithm using static variables to minimize the queries required (hint: static variables can be shared between the outer invocation and the inner invocation).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.