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

假如想象 提交于 2019-12-21 13:07:56

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/10738182/when-using-a-generic-controller-how-to-return-a-view-inherent-to-a-specific-con

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!