I am creating a simple project to learn Salesforce and APEX. Currently, I am taking in a file that the user uploads, parsing it, and reading it line by line to check for a particular value, and then uploading a Standard object.

The readFile() method is the method that performs all the business logic. This tiny learning project I thought of is to use the current Contact's email address as the filter in which to read a .csv file that the user uploads, which contains a list of email addresses and each's associated Gravitar URL.

My current error is the following:

Unknown method 'ContactStandardController.readFile()' Error is in expression '{!readFile}' in component in page gravitarimport:uploadgravitarurl

Here is my Visualforce page:

   <apex:page standardController="Contact" extensions="GravitarImportExtension" sidebar="true">
   <apex:outputPanel id="all">
      <apex:sectionHeader title="Upload data from CSV file"/>
      <apex:pagemessages />
      <apex:pageBlock >
             <!--  Component to allow user to upload file from local machine -->
              <apex:inputFile value="{!contentFile}" filename="{!nameFile}" /> 
                 <apex:commandButton action="{!readFile}" value="Upload File" rerender="all" id="theButton" style="width:70px;" >
                  <apex:param name="paramValue" value="{!contentFile}" assignTo="{!csvFILE}"></apex:param>
                  <apex:param name="paramValue2" value="{!currentRecord}" assignTo="{!currentRecord}"></apex:param>
              <br/> <br/> 
              <font color="blue"> <b>Note: Please use the standard template to upload a Gravitar URL. <a href="https://drive.google.com/open?id=0B6RcI99u5FjFbWlHRFREdWYyUjQ" target="_blank"> Click here </a> to download the template. </b> </font>

My Controller Extension:

public class GravitarImportExtension {
private ApexPages.StandardController controller;
public string nameFile{get;set;}
public Blob contentFile{get;set;}
String[] filelines = new String[]{};
public Blob csvFILE {get; set;}

Contact currentRecord;

public GravitarImportExtension(ApexPages.StandardController controller) {
    this.currentRecord = (Contact)controller.getRecord();
    currentRecord = [SELECT Email FROM Contact WHERE Id = :currentRecord.Id];
//public GravitarImportExtension(){}

public void readFile(Contact currentRecord, Blob contentFile) {
     try {
            // Convert the uploaded Blob file into a String in order to be parsed
            nameFile = convertBlobToString(contentFile,'ISO-8859-1');

            // Parse every row into a new line
            fileLines = nameFile.split('\n');

            // Iterate through the lines and populate the Gravitar_URL custom field with the Gravitar URL field in the CSV file
            for (Integer i=1; i<fileLines.size(); i++) {
                String[] recordContents = new String[]{};
                recordContents = fileLines[i].split(',');

                if (recordContents[0] == currentRecord.Email) {
                    currentRecord.Gravitar_URL__c = recordContents[1];
                } else {
                    ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'Contact was not found in provided data');

                    try {
                        update currentRecord;
                        ApexPages.Message successmsg = new ApexPages.Message(ApexPages.severity.CONFIRM, 'Contact\'s Gravitar URL successfully imported.');
                    } catch (Exception e) {
                        ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'An error has occured while updating the contact'+e.getMessage());

      catch(Exception e) {
            ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'An error has occured reading the CSV file'+e.getMessage());

    //return null;

public String convertBlobToString(Blob input, String inCharset){
    String hex = EncodingUtil.convertToHex(input);
    System.assertEquals(0, hex.length() & 1);
    final Integer bytesCount = hex.length() >> 1;
    String[] bytes = new String[bytesCount];
    for(Integer i = 0; i < bytesCount; ++i)
        bytes[i] =  hex.mid(i << 1, 2);
    return EncodingUtil.urlDecode('%' + String.join(bytes, '%'), inCharset);


My Test Class:

private class GravitarImportExtensionTest {

@isTest static void testingImport() {

// Create a test Contact
            Contact currentRecord = new Contact();
            currentRecord.GravitarImport__Gravitar_URL__c = 'https://s.gravatar.com/avatar/70796209a2c14dc50962c8ab05b394cb?s=80';

// Build the .csv file
            String csvHeader = 'Email, Gravitar URL\n';
            String csvFinalString = csvHeader;

            String csvRecord = currentRecord.Email+','+currentRecord.GravitarImport__Gravitar_URL__c+'\n';
            csvFinalString = csvFinalString + csvRecord;
            Blob csvBlob = Blob.valueOf(csvFinalString);

// Create an instance of the object in order to invoke the readFile() method
            GravitarImportExtension gravitarClass = new GravitarImportExtension();
            gravitarClass.readFile(currentRecord, csvBlob);

// Test that the return message is the successful confirmation message   
            List<ApexPages.Message> messages = ApexPages.getMessages();
            boolean flag = false;
            for(Apexpages.Message message : messages) {
               String messageDetail = message.getDetail();
                if (message.getDetail() != null) {
                flag = true;
            System.assertEquals(true, flag);

My code is of course very crude. Specifically, I have tried the "assignTo" param method (as shown here) and I have also seen these two attempts using a current page and scripts, but I do not know which is a best practice: Passing method parameters with apex:actionFunction

I believe the simplest test here is to ensure whether the confirm Apex message is displayed. I researched that the convertBlobToString() method will also be included in my coverage since the tested method "readFile()" calls it.

The readFile method in my extension initially worked flawlessly without any parameters (as I assume that's what the get and set methods at the top were for, correct?). However, in the process of creating my test class I have added the two parameters, so that in my test I can include a newly built Contact record and a .csv file within the test for use in the method, instead of the one read from the page, hence my parameters.

I am coming from a few weeks of learning Java, so not being able to directly pass parameters is frustrating. Let me know if you need any clarification, any help would be appreciated!


Visualforce action methods can't have parameters so to avoid the error your report in your question the signature must be:

public Blob contentFile {get;set;}
public Contact currentRecord {get; set;}

public void readFile() {
    // You can reference the contentFile and currentRecord fields in here

In your test you can set the fields before you invoke the method:

GravitarImportExtension e = new GravitarImportExtension(...);
e.contentFile = ...;
e.currentRecord = ...;
  • Keith, thank you for your response, I got my test to execute successfully! I have a few follow-up questions: 1. You can directly add "properties" to an object like that (e.contentFile, etc.)? 2. I had to make a second, no-arg constructor and then reference that in my test class. Is this because all I need is the object to add the above properties, and do not need the functionality that I have in my 1-arg constructor (that takes in a controller as an arg)? 3. In defining "contentFile" and "currentRecord" in my test class, how does the no-arg "e.readFile()" know to use these instead? – James McCreary Nov 14 '16 at 18:38
  • @JamesMcCreary I recommend you make use Trailhead to learn more. This is a site that is focussed on single specific questions not broader learning needs. But briefly 1) yes; 2) you can create a standard controller in your test and pass it in; Google that; 3) fields of classes are available to methods in most/all OO languages. – Keith C Nov 14 '16 at 18:45
  • Keith, thank you for your patience and assistance. I have gone through the Trailhead, perhaps its a matter of time for me to know this subconsciously. I appreciate the help and will definitely look into this more for my future testing needs! – James McCreary Nov 14 '16 at 18:59
  • @JamesMcCreary Perhaps some general Googling/reading about OO would complement Trailhead. Best of luck. – Keith C Nov 14 '16 at 19:28

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.