Controllers are the “C” in the MVC paradigm. Controllers will do actual stuff. They get requests or part of requests, retrieve stuff from databases and cause Ninja to return certain results.
A basic controller looks like:
package controllers; @Singleton public class AppController { public Result index() { return Results.html(); } }
The important conventions are that a controller method must return a Result. A result is in short just an object that holds response codes, content to render, headers and cookies that should be returned to the visitors of your website.
Results with the “s” at the end is a convenience class that allows you to generate a preconfigured Result. For instance Results.html() will tell Ninja to render an HTML page. But you can also render JSON and other results.
By convention controllers live in the package “controllers”.
A controller usually not only renders stuff, but also takes some inputs and does something with them.
You can get parts of the URL inside your controller via three annotations: @Param, @Params and @PathParam. Via @PathParam you can get variable parts of a route (described in more details in the routing section).
@Param allows you to get single-value Query or Form parameters. @Params allows you to get multi-value Query parameters.
Let’s say and the user visits the following URL…
/user/12345/my@email.com/userDashboard?debug=false&filters=new&filters=urgent
… and we got a route definition like that:
router.GET().route("/user/{id}/{email}/userDashboard").with(AppController::userDashboard);
We can then get all variable parts of this URL via the following method controller definition:
package controllers; @Singleton public class AppController { public Result userDashboard( @PathParam("id") String id, @PathParam("email") String email, @Param("debug") Optional<String> debug, @Param("isAdmin") Boolean isAdmin, @Params("filters") String [] filters, @Header("user-agent") String userAgent) { //do something with the parameters... } }
Have a look at that method:
package controllers; @Singleton public class ApplicationController { public Result index( @PathParam("id") String id, @PathParam("email") String email, @Param("debug") Optional<String> debug, @Param("isAdmin") Boolean isAdmin, Context context, MyObject myObject) { //do something with the parameters... } }
Ninja can not only inject @PathParam and @Param objects. But also the Context. The context is a request scoped object that holds all information of the current request - parameters, headers and so on…
Even better: Ninja will parse an arbitrary object given in the method. In the above case MyObject will be automatically parsed by Ninja. The way it will parsed (JSON, XML, POST form) will be determined via the “Content-Type” request header.
If you are using the POST form way, this object can contains any parameter type listed in the Argument extractors chapter (or custom ones, as described in the same chapter). Custom objects contained in this arbitrary object can also be parsed if they have a constructor without arguments.
public class Address { String street; } public class User { Address address; }
Assuming your controller method want to receive a User object, your HTML form can send a field value for the key address.street.
If your custom object has a date/timestamp field, first make sure that you use the java.util.Date type and when you pass some date to the controller pass in the format accepted by Joda Time library, or implement your own ParamParser for a java.util.Date.
In the example above we got two parameters: “debug” and “isAdmin”. If both parameters are not supplied then debug will be Optional.empty() and isAdmin will be null.
But null has a lot of problems and Optional is the clean solution to say “The value may be there - or not”. Therefore Ninja will deprecate the usage of null in controller methods from version 7.0.0 onwards.
Ninja makes the transition smooth by providing a configuration variable that allows to control what behavior you want to use. By default the value will be false.
ninja.strict_argument_extractors=false # accepts null
The new way eliminates the need to check for nulls.
Activate the mode by setting a configuration variable in application.conf:
ninja.strict_argument_extractors=true # redirects to Bad Request page if parameter is null
Then
Make sure you upgrade asap to be compatible with future versions of Ninja.
1) Activate the mode by setting a configuration variable in application.conf:
ninja.strict_argument_extractors=true # redirects to Bad Request page if parameter is null
2) Then investigate all your controller methods.
If you do null checks on parameters that get injected into your controller then change them to Optional. That way you get rid of all null checks.
Things like @PathParam, Context and Session will never be Optional, because Ninja is always able to provide them. But things like @Param or custom argument extractors may well be optional.
In the last section we have already seen that parsing of objects works via content negotiation.
Rendering of objects works the same way and evaluates the “Accept” request header for that.
Therefore a controller method like…
public void Result getUser() { User user = userDao.getUser(); return Results.ok().render(user); }
…will render XML when the “Accept” request header is “application/xml” or JSON when the “Accept” header is “application/json”.
While content negotiation is pretty cool it sometimes breaks down in reality.
Especially if clients send strange headers or if you want to be sure that a certain controller method always renders a return format.
Then you can simply call use return Results.json().render(user) or return Results.xml().render(user) to enforce the rendering of a certain return format.
Rendering by convention has been already explained in the getting started part. But in short consider the following class:
package controllers; public class ApplicationController { public Result index() { return Results.html(); } }
This renders by convention a view in directory views/ApplicationController/index.ftl.html.
But you can specify your own HTML view, too:
package controllers; public class ApplicationController { public Result index() { return Results.html().template("views/AnotherController/anotherview.ftl.html"); } }
In conclusion you can use convention over configuration in most cases - but you can also specify a view to render explicitly.