diff --git a/HISTORY.md b/HISTORY.md index 9ef562a7..901071f6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -499,68 +499,7 @@ However two more methods added to the `Controller`. - `RelTmpl() string`, returns the relative template directory based on the controller's name. These are useful when dealing with big `controllers`, they help you to keep align with any -future changes inside your application. - -Let's refactor our [ProfileController](_examples/mvc/controller-with-model-and-view/main.go) enhancemed by these two new functions. - -```go -func (pc *ProfileController) tmpl(relativeTmplPath string) { - // the relative template files directory of this controller. - views := pc.RelTmpl() - pc.Tmpl = views + relativeTmplPath -} - -func (pc *ProfileController) match(relativeRequestPath string) bool { - // the relative request path of this controller. - path := pc.RelPath() - return path == relativeRequestPath -} - -func (pc *ProfileController) Get() { - // requested: "/profile" - // so relative path is "/" because of the ProfileController. - if pc.match("/") { - - // views/profile/index.html - pc.tmpl("index.html") - return - } - - // requested: "/profile/browse" - // so relative path is "/browse". - if pc.match("/browse") { - pc.Path = "/profile" - return - } - - // requested: "/profile/me" - // so the relative path is "/me" - if pc.match("/me") { - - // views/profile/me.html - pc.tmpl("me.html") - return - } - - // requested: "/profile/$ID" - // so the relative path is "/$ID" - id, _ := pc.Params.GetInt64("id") - - user, found := pc.DB.GetUserByID(id) - if !found { - pc.Status = iris.StatusNotFound - - // views/profile/notfound.html - pc.tmpl("notfound.html") - pc.Data["ID"] = id - return - } - - // views/profile/profile.html - pc.tmpl("profile.html") - pc.User = user -} -``` +future changes inside your application. Want to learn more about these functions? Go to the [mvc/controller_test.go](mvc/controller_test.go) file and scroll to the bottom! @@ -769,7 +708,7 @@ and it adds its logic to its `BeginRequest`, [here](https://github.com/kataras/i Read access to the current route via the `Route` field. -**Using Iris MVC for code reuse** +**Using Iris MVC for code reuse** By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user. @@ -778,9 +717,7 @@ If you're new to back-end web development read about the MVC architectural patte Follow the examples below, -- [Hello world](_examples/mvc/hello-world/main.go) -- [Session Controller](_examples/mvc/session-controller/main.go) -- [A simple but featured Controller with model and views](_examples/mvc/controller-with-model-and-view). +https://github.com/kataras/iris/tree/master/_examples/#mvc ### Bugs diff --git a/README.md b/README.md index 47112136..d54c1c35 100644 --- a/README.md +++ b/README.md @@ -712,7 +712,7 @@ func (m Movie) IsValid() bool { ``` Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, -so theoritically, something like the following is permitted if it's really necessary; +so theoretically, something like the following is permitted if it's really necessary; ```go // Dispatch completes the `kataras/iris/mvc#Result` interface. diff --git a/_examples/README.md b/_examples/README.md index b44c2f3e..b0058caa 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -183,15 +183,18 @@ func(c *ExampleController) Get() string | (string, string) | (string, int) | int | - (int, string | + (int, string) | (string, error) | + bool | + (any, bool) | + (bool, any) | error | (int, error) | (customStruct, error) | customStruct | (customStruct, int) | (customStruct, string) | - mvc.Result or (mvc.Result, error) + mvc.Result or (mvc.Result, error) and so on... ``` where [mvc.Result](https://github.com/kataras/iris/blob/master/mvc/method_result.go) is an interface which contains only that function: `Dispatch(ctx iris.Context)`. @@ -204,13 +207,20 @@ If you're new to back-end web development read about the MVC architectural patte Follow the examples below, +- [Hello world](mvc/hello-world/main.go) **UPDATED** +- [Session Controller](mvc/session-controller/main.go) **UPDATED** - [Overview - Plus Repository and Service layers](mvc/overview) **NEW** - [Login showcase - Plus Repository and Service layers](mvc/login) **NEW** - ### Subdomains diff --git a/_examples/mvc/controller-with-model-and-view/main.go b/_examples/mvc/controller-with-model-and-view/main.go deleted file mode 100644 index 61da2a35..00000000 --- a/_examples/mvc/controller-with-model-and-view/main.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "sync" - - "github.com/kataras/iris" -) - -func main() { - app := iris.New() - app.RegisterView(iris.HTML("./views", ".html")) - - // when we have a path separated by spaces - // then the Controller is registered to all of them one by one. - // - // myDB is binded to the controller's `*DB` field: use only structs and pointers. - app.Controller("/profile /profile/browse /profile/{id:int} /profile/me", - new(ProfileController), myDB) // IMPORTANT - - app.Run(iris.Addr(":8080")) -} - -// UserModel our example model which will render on the template. -type UserModel struct { - ID int64 - Username string -} - -// DB is our example database. -type DB struct { - usersTable map[int64]UserModel - mu sync.RWMutex -} - -// GetUserByID imaginary database lookup based on user id. -func (db *DB) GetUserByID(id int64) (u UserModel, found bool) { - db.mu.RLock() - u, found = db.usersTable[id] - db.mu.RUnlock() - return -} - -var myDB = &DB{ - usersTable: map[int64]UserModel{ - 1: {1, "kataras"}, - 2: {2, "makis"}, - 42: {42, "jdoe"}, - }, -} - -// ProfileController our example user controller which controls -// the paths of "/profile" "/profile/{id:int}" and "/profile/me". -type ProfileController struct { - iris.Controller // IMPORTANT - - User UserModel `iris:"model"` - // we will bind it but you can also tag it with`iris:"persistence"` - // and init the controller with manual &PorifleController{DB: myDB}. - DB *DB -} - -// These two functions are totally optional, of course, don't use them if you -// don't need such as a coupled behavior. -func (pc *ProfileController) tmpl(relativeTmplPath string) { - // the relative templates directory of this controller. - views := pc.RelTmpl() - pc.Tmpl = views + relativeTmplPath -} - -func (pc *ProfileController) match(relativeRequestPath string) bool { - // the relative request path based on this controller's name. - path := pc.RelPath() - return path == relativeRequestPath -} - -// Get method handles all "GET" HTTP Method requests of the controller's paths. -func (pc *ProfileController) Get() { // IMPORTANT - // requested: "/profile" - if pc.match("/") { - pc.tmpl("index.html") - return - } - - // requested: "/profile/browse" - // this exists only to proof the concept of changing the path: - // it will result to a redirection. - if pc.match("/browse") { - pc.Path = "/profile" - return - } - - // requested: "/profile/me" - if pc.match("/me") { - pc.tmpl("me.html") - return - } - - // requested: "/profile/$ID" - id, _ := pc.Params.GetInt64("id") - - user, found := pc.DB.GetUserByID(id) - if !found { - pc.Status = iris.StatusNotFound - pc.tmpl("notfound.html") - pc.Data["ID"] = id - return - } - - pc.tmpl("profile.html") - pc.User = user -} diff --git a/_examples/mvc/controller-with-model-and-view/views/profile/index.html b/_examples/mvc/controller-with-model-and-view/views/profile/index.html deleted file mode 100644 index 7a97e7df..00000000 --- a/_examples/mvc/controller-with-model-and-view/views/profile/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Profile Browser - - - -

- This is the main page of the /profile path, we'd use that to browser profiles. -

- - - \ No newline at end of file diff --git a/_examples/mvc/controller-with-model-and-view/views/profile/me.html b/_examples/mvc/controller-with-model-and-view/views/profile/me.html deleted file mode 100644 index f60f998c..00000000 --- a/_examples/mvc/controller-with-model-and-view/views/profile/me.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - My Profile - - - -

- This is the current's user imaginary profile space. -

- - - \ No newline at end of file diff --git a/_examples/mvc/controller-with-model-and-view/views/profile/notfound.html b/_examples/mvc/controller-with-model-and-view/views/profile/notfound.html deleted file mode 100644 index ca4b1986..00000000 --- a/_examples/mvc/controller-with-model-and-view/views/profile/notfound.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Not Found - - - -

- User with {{.ID}} doesn't exist! -

- - - \ No newline at end of file diff --git a/_examples/mvc/controller-with-model-and-view/views/profile/profile.html b/_examples/mvc/controller-with-model-and-view/views/profile/profile.html deleted file mode 100644 index 3d16a91e..00000000 --- a/_examples/mvc/controller-with-model-and-view/views/profile/profile.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Profile of {{.User.Username}} - - - -

- This is the profile of a user with ID: {{.User.ID}} and Username: {{.User.Username}} -

- - - \ No newline at end of file diff --git a/_examples/mvc/hello-world/main.go b/_examples/mvc/hello-world/main.go index b83cb8ec..5d2c759a 100644 --- a/_examples/mvc/hello-world/main.go +++ b/_examples/mvc/hello-world/main.go @@ -55,32 +55,54 @@ func main() { type ExampleController struct { // if you build with go1.8 you have to use the mvc package always, // otherwise - // you can simply use `iris.Controller`. - mvc.Controller + // you can, optionally + // use the type alias `iris.C`, + // same for + // context.Context -> iris.Context, + // mvc.Result -> iris.Result, + // mvc.Response -> iris.Response, + // mvc.View -> iris.View + mvc.C } // Get serves // Method: GET // Resource: http://localhost:8080 -func (c *ExampleController) Get() { - c.ContentType = "text/html" - c.Text = "

Welcome!

" +func (c *ExampleController) Get() mvc.Result { + return mvc.Response{ + ContentType: "text/html", + Text: "

Welcome

", + } } // GetPing serves // Method: GET // Resource: http://localhost:8080/ping -func (c *ExampleController) GetPing() { - c.Text = "pong" +func (c *ExampleController) GetPing() string { + return "pong" } // GetHello serves // Method: GET // Resource: http://localhost:8080/hello -func (c *ExampleController) GetHello() { - c.Ctx.JSON(iris.Map{"message": "Hello Iris!"}) +func (c *ExampleController) GetHello() interface{} { + return map[string]string{"message": "Hello Iris!"} } +// GetUserBy serves +// Method: GET +// Resource: http://localhost:8080/user/{username:string} +// By is a reserved "keyword" to tell the framework that you're going to +// bind path parameters in the function's input arguments, and it also +// helps to have "Get" and "GetBy" in the same controller. +// +// func (c *ExampleController) GetUserBy(username string) mvc.Result { +// return mvc.View{ +// Name: "user/username.html", +// Data: username, +// } +// } + /* Can use more than one, the factory will make sure that the correct http methods are being registered for each route for this controller, uncomment these if you want: diff --git a/_examples/mvc/login/web/viewmodels/README.md b/_examples/mvc/login/web/viewmodels/README.md index f7814885..9cb99051 100644 --- a/_examples/mvc/login/web/viewmodels/README.md +++ b/_examples/mvc/login/web/viewmodels/README.md @@ -22,7 +22,7 @@ func (m User) IsValid() bool { ``` Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, -so theoritically, something like the following is permitted if it's really necessary; +so theoretically, something like the following is permitted if it's really necessary; ```go // Dispatch completes the `kataras/iris/mvc#Result` interface. diff --git a/_examples/mvc/overview/web/viewmodels/README.md b/_examples/mvc/overview/web/viewmodels/README.md index 909e83de..002467ba 100644 --- a/_examples/mvc/overview/web/viewmodels/README.md +++ b/_examples/mvc/overview/web/viewmodels/README.md @@ -22,7 +22,7 @@ func (m Movie) IsValid() bool { ``` Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, -so theoritically, something like the following is permitted if it's really necessary; +so theoretically, something like the following is permitted if it's really necessary; ```go // Dispatch completes the `kataras/iris/mvc#Result` interface. diff --git a/_examples/mvc/session-controller/main.go b/_examples/mvc/session-controller/main.go index 6684e5a4..b768da68 100644 --- a/_examples/mvc/session-controller/main.go +++ b/_examples/mvc/session-controller/main.go @@ -1,40 +1,78 @@ +// +build go1.9 + package main import ( + "fmt" "time" "github.com/kataras/iris" - "github.com/kataras/iris/sessions" ) +// VisitController handles the root route. type VisitController struct { - iris.SessionController + iris.C + // the sessions manager, we need that to set `Session`. + // It's binded from `app.Controller`. + Manager *sessions.Sessions + // the current request session, + // its initialization happens at the `BeginRequest`. + Session *sessions.Session + + // A time.time which is binded from the `app.Controller`, + // order of binded fields doesn't matter. StartTime time.Time } -func (u *VisitController) Get() { +// BeginRequest is executed for each Get, Post, Put requests, +// can be used to share context, common data +// or to cancel the request via `ctx.StopExecution()`. +func (c *VisitController) BeginRequest(ctx iris.Context) { + // always call the embedded `BeginRequest` before everything else. + c.C.BeginRequest(ctx) + + if c.Manager == nil { + ctx.Application().Logger().Errorf(`VisitController: sessions manager is nil, you should bind it`) + // dont run the main method handler and any "done" handlers. + ctx.StopExecution() + return + } + + // set the `c.Session` we will use that in our Get method. + c.Session = c.Manager.Start(ctx) +} + +// Get handles +// Method: GET +// Path: http://localhost:8080 +func (c *VisitController) Get() string { // get the visits, before calcuate this new one. - visits, _ := u.Session.GetIntDefault("visits", 0) + visits, _ := c.Session.GetIntDefault("visits", 0) // increment the visits and store to the session. visits++ - u.Session.Set("visits", visits) + c.Session.Set("visits", visits) - // write the current, updated visits - u.Ctx.Writef("%d visit from my current session in %0.1f seconds of server's up-time", - visits, time.Now().Sub(u.StartTime).Seconds()) + // write the current, updated visits. + since := time.Now().Sub(c.StartTime).Seconds() + return fmt.Sprintf("%d visit from my current session in %0.1f seconds of server's up-time", + visits, since) } -func main() { - mySessionManager := sessions.New(sessions.Config{Cookie: "mysession_cookie_name"}) +var ( + manager = sessions.New(sessions.Config{Cookie: "mysession_cookie_name"}) +) +func main() { app := iris.New() - // bind our session manager, which is required, to the `VisitController.SessionManager.Manager` + // bind our session manager, which is required, to the `VisitController.Manager` // and the time.Now() to the `VisitController.StartTime`. - app.Controller("/", new(VisitController), mySessionManager, time.Now()) + app.Controller("/", new(VisitController), + manager, + time.Now()) // 1. open the browser (no in private mode) // 2. navigate to http://localhost:8080 diff --git a/doc.go b/doc.go index 03abdee4..7dda27ca 100644 --- a/doc.go +++ b/doc.go @@ -961,10 +961,15 @@ The example below is not intended to be used in production but it's a good showc // MoviesController is our /movies controller. type MoviesController struct { - // mvc.C is just a lightweight lightweight alternative - // to the "mvc.Controller" controller type, - // use it when you don't need mvc.Controller's fields - // (you don't need those fields when you return values from the method functions). + // if you build with go1.8 you have to use the mvc package always, + // otherwise + // you can, optionally + // use the type alias `iris.C`, + // same for + // context.Context -> iris.Context, + // mvc.Result -> iris.Result, + // mvc.Response -> iris.Response, + // mvc.View -> iris.View mvc.C } @@ -1034,13 +1039,7 @@ different data because the view is simply handling how the data is being display If you're new to back-end web development read about the MVC architectural pattern first, a good start is that wikipedia article: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller. -Follow the examples below, - -- Hello world: https://github.com/kataras/iris/blob/master/_examples/mvc/hello-world/main.go - -- Session Controller usage: https://github.com/kataras/iris/blob/master/_examples/mvc/session-controller/main.go - -- A simple but featured Controller with model and views: https://github.com/kataras/iris/tree/master/_examples/mvc/controller-with-model-and-view +Follow the examples at: https://github.com/kataras/iris/tree/master/_examples/#mvc Parameterized Path diff --git a/mvc/activator/activator.go b/mvc/activator/activator.go index 451656c9..67ac4a39 100644 --- a/mvc/activator/activator.go +++ b/mvc/activator/activator.go @@ -59,7 +59,7 @@ var ( // a new Controller type. // Controller looks the whole flow as one handler, so `ctx.Next` // inside `BeginRequest` is not be respected. -// Alternative way to check if a middleware was procceed succesfully +// Alternative way to check if a middleware was procceed successfully // and called its `ctx.Next` is the `ctx.Proceed(handler) bool`. // You have to navigate to the `context/context#Proceed` function's documentation. type BaseController interface { diff --git a/mvc/activator/methodfunc/func_result_dispatcher.go b/mvc/activator/methodfunc/func_result_dispatcher.go index 6a5a7b87..480a9e36 100644 --- a/mvc/activator/methodfunc/func_result_dispatcher.go +++ b/mvc/activator/methodfunc/func_result_dispatcher.go @@ -171,7 +171,7 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) { found = b if !found { // skip everything, we don't care about other return values, - // this boolean is the heighest in order. + // this boolean is the higher in order. break } continue diff --git a/mvc/method_result_view.go b/mvc/method_result_view.go index 167fcc6a..59742fb2 100644 --- a/mvc/method_result_view.go +++ b/mvc/method_result_view.go @@ -37,7 +37,7 @@ var DefaultViewExt = ".html" func ensureExt(s string) string { if len(s) == 0 { - return "index.html" + return "index" + DefaultViewExt } if strings.IndexByte(s, dotB) < 1 {