mirror of
https://github.com/kataras/iris.git
synced 2025-12-20 03:17:04 +00:00
8.4.0 | New MVC Features | Refactor examples and godoc for go 1.9 use. Read HISTORY.md.
Former-commit-id: 90c05e743052bc3722e7fefaa0cbb0ed5153a1fb
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
# Controllers from scratch
|
||||
|
||||
This folder shows how [@kataras](https://github.com/kataras) started to develop
|
||||
the MVC idea inside the Iris web framework itself.
|
||||
|
||||
**Now** it has been enhanced and it's a **built'n** feature and can be used as:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
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 {
|
||||
mvc.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
|
||||
}
|
||||
|
||||
// Get method handles all "GET" HTTP Method requests of the controller's paths.
|
||||
func (pc *ProfileController) Get() { // IMPORTANT
|
||||
path := pc.Path
|
||||
|
||||
// requested: /profile path
|
||||
if path == "/profile" {
|
||||
pc.Tmpl = "profile/index.html"
|
||||
return
|
||||
}
|
||||
// requested: /profile/browse
|
||||
// this exists only to proof the concept of changing the path:
|
||||
// it will result to a redirection.
|
||||
if path == "/profile/browse" {
|
||||
pc.Path = "/profile"
|
||||
return
|
||||
}
|
||||
|
||||
// requested: /profile/me path
|
||||
if path == "/profile/me" {
|
||||
pc.Tmpl = "profile/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 = "profile/notfound.html"
|
||||
pc.Data["ID"] = id
|
||||
return
|
||||
}
|
||||
|
||||
pc.Tmpl = "profile/profile.html"
|
||||
pc.User = user
|
||||
}
|
||||
|
||||
/* 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:
|
||||
|
||||
func (pc *ProfileController) Post() {}
|
||||
func (pc *ProfileController) Put() {}
|
||||
func (pc *ProfileController) Delete() {}
|
||||
func (pc *ProfileController) Connect() {}
|
||||
func (pc *ProfileController) Head() {}
|
||||
func (pc *ProfileController) Patch() {}
|
||||
func (pc *ProfileController) Options() {}
|
||||
func (pc *ProfileController) Trace() {}
|
||||
*/
|
||||
|
||||
/*
|
||||
func (c *ProfileController) All() {}
|
||||
// OR
|
||||
func (c *ProfileController) Any() {}
|
||||
*/
|
||||
```
|
||||
|
||||
Example can be found at: [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc).
|
||||
@@ -1,155 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/router"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
// path params.
|
||||
Params *context.RequestParams
|
||||
|
||||
// view properties.
|
||||
Layout string
|
||||
Tmpl string
|
||||
Data map[string]interface{}
|
||||
|
||||
// give access to the request context itself.
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
// all lowercase, so user can see only the fields
|
||||
// that are necessary to him/her.
|
||||
func (b *Controller) init(ctx context.Context) {
|
||||
b.Ctx = ctx
|
||||
b.Params = ctx.Params()
|
||||
b.Data = make(map[string]interface{}, 0)
|
||||
}
|
||||
|
||||
func (b *Controller) exec() {
|
||||
if v := b.Tmpl; v != "" {
|
||||
if l := b.Layout; l != "" {
|
||||
b.Ctx.ViewLayout(l)
|
||||
}
|
||||
if d := b.Data; d != nil {
|
||||
for key, value := range d {
|
||||
b.Ctx.ViewData(key, value)
|
||||
}
|
||||
}
|
||||
b.Ctx.View(v)
|
||||
}
|
||||
}
|
||||
|
||||
// get the field name at compile-time,
|
||||
// will help us to catch any unexpected results on future versions.
|
||||
var baseControllerName = reflect.TypeOf(Controller{}).Name()
|
||||
|
||||
func RegisterController(app *iris.Application, path string, c interface{}) {
|
||||
typ := reflect.TypeOf(c)
|
||||
|
||||
if typ.Kind() != reflect.Ptr {
|
||||
typ = reflect.PtrTo(typ)
|
||||
}
|
||||
|
||||
elem := typ.Elem()
|
||||
|
||||
// check if "c" has the "Controller" typeof `Controller` field.
|
||||
b, has := elem.FieldByName(baseControllerName)
|
||||
if !has {
|
||||
panic("controller should have a field of Controller type")
|
||||
}
|
||||
|
||||
baseControllerFieldIndex := b.Index[0]
|
||||
persistenceFields := make(map[int]reflect.Value, 0)
|
||||
|
||||
if numField := elem.NumField(); numField > 1 {
|
||||
val := reflect.Indirect(reflect.ValueOf(c))
|
||||
|
||||
for i := 0; i < numField; i++ {
|
||||
f := elem.Field(i)
|
||||
valF := val.Field(i)
|
||||
// catch persistence data by tags, i.e:
|
||||
// MyData string `iris:"persistence"`
|
||||
// DB *DB `iris:"persistence"`
|
||||
if t, ok := f.Tag.Lookup("iris"); ok {
|
||||
if t == "persistence" {
|
||||
persistenceFields[i] = reflect.ValueOf(valF.Interface())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// check if has Any() or All()
|
||||
// if yes, then register all http methods and
|
||||
// exit.
|
||||
m, has := typ.MethodByName("Any")
|
||||
if !has {
|
||||
m, has = typ.MethodByName("All")
|
||||
}
|
||||
if has {
|
||||
app.Any(path,
|
||||
controllerToHandler(elem, persistenceFields,
|
||||
baseControllerFieldIndex, m.Index))
|
||||
return
|
||||
}
|
||||
|
||||
// else search the entire controller
|
||||
// for any compatible method function
|
||||
// and register that.
|
||||
for _, method := range router.AllMethods {
|
||||
httpMethodFuncName := strings.Title(strings.ToLower(method))
|
||||
|
||||
m, has := typ.MethodByName(httpMethodFuncName)
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
httpMethodIndex := m.Index
|
||||
|
||||
app.Handle(method, path,
|
||||
controllerToHandler(elem, persistenceFields,
|
||||
baseControllerFieldIndex, httpMethodIndex))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value,
|
||||
baseControllerFieldIndex, httpMethodIndex int) context.Handler {
|
||||
return func(ctx context.Context) {
|
||||
// create a new controller instance of that type(>ptr).
|
||||
c := reflect.New(elem)
|
||||
|
||||
// get the responsible method.
|
||||
// Remember:
|
||||
// To improve the performance
|
||||
// we don't compare the ctx.Method()[HTTP Method]
|
||||
// to the instance's Method, each handler is registered
|
||||
// to a specific http method.
|
||||
methodFunc := c.Method(httpMethodIndex)
|
||||
|
||||
// get the Controller embedded field.
|
||||
b, _ := c.Elem().Field(baseControllerFieldIndex).Addr().Interface().(*Controller)
|
||||
|
||||
if len(persistenceFields) > 0 {
|
||||
elem := c.Elem()
|
||||
for index, value := range persistenceFields {
|
||||
elem.Field(index).Set(value)
|
||||
}
|
||||
}
|
||||
|
||||
// init the new controller instance.
|
||||
b.init(ctx)
|
||||
|
||||
// execute the responsible method for that handler.
|
||||
methodFunc.Interface().(func())()
|
||||
|
||||
// finally, execute the controller.
|
||||
b.exec()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package controllers
|
||||
|
||||
// Index is our index example controller.
|
||||
type Index struct {
|
||||
Controller
|
||||
}
|
||||
|
||||
func (c *Index) Get() {
|
||||
c.Tmpl = "index.html"
|
||||
c.Data["title"] = "Index page"
|
||||
c.Data["message"] = "Hello world!"
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/persistence"
|
||||
)
|
||||
|
||||
// User is our user example controller.
|
||||
type User struct {
|
||||
Controller
|
||||
|
||||
// All fields that are tagged with iris:"persistence"`
|
||||
// are being persistence and kept between the different requests,
|
||||
// meaning that these data will not be reset-ed on each new request,
|
||||
// they will be the same for all requests.
|
||||
CreatedAt time.Time `iris:"persistence"`
|
||||
Title string `iris:"persistence"`
|
||||
DB *persistence.Database `iris:"persistence"`
|
||||
}
|
||||
|
||||
func NewUserController(db *persistence.Database) *User {
|
||||
return &User{
|
||||
CreatedAt: time.Now(),
|
||||
Title: "User page",
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Get serves using the User controller when HTTP Method is "GET".
|
||||
func (c *User) Get() {
|
||||
c.Tmpl = "user/index.html"
|
||||
c.Data["title"] = c.Title
|
||||
c.Data["username"] = "kataras " + c.Params.Get("userid")
|
||||
c.Data["connstring"] = c.DB.Connstring
|
||||
c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
|
||||
}
|
||||
|
||||
/* Can use more than one, the factory will make sure
|
||||
that the correct http methods are being registered for this
|
||||
controller, uncommend these if you want:
|
||||
|
||||
func (c *User) Post() {}
|
||||
func (c *User) Put() {}
|
||||
func (c *User) Delete() {}
|
||||
func (c *User) Connect() {}
|
||||
func (c *User) Head() {}
|
||||
func (c *User) Patch() {}
|
||||
func (c *User) Options() {}
|
||||
func (c *User) Trace() {}
|
||||
*/
|
||||
|
||||
/*
|
||||
func (c *User) All() {}
|
||||
// OR
|
||||
func (c *User) Any() {}
|
||||
*/
|
||||
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/controllers"
|
||||
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/persistence"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
db := persistence.OpenDatabase("a fake db")
|
||||
|
||||
controllers.RegisterController(app, "/", new(controllers.Index))
|
||||
|
||||
controllers.RegisterController(app, "/user/{userid:int}",
|
||||
controllers.NewUserController(db))
|
||||
|
||||
// http://localhost:8080/
|
||||
// http://localhost:8080/user/42
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// User is an example model.
|
||||
type User struct {
|
||||
ID int64
|
||||
Username string
|
||||
Firstname string
|
||||
Lastname string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package persistence
|
||||
|
||||
// Database is our imaginary storage.
|
||||
type Database struct {
|
||||
Connstring string
|
||||
}
|
||||
|
||||
func OpenDatabase(connstring string) *Database {
|
||||
return &Database{Connstring: connstring}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{.message}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1> Hello {{.username}} </h1>
|
||||
|
||||
|
||||
All fields inside a controller that are pointers or they tagged as `iris:"persistence"` are marked as persistence data, meaning
|
||||
that they will not be reset-ed on each new request.
|
||||
<h3>Persistence data from *DB.Connstring: {{.connstring}} </h3>
|
||||
<h3>Persistence data from CreatedAt `iris:"persistence"`: {{.uptime}} seconds </h3>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
@@ -26,12 +25,12 @@ func main() {
|
||||
// register static assets request path and system directory
|
||||
app.StaticWeb("/js", "./static/assets/js")
|
||||
|
||||
h := func(ctx context.Context) {
|
||||
h := func(ctx iris.Context) {
|
||||
ctx.ViewData("", page{PageID: "index page"})
|
||||
ctx.View("index.html")
|
||||
}
|
||||
|
||||
h2 := func(ctx context.Context) {
|
||||
h2 := func(ctx iris.Context) {
|
||||
ctx.ViewData("", page{PageID: "other page"})
|
||||
ctx.View("other.html")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"html/template"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -52,7 +51,7 @@ func newApp(db *DB) *iris.Application {
|
||||
// Serve static files (css)
|
||||
app.StaticWeb("/static", "./resources")
|
||||
|
||||
indexHandler := func(ctx context.Context) {
|
||||
indexHandler := func(ctx iris.Context) {
|
||||
ctx.ViewData("URL_COUNT", db.Len())
|
||||
ctx.View("index.html")
|
||||
}
|
||||
@@ -60,7 +59,7 @@ func newApp(db *DB) *iris.Application {
|
||||
|
||||
// find and execute a short url by its key
|
||||
// used on http://localhost:8080/u/dsaoj41u321dsa
|
||||
execShortURL := func(ctx context.Context, key string) {
|
||||
execShortURL := func(ctx iris.Context, key string) {
|
||||
if key == "" {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
return
|
||||
@@ -75,11 +74,11 @@ func newApp(db *DB) *iris.Application {
|
||||
|
||||
ctx.Redirect(value, iris.StatusTemporaryRedirect)
|
||||
}
|
||||
app.Get("/u/{shortkey}", func(ctx context.Context) {
|
||||
app.Get("/u/{shortkey}", func(ctx iris.Context) {
|
||||
execShortURL(ctx, ctx.Params().Get("shortkey"))
|
||||
})
|
||||
|
||||
app.Post("/shorten", func(ctx context.Context) {
|
||||
app.Post("/shorten", func(ctx iris.Context) {
|
||||
formValue := ctx.FormValue("url")
|
||||
if formValue == "" {
|
||||
ctx.ViewData("FORM_RESULT", "You need to a enter a URL")
|
||||
@@ -107,7 +106,7 @@ func newApp(db *DB) *iris.Application {
|
||||
indexHandler(ctx) // no redirect, we need the FORM_RESULT.
|
||||
})
|
||||
|
||||
app.Post("/clear_cache", func(ctx context.Context) {
|
||||
app.Post("/clear_cache", func(ctx iris.Context) {
|
||||
db.Clear()
|
||||
ctx.Redirect("/")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user