Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using a generic controller, how to return a view inherent to a specific controller?

As a result of this answer: https://stackoverflow.com/a/10708026/694597, I am wondering how to return a view inherent to a specific controller when using a generic controller.

like image 644
Leonard Punt Avatar asked May 24 '12 13:05

Leonard Punt


1 Answers

When you render a view in a controller action, you just invoke a plain function which has been generated by the template engine:

public Application extends Controller {
  public static Result index() {
    return ok(views.html.index.render(42));
  }
}

Here, render is a method of the object index which has type Template1<Integer, Html>.

Now the question is: how to write a generic controller able to invoke a view specific to another controller? Or simply: how to abstract over views?

I see two solutions: inversion of control and reflection.

Let’s see how to implement both on a simple use case. Say you have the following generic Shower<T> class able to compute an HTTP response containing an HTML representation of any value of type T:

public class Shower<T> {
  public Result show(T value) {
    // TODO return an HTML representation of `value`
  }
}

Inversion of control

To implement Shower<T> using inversion of control we just need to inject the Template1<T, Html> value used to perform the rendering:

public class Shower<T> {

  public final Template1<T, Html> template;

  public Shower(Template1<T, Html> template) {
    this.template = template;
  }

  public Result show(T value) {
    return ok(template.render(value));
  }

}

To use it in a controller, create a static instance of Shower<T> and inject it the template to use:

public class Application extends Controller {
  public static Shower<Foo> foo = new Shower<Foo>(views.html.Foo.show.ref());
}

Reflection

You may find it too boilerplate to have to inject explicitly the template to use for each instance of Shower<T>, so you may be tempted to retrieve it by reflection, based on a naming convention, e.g. to show a value of type Foo, just look for an object named show in the package views.html.Foo:

public class Shower<T> {

  private final Class<T> clazz;

  public Shower(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Result show(T value) throws Exception {
    Class<?> object = Play.application().classLoader().loadClass("views.html." + clazz.getSimpleName() + ".show$");
    Template1<T, Html> template = (Template1<T, Html>)object.getField("MODULE$").get(null);
    return ok(template.render(value));
  }
}

(that’s the way to access Scala objects using reflection)

You can use it as follows in a controller:

public class Application extends Controller {
  public static Shower<Foo> foo = new Shower<Foo>(Foo.class);
}

Pros and cons

The reflection-based solution requires less boilerplate on the call site, but the fact it relies on a naming convention makes it more fragile. Furthermore, this solution will only fail at runtime when it’ll fail, while the first solution will show you your missing templates at compile time. Last but not least, the reflection based solution may add some performance overhead due to the reflection.

like image 187
Julien Richard-Foy Avatar answered Oct 04 '22 23:10

Julien Richard-Foy