简体   繁体   English

Play Framework 2.2动作组合返回自定义对象

[英]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文档之后,我创建了一个ActionActionBuilder但我似乎无法从操作中返回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时进行编译

Additionally... 另外...

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.accountrequest.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM