[英]Play Framework 2.2 action composition returning a custom object
I am trying to create a custom play.api.mvc.Action
which can be used to populate a CustomerAccount
based on the request and pass the CustomerAccount
into the controller. 我正在尝试创建一个自定义的play.api.mvc.Action
,它可用于根据请求填充CustomerAccount
并将CustomerAccount
传递给控制器。
Following the documentation for Play 2.2.x I've created an Action
and ActionBuilder
but I cannot seem to return the CustomerAccount
from within the action. 在Play 2.2.x的文档之后,我创建了一个Action
和ActionBuilder
但我似乎无法从操作中返回CustomerAccount
。
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 is expecting a type of AccountWrappedRequest
which I cannot populate. 注意:这将无法编译,因为block(request)
函数需要一种我无法填充的AccountWrappedRequest
类型。 It will compile when using a straight Request
它将在使用直接Request
时进行编译
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount
can be passed into the Authentication action and user authentication can be provided based on that customer's account. 最终,我希望能够将此帐户操作与身份验证操作相结合,以便可以将CustomerAccount
传递到身份验证操作,并且可以根据该客户的帐户提供用户身份验证。 I would then want to pass the customer account and user into the controller. 然后我想将客户帐户和用户传递给控制器。
For example: 例如:
Account(Authenticated(Action))) { request => request.account; request.user ... }
Account(Authenticated(Action))) { request => request.account; request.user ... }
or better yet as individual objects not requiring a custom request object. Account(Authenticated(Action))) { request => request.account; request.user ... }
或更好的作为不需要自定义请求对象的单个对象。
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well. 我不确定这是否是最好的方法,但我设法提出了一个似乎运作良好的解决方案。
The key was to match on the request converting it into an AccountWrappedRequest
inside invokeBlock
before passing it on to the next request. 关键是匹配请求将其转换为AccountWrappedRequest
内的invokeBlock
然后再将其传递给下一个请求。 If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters. 如果链中的另一个Action期望来自链中较早操作的值,则可以类似地将请求转换为您需要访问请求参数的类型。
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 Authenticated action in my case) you can similarly do: 然后在另一个Action(在我的情况下为Authenticated操作apply()
的apply()
方法内部,您可以类似地执行:
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 chain the actions together in the ActionBuilder 您可以在ActionBuilder中将操作链接在一起
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest
is then passed into the controller you would have access to request.account
, request.user
and all the usual request parameters. 如果然后将AuthenticatedWrappedRequest
传递给控制器,则可以访问request.account
, request.user
和所有常用请求参数。
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest
. 正如您所看到的,有几种情况下响应未知会产生BadRequest
。 In reality these should never get called as far as I can tell but they are in there just incase. 实际上,就我所知,这些应该永远不会被调用,但它们只是在那里。
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too. 我很想得到关于这个解决方案的一些反馈,因为我对Scala还是比较新的,我不确定是否有更好的方法来做同样的结果,但我希望这对某人也有用。
I wrote a standalone small (ish) example that does what you're looking for: 我写了一个独立的小(ish)示例来完成你正在寻找的东西:
https://github.com/aellerton/play-login-example https://github.com/aellerton/play-login-example
I gave up trying to use the Security
classes that exist in the play framework proper. 我放弃了尝试使用play框架中存在的Security
类。 I'm sure they're good, but I just couldn't understand them. 我相信他们很好,但我无法理解他们。
Brief guide... 简要指南......
In the example code, a controller is declared as using the AuthenticatedRequests
trait: 在示例代码中,控制器声明为使用AuthenticatedRequests
特征:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication
action: 使用RequireAuthentication
操作强制任何页面要求身份验证(或重定向以获取它):
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication
action: 使用AbandonAuthentication
操作完成AbandonAuthentication
:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests
trait, eg: 请注意,为此,您必须覆盖AuthenticatedRequests
特征中的方法,例如:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; 还有更多; best to see the code. 最好看看代码。
HTH Andrew HTH安德鲁
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.