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個:二、三、四、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')。
   
相關文章
相關標籤/搜索