• Grails 1.2参考文档速读(10):Controller


         
    从本篇起,我们将开始进入Grails的Web层,首先让我们从Controller说起。
    Grails中Controller的特点:
    • 线程安全:每次请求创建新实例
    • Controller – Action两级
    • 缺省URL Mapping:/controller/action
    • 文件名以Controller结尾
    • 文件位置:grails-app/controllers
    • 创建命令:grails create-controller
    • 在Controller文件内,Action是闭包
    Controller的例子:
    class BookController {
        def list = {
            ...
        }
    }
    做过Web的开发的都知道,缺省情况下是显示应用的 index.jsp或者类似的东西。在Grails中也有类似的概念:如果url中只指出了Controller但没说明action,那么缺省将定位到 controller的缺省action。缺省Action的规则如下:
    • 如果Controller只有一个action,那么它就是缺省action
    • 使用defaultAction属性指定action名字
    • 名字为index的action
    Web 应用中常见的servletContext、session、request、params等范围在Grails中依然存在,同时Grails还增加了 flash范围,表示当前request和下个request,常用于redirect。对于这些范围的访问:scope['attr']或 scope.attr,如params["findBy"]。
    Grails Web层的基础是Spring MVC,它当然也无法脱离MVC架构。对于Model,在Grails中就是Map。返回Model的方式有:
    • 返回Map:[ book : Book.get( params.id ) ]
    • 不明确指明返回一个Model,那么Model就由Controller的属性组成。注意,正是因为Grails保证了Controller对于每一个请求都会新创建一个实例,我们才能利用这种实例变量。
    • 返回Spring的ModelAndView:return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
    View承担了显示用户界面的任务,在Grails中的规则如下:
    • 没有指定View,那么就查找(jsp文件优先)grails-app/views/controller/action.gsp
    • 指 定View:render(view:“view名”, model:模型)。如:render(view:“display”, model:map),查找grails-app/views/当前controller/display.gsp;render(view:“ /shared/display”, model:map),查找grails-app/views/shared/display.gsp。
    其他的render例子:
    • 文本:render "Hello World!"
    • view:render(view:'show')
    • Template:render(template:'book_template', collection:Book.list())
    • XML:render(text:“some xml", contentType:"text/xml",encoding:"UTF-8")
    • HTML片段,由于Grails中Tag可以象方法一样调用,在使用这种方式时,要注意避免跟这些Tag产生名字冲突,导致产生结果无意间调用了Tag。在使用时,应使用全限定名。如:builder.form
      render {
              builder.form{
                 for(b in books) {
                    div(id:b.id, b.title)
                 }
              }
          }
    重定向一个请求使用redirect:
    • redirect(action:'login')
    • redirect(controller:'home',action:'index')
    • redirect(uri:"/login.html")
    • redirect(action:myaction, params:[p:"v"])
    • redirect(action:"next", params:params)
    • redirect(controller: "test", action: "show", fragment: "profile"),对应/myapp/test/show#profile
    Chaining的特点:
    • 在Action转移时,Model被保存
    • 后续Action可以访问前一个Action的Model
    使用:
    • chain(action:"a", model:[one:1],params:[p:"v"])
    • 访问前一个Action传来的Model,使用chainModel。如:chainModel.one
    例子如下,下例的最终结果是[one:1, two:2, three:3]:
    class ExampleChainController {
        def first = {
            chain(action:second,model:[one:1])
        }
        def second  = {
            chain(action:third,model:[two:2])
        }
        def third = {
             [three:3]
        }
    }
    谈到拦截器,这里需要注意,Grails的拦截器和Spring的拦截器不是一个概念:它只拦截Action,如果要拦截Controller用Filter。拦截器有两种:before和after,定义在Controller中。
    def beforeInterceptor = {  //before返回false,后续action不执行
           println "Tracing action ${actionUri}"
    }
    //入参还可是ModelAndView
    def afterInterceptor = { model ->
           println "Tracing action ${actionUri}"
    }
    除了直接指定闭包,还可以使用表达式来指定拦截哪些Action。表示式的形式:[实现拦截器的方法, 拦截器条件]。
    //auth实现拦截器,except指明拦截器应用的条件
    def beforeInterceptor = [action:this.&auth,except:'login']
    def auth() {
         ...
    }
    def login = {
         // display login page
    }
    条件类型:except,only,使用如下:
    • [action:this.&auth,except:'login']
    • [action:this.&auth,except:['login','register']]
    • [action:this.&auth,only:['secure']]
    对于Grails中的数据绑定,它是以Spring的数据绑定为基础,将请求参数与对象属性绑定到一起:
    • 绑定新对象:new Book(params)
    • 绑定已有对象:
      def b = Book.get(params.id)
          b.properties = params
    Grails的绑定会自动对关联进行处理。
    绑定1端关联:one-to-one和many-to-one,那么url采用形如/book/save?author.id=20,grails自动检测.id后缀,在绑定(如: new Book(params) )时,自动按id加载Author对象。
    绑定多端:one-many和many-to-many,有多种方法:
    • 方 法1:<g:select name="books" from="${Book.list()}" size="5" multiple="yes" optionKey="id" value="${author?.books}" />,Grails会自动根据select的值来绑定。
    • 方法2:如果要更新关联的某些属性,那么就采用:
      <g:textField name="books[0].title" value="..." />
          <g:textField name="books[1].title" value="..." />
      Grails 会自动绑定,但是要保证更新顺序和显示顺序的一致性,对于List和Map不存在问题,对于Set要小心。如果显示的textField比实际关联的个数 多,且数组编号连续,如最后加一个“books[2].title”,Grails会自动给关联新增一个实例。如果显示的textField比实际关联的 个数多,且数组编号不连续,如最后加一个“books[5].title”,Grails会自动补齐中间的空缺。本例中会再添加4个:2、3、4、5。
    绑定多个domain class同样也是在url上做文章,如/book/save?book.title=Title1&author.name=Author1。这时就使用params[‘前缀名’]分别取出数据:
    def b = new Book(params['book'])
    def a = new Author(params['author'])
    对于数据绑定发生的错误,则是通过hasErrors来判断的。显示错误消息已经考虑的i18n,对应的消息键值以typeMismatch为前缀:
    • 按类名(通用): typeMismatch.java.net.URL
    • 按属性(特殊): typeMismatch.Book.publisherURL
    上述各例都是针对Domain Class,但绑定同样也可针对属性单独进行:
    def p = Person.get(1)
    p.properties['firstName','lastName'] = params
    绑定还可以发生在其他类型的对象上,使用bindData(sc, params):
    • params也可使用Map代替
    • 排除某属性绑定:bindData(sc, params, [exclude:'prop'])
    • 只包含指定属性:bindData(sc, params, [include:'prop'])
    对于不想直接暴露Domain Class的场合,可以使用Command Object来代替。
    对于XML和JSON响应,Grails还有一种简单的方法(JSON的话把XML换成JSON即可):render Book.list() as XML。或者你可以使用Builder:
    def books = Book.list()
    //json则为text/json
    render(contentType:"text/xml") {
        books {
            for(b in results) {
                book(title:b.title)
            }
        }   
    }
    Grails 1.2提供了新的JSONBuilder,缺省它会使用老的,如想使用新的,在config.groovy中:grails.json.legacy.builder=false。整个用法依旧如上:
    render(contentType:"text/json") {
        ...
    }
    新JSONBuilder的语法:
    • 输出简单对象({"hello":"world"}):hello = "world"
    • 输出数组({"categories": ["a","b","c"]}):categories = ['a', 'b', 'c']
    • 输出对象数组({"categories":[ {"a":"A"} , {"b":"B"}] }):categories = [ { a = "A" }, { b = "B" } ]
    • 只输出数组([1,2,3]):
      render(contentType:"text/json") {
              element 1
              element 2
              element 3       
          }
    • 输出复杂对象({"categories":["a","b","c"],"title":"Hello JSON","information":{"pages":10}}):
      render(contentType:"text/json") {
              categories = ['a', 'b', 'c']
              title ="Hello JSON"
              information = {
                  pages = 10
              }
          }
    • 动态输出复杂对象:
      def results = Book.list()
          render(contentType:"text/json") {
              books = array {
                  for(b in results) {
                      book title:b.title
                  }
              }   
          }
    除了在render中使用,你还可以直接调用它:
    def builder = new JSONBuilder()
    def result = builder.build {
        categories = ['a', 'b', 'c']
        title ="Hello JSON"
        information = {
            pages = 10
        }
    }
    println result.toString()
    def sw = new StringWriter()
    result.render sw
    再来看看最常见的文件上载,前台页面如下:
    <g:form action="upload" method="post" 
                   enctype="multipart/form-data">
        <input type="file" name="myFile" />
        <input type="submit" />
    </g:form>
    后台的处理有两种选择:
    • 直接使用MultipartFile
      def  f = request.getFile('myFile')
          if(!f.empty) {
               f.transferTo( new File('/some/local/dir/myfile.txt') )
          }
    • 数据绑定
      class Image {
             byte[] myFile
          }
          def img = new Image(params)
    如果想知道上传的文件名,使用MultipartFile.originalFilename。
    Grails的Command Object跟Spring MVC中的没什么区别,是一个View Object。特点如下:
    • 使用上相当于简版Domain Class(不要误解,Command Object和Domain Class是两回事)。
    • 它在Controller目录中定义,一般包含在使用它的Controller文件中,但也可单独抽出。
    • 它还可以使用约束,但不能使用那些要查找数据库的约束,如unique。
    • 它也支持依赖注入。
    使用例子:
    class LoginController {
      def login = { LoginCommand cmd ->
             if(cmd.hasErrors()) {
                    redirect(action:'loginForm')
             }
             else {
                // do something else
            }
      }
    }
    class LoginCommand {
       String username
       String password
       static constraints = {
               username(blank:false, minSize:6)
               password(blank:false, minSize:6)
       }
    }
    重复提交对于Web开发是个老问题了,Grails对此也提供了应对措施:
    • 前台页面: <g:form useToken="true" ...>
    • Controller:
      withForm {
             // good request
          }.invalidToken {
             // bad request
          }
    • 如果没有invalidToken,则可通过flash.invalidToken访问
    • 注意:withForm需要用到 session,因此,在集群环境下,要设置“session affinity”。这同样也适用于任何使用session的程序
    params 提供了类型转换方法,可用于简单场合:params.int('total')。当某参数对应有多个值时,如?name=a&name=b,对其 处理需小心。因为params.name缺省返回字符串,而字符串在Groovy中也可以在循环中使用,这样,导致处理逻辑错误。为了确保总是获得 List,需使用params的list方法:params.list('name')。
       
  • 相关阅读:
    Vue-router(5)之 路由的before家族
    Vue-router(4)之路由跳转
    Vue-router(3)之 router-link 和 router-view 使用
    Vue-router(1)之component标签
    Vue.js(4)- 生命周期
    Vue.js 之 过渡动画
    Vue.js(2)- 过滤器
    函数节流和函数防抖
    认识与学习 BASH
    linux下创建文件与目录时默认被赋予了什么样的权限?
  • 原文地址:https://www.cnblogs.com/lost-in-code/p/3545966.html
Copyright © 2020-2023  润新知