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.
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`
}
}
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());
}
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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With