Action Framework 2.2 returning a custom object

I am trying to create a custom play.api.mvc.Action that can be used to populate the CustomerAccount based on the request and passing the CustomerAccount to the controller.

Following the documentation for Play 2.2.x , I created an Action and an ActionBuilder , but I cannot return a CustomerAccount from the action.

My current code is:

 case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request) case class Account[A](action: Action[A]) extends Action[A] { lazy val parser = action.parser def apply(request: Request[A]): Future[SimpleResult] = { AccountService.getBySubdomain(request.host).map { account => // Do something to return the account like return a new AccountWrappedRequest? action(AccountWrappedRequest(account, request)) } getOrElse { Future.successful(NotFound(views.html.account_not_found())) } } } object AccountAction extends ActionBuilder[AccountWrappedRequest] { def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = { // Or here to pass it to the next request? block(request) // block(AccountWrappedRequest(account??, request)) } override def composeAction[A](action: Action[A]) = Account(action) } 

Note. This will not compile because the block(request) function expects an AccountWrappedRequest type, which I cannot populate. It will be compiled using direct Request

Additionally...

Ultimately, I want to combine it with an existing account authentication action to CustomerAccount can be transmitted to the authentication action, and user authentication can be provided on the basis of the customer's account. Then I would like to transfer the client and user account to the controller.

For instance:

Account(Authenticated(Action))) { request => request.account; request.user ... } Account(Authenticated(Action))) { request => request.account; request.user ... } or better, as individual objects do not require a custom request object.

+6
source share
2 answers

I'm not sure if this is the best way to do this, but I managed to find a solution that seems to work very well.

The key must have AccountWrappedRequest request, converting it to AccountWrappedRequest inside invokeBlock , before passing it to the next request. If another action in the chain expects values ​​from an earlier action in the chain, you can likewise match the request, converting it to the type needed to access the request parameters.

Updating the example from the original question:

 case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request) case class Account[A](action: Action[A]) extends Action[A] { lazy val parser = action.parser def apply(request: Request[A]): Future[SimpleResult] = { AccountService.getBySubdomain(request.host).map { account => action(AccountWrappedRequest(account, request)) } getOrElse { Future.successful(NotFound(views.html.account_not_found())) } } } object AccountAction extends ActionBuilder[AccountWrappedRequest] { def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = { request match { case req: AccountRequest[A] => block(req) case _ => Future.successful(BadRequest("400 Invalid Request")) } } override def composeAction[A](action: Action[A]) = Account(action) } 

Then, inside the apply() method of another action (the "Validation" action in my case), you can also do:

 def apply(request: Request[A]): Future[SimpleResult] = { request match { case req: AccountRequest[A] => { // Do something that requires req.account val user = User(1, "New User") action(AuthenticatedWrappedRequest(req.account, user, request)) } case _ => Future.successful(BadRequest("400 Invalid Request")) } } 

And you can bind actions together in ActionBuilder

 override def composeAction[A](action: Action[A]) = Account(Authenticated(action)) 

If AuthenticatedWrappedRequest then passed to the controller, you will have access to request.account , request.user and all the usual request parameters.

As you can see, there are several cases where the answer is unknown, which generates BadRequest . In fact, they should never be called, as far as I can tell, but they just crack there.

I would like to get some feedback on this solution, since I'm still pretty new to Scala, and I'm not sure there might be a better way to do this with the same result, but I hope that someone else use this too.

+4
source

I wrote a separate small (ish) example that does what you are looking for:

https://github.com/aellerton/play-login-example

I gave up trying to use the Security classes that exist in the actual game structure. I'm sure they are good, but I just could not understand them.

Quick Start Guide ...

In the code example, the controller is declared using the AuthenticatedRequests attribute:

 object UserSpecificController extends Controller with AuthenticatedRequests { ... } 

Forcing any page that requires authentication (or redirecting to receive it) is performed using the RequireAuthentication action:

 def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] => Ok("This content will be accessible only after logging in) } 

Output is performed by the action AbandonAuthentication :

 def signOut = AbandonAuthentication { implicit request => Ok("You're logged out.").withNewSession } 

Note that for this you need to override the methods from the AuthenticatedRequests tag, for example:

 override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = { Future.successful( Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path) ) } 

There are more; best to see the code.

NTN Andrew

+1
source

Source: https://habr.com/ru/post/957364/


All Articles