inputFile body not Transmitted to VF Controller

Here was the use case:

  1. Display step 1 of a VF page with a commandLink to jump to step 2
  2. Step 2 was initially rendered invisible
  3. Step2 included an apex:inputFile component

When Step 2 was rendered, the inputFile component appeared.

But, if you select a file and click a button to upload the file to the controller, the controller does not receive the file body but does receive the filename and content type.

Huh?

The issue appears to be that initially unrendered apex:inputFile components don’t bind as expected to a controller variable declared as transient (as it typically must be to avoid Visualforce viewstate size issues). The upload action method occurs, all other non-transient attributes of inputFile do transmit but the file body does not.

Here’s proof

The controller

public with sharing class Foo {
	// Class to investigate rerender issue with inputFile
	public Boolean				hasStep1Completed		{get {return this.hasStep1Completed == null ? false : this.hasStep1Completed;} set;}						// vis control
	
	public transient Blob  		fileBody				{get; set;}		// must be transient as view state can't handle > 128KB				
	public String       		fileDescription			{get; set;}
	public String  				fileName				{get; set;}		
  	public Integer       		fileSize				{get; set;}
	public String       		fileType				{get; set;}
	
	public Boolean 				isFileBodyNullOnXmit	{get; set;}		// VF feedback for example
	
	public Foo() {}
	
	public PageReference upload() {
		this.isFileBodyNullOnXmit = this.fileBody == null ? true : false;
		return null;
	}
}

This VF page does not work

<apex:page controller="Foo"  tabStyle="Account">
 <apex:form id="form">
  <apex:outputPanel id="step1Op">
   <apex:pageBlock title="Step 1" id="step1Pb"  rendered="{!NOT(hasStep1Completed)}">
	<apex:actionregion >
	 <apex:commandLink value="Go to step2"  reRender="step1Op,step2Op">
		<apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/>
	 </apex:commandLink>
	</apex:actionregion>
   </apex:pageBlock>
  </apex:outputPanel>

<!--  This doesn't work, initially unrendered inputFile won't transmit file body once rendered -->
  <apex:outputPanel id="step2Op" >
   <apex:pageBlock title="Step2" id="step2Pb" rendered="{!hasStep1Completed}" >
	<apex:pageBlockButtons location="top">
         <apex:commandButton value="Upload" action="{!upload}"/>
	</apex:pageBlockButtons>
	<apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/>
   </apex:pageBlock>
 </apex:outputPanel>		
  
 <apex:outputPanel id="resultOp" >
   <apex:pageBlock rendered="{!hasStep1Completed}">
	<apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/>
   </apex:pageBlock>
 </apex:outputpanel> 
		
 </apex:form>
</apex:page>

The apex:inputFile is not initially rendered as Step1 hasn’t completed
FileXmitStep1

Now, by clicking the commandLink, I render it and then choose a file and click Upload
FileXmitStep2

The results outputPanel shows that no fileBody was transmitted!
FileXmitResultnullBodyisTrue

So, how to work around this?

The answer was provided in Salesforce StackExchange. Instead of using VF rendered= attributes, the inputFile must be rendered by made invisible by CSS. So, the revised Visualforce page (no changes to the controller)

<apex:page controller="Foo"  tabStyle="Account">
 <apex:form id="form">
  <apex:outputPanel id="step1Op">
   <apex:pageBlock title="Step 1" id="step1Pb"  rendered="{!NOT(hasStep1Completed)}">
	<apex:actionregion >
	 <apex:commandLink value="Go to step2"  reRender="step1Op,step2Op">
		<apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/>
	 </apex:commandLink>
	</apex:actionregion>
   </apex:pageBlock>
  </apex:outputPanel>

<!--  This does work, initially rendered by css invisible inputFile will transmit file body once made visible --> 
  <apex:outputPanel id="step2Op" style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};">
   <apex:pageBlock title="Step2" id="step2Pb" >
    <apex:pageBlockButtons location="top">
	<apex:commandButton value="Upload" action="{!upload}"/>
    </apex:pageBlockButtons>
    <apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/>
  </apex:pageBlock>
 </apex:outputPanel>		
  
 <apex:outputPanel id="resultOp" >
   <apex:pageBlock rendered="{!hasStep1Completed}">
	<apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/>
   </apex:pageBlock>
 </apex:outputpanel> 
		
 </apex:form>
</apex:page>

Only the second outputPanel was changed. Note the use of style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};" to trick Visualforce into thinking the inputFile is rendered but using display: none to make invisible.

The sequence of steps now is:
FileXmitStep1
FileXmitStep2

The results outputPanel shows that fileBody was transmitted! Problem solved.
FileXmitResultnullBodyisFalse

Leave a Reply

Your email address will not be published. Required fields are marked *