8

A visualforce page controller uses ApexPages.currentPage().getParameters().get('id'); in its constructor.

I need to call this controller from apex schedulable class. So before creating instance i set page parameter as:

ApexPages.currentPage().getParameters().put('id',this.sid); but this is not working in schedulable class but this approach works in test class (therefore i used it in schedulable class).

//Code reference in schedulable
public void execute(SchedulableContext ctx)
{
    ApexPages.currentPage().getParameters().put('id',this.sid); // Exception: Attempt reference null        
    MyController con = new MyController();
}

Is there any workaround or need to change approach? Just want to know possibilities.

5
  • Yes in test class it works because you'll be setting the current page but thats not the case in Schedule class...i guess creating a parameterized controller would help in this case Commented Sep 4, 2013 at 6:57
  • Parameterized controller is a sure working result but that would be my last option. Thanks.
    – Ashwani
    Commented Sep 4, 2013 at 7:03
  • 2
    What do you need to schedule that is coded in a page controller ? To me this would indicate you may want to consider abstracting your business logic away from your pages so that it can be approached from pages,batches, webservices, ... Commented Sep 4, 2013 at 9:12
  • @Sdry Yes, you are right. If you have some suggestion/approach for this please post as answer.
    – Ashwani
    Commented Sep 4, 2013 at 10:24
  • 1
    I've just +1 @Sdry and referenced in answer I just posted some thoughts on Separation of Concerns I've been publishing recently, hopefully some food for thought... :-) Commented Sep 12, 2013 at 11:09

3 Answers 3

6

Fairly certain you'll need to change your approach. Fortunately this can be a quick refactor. Just extract your constructor into a method that takes the id param, then create a second constructor that also takes and id param, and wire it all up. You'll then be able to call it from your batch class without having to deal with re-testing the visualforce controller you tweaked.

Initial Constructor

public class MyController {
    private Id myId;

    public MyController() {
        myId = ApexPages.getCurrentPage().getParameters().get('id');
        ... stuff you don't want to touch ...
    }
}

Refactored

public MyController {
    private Id myId;

    public MyController() {
        init(ApexPages.getCurrentPage().getParameters());
    }

    public MyController(Id fromTestId) {
        init(fromTestId);
    }

    private void init(Id inputId) {
        myId = inputId;
        ... stuff you don't want to touch ...
    }
}
4
  • (+1) @Ralph Yes, this is an expected working approach. But it has a little issue that if any of the referencing class has ApexPages.getCurrentPage().getParameters(), will throw exception. I will sure implement this if there will not any workaround. Right now I am trying to prevent code refactoring.
    – Ashwani
    Commented Sep 4, 2013 at 10:31
  • 3
    Refactoring is the correct approach. Scheduled classes don't run in a Visualforce context, so there's no current page to reference. As a general guideline, I usually recommend that utility classes follow the following rules: Don't assume Visualforce context, don't perform queries, and don't perform DML operations. Of course, there are exceptions to each rule, but you'll find that methods that conform to these three rules will be available in more places than just one context, such as a trigger or a page.
    – sfdcfox
    Commented Sep 4, 2013 at 11:24
  • @responsive there isn't a workaround. In general it's based to design your core logic without depending on those features and then do context specific actions, i.e. trigger context variable, ApexPages actions, in a controller that then references your core logic. Commented Sep 5, 2013 at 12:37
  • I just want to get some advices and idea about this. Actually class was a part of a big tested managed package and I need to add some functionality to it, therefore refactoring code became the matter of worries. Thanks @Ralph
    – Ashwani
    Commented Sep 5, 2013 at 13:34
6

Just gave +1 for @Sdry's comment on this question.

While these answers technically resolve this particular problem, if your code base is growing in complexity you may want to start to think more about a better Separation of Concerns and layering of your logic to avoid problems like this when reusing code between different contexts.

enter image description here

Following a Service layer (part of the above series) approach encapsulates logic in way that is agnostic of the caller and thus does not make assumptions such as having ApexPages in scope.

Obviously as is the nature of software development we don't always get this option to refactor existing code bases, hence the immediate need I'm glad has been resolved. Sometimes we can do things incrementally just by starting a MyXYZService class and agreeing a few rules on what code can and cannot do it it and encouraging through incremental development to converge into it.

Hopefully some food for thought... :-)

0

How is the scheduled class called? Does your VF page schedule the class? For example (and correct me if I am wrong), is it like you have a VF page with a standard controller on, say, Account. And then you want the VF page to schedule the class to do something neat pertaining to the specific Account we're viewing on the VF page. If this is the case you could try the following:

You'd need to collect the id of the record you're concerned with and pass that to the scheduled class. Then in the scheduled class, you need to create a new instance of your VF controller class, but to do this, you'll need to pass it an ApexPages.StandardController for the id that gave the scheduled class. "Cool," you may say, "but what about the error in calling ApexPages.currentPage().getParamters()?" To overcome this obstacle (assuming you are using a standard controller), just use the getId method of the ApexPages.StandardController class.

Now, if you're not using a standard controller, (and even if you are) you can always create multiple initialization methods for your VF controller.

For example:

public class myVFController{

    private id acctId;

    // to be used as a controller for a VF page 
    public myVFController(){
        acctId = ApexPages.CurrentPage().getParameters().get('id');
    }

    // so myVFController can be used as an extension for StandardController
    public myVFController(ApexPages.StandardController con){ 
        acctID = con.getId();
    }

    // to be used in your scheduled code
    public myVFController(id acctId){
        this.acctId = acctId;
    }

}  

And then in your scheduled class, make sure you initialize your VF controller by passing calling:

myVFController con = new myVFController(myAcctId);

You must log in to answer this question.

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