mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Remove big README examples, will be replaced with small quick examples so new users can found easier to get started
Former-commit-id: d5e804babf0edc0765a4195247071e410b27ea27
This commit is contained in:
parent
ef3a09c126
commit
b8b9643ca7
851
README.md
851
README.md
|
@ -167,857 +167,6 @@ func main() {
|
|||
|
||||
Guidelines for bootstrapping applications can be found at the [_examples/structuring](_examples/#structuring).
|
||||
|
||||
### Quick MVC Tutorial
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Controller("/helloworld", new(HelloWorldController))
|
||||
|
||||
app.Run(iris.Addr("localhost:8080"))
|
||||
}
|
||||
|
||||
type HelloWorldController struct {
|
||||
mvc.C
|
||||
|
||||
// [ Your fields here ]
|
||||
// Request lifecycle data
|
||||
// Models
|
||||
// Database
|
||||
// Global properties
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld
|
||||
|
||||
func (c *HelloWorldController) Get() string {
|
||||
return "This is my default action..."
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/{name:string}
|
||||
|
||||
func (c *HelloWorldController) GetBy(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome
|
||||
|
||||
func (c *HelloWorldController) GetWelcome() (string, int) {
|
||||
return "This is the GetWelcome action func...", iris.StatusOK
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome/{name:string}/{numTimes:int}
|
||||
|
||||
func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
|
||||
// Access to the low-level Context,
|
||||
// output arguments are optional of course so we don't have to use them here.
|
||||
c.Ctx.Writef("Hello %s, NumTimes is: %d", name, numTimes)
|
||||
}
|
||||
```
|
||||
|
||||
> The [_examples/mvc](_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advantage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
|
||||
Every `exported` func prefixed with an HTTP Method(`Get`, `Post`, `Put`, `Delete`...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
|
||||
|
||||
An HTTP endpoint is a targetable URL in the web application, such as `http://localhost:8080/helloworld`, and combines the protocol used: HTTP, the network location of the web server (including the TCP port): `localhost:8080` and the target URI `/helloworld`.
|
||||
|
||||
The first comment states this is an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld" to the base URL. The third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld/welcome" to the URL.
|
||||
|
||||
Controller knows how to handle the "name" on `GetBy` or the "name" and "numTimes" at `GetWelcomeBy`, because of the `By` keyword, and builds the dynamic route without boilerplate; the third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) dynamic method that is invoked by any URL that starts with "/helloworld/welcome" and followed by two more path parts, the first one can accept any value and the second can accept only numbers, i,e: "http://localhost:8080/helloworld/welcome/golang/32719", otherwise a [404 Not Found HTTP Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5) will be sent to the client instead.
|
||||
|
||||
### Quick MVC Tutorial #2
|
||||
|
||||
Iris has a very powerful and **blazing [fast](_benchmarks)** MVC support, you can return any value of any type from a method function
|
||||
and it will be sent to the client as expected.
|
||||
|
||||
* if `string` then it's the body.
|
||||
* if `string` is the second output argument then it's the content type.
|
||||
* if `int` then it's the status code.
|
||||
* if `error` and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead.
|
||||
* if `(int, error)` and error is not nil then the response result will be the error's text with the status code as `int`.
|
||||
* if `bool` is false then it throws 404 not found http error by skipping everything else.
|
||||
* if `custom struct` or `interface{}` or `slice` or `map` then it will be rendered as json, unless a `string` content type is following.
|
||||
* if `mvc.Result` then it executes its `Dispatch` function, so good design patters can be used to split the model's logic where needed.
|
||||
|
||||
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/middleware/basicauth"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// Movie is our sample data structure.
|
||||
type Movie struct {
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// movies contains our imaginary data source.
|
||||
var movies = []Movie{
|
||||
{
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
var basicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Use(basicAuth)
|
||||
|
||||
app.Controller("/movies", new(MoviesController))
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
// MoviesController is our /movies controller.
|
||||
type MoviesController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
// Get returns list of the movies
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []Movie {
|
||||
return movies
|
||||
}
|
||||
|
||||
// GetBy returns a movie
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) Movie {
|
||||
return movies[id]
|
||||
}
|
||||
|
||||
// PutBy updates a movie
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MoviesController) PutBy(id int) Movie {
|
||||
// get the movie
|
||||
m := movies[id]
|
||||
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
c.Ctx.StatusCode(iris.StatusInternalServerError)
|
||||
return Movie{}
|
||||
}
|
||||
file.Close() // we don't need the file
|
||||
poster := info.Filename // imagine that as the url of the uploaded file...
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
movies[id] = m
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
// delete the entry from the movies slice
|
||||
deleted := movies[id].Name
|
||||
movies = append(movies[:id], movies[id+1:]...)
|
||||
// and return the deleted movie's name
|
||||
return iris.Map{"deleted": deleted}
|
||||
}
|
||||
```
|
||||
|
||||
### Quick MVC Tutorial #3
|
||||
|
||||
Nothing stops you from using your favorite **folder structure**. Iris is a low level web framework, it has got MVC first-class support but it doesn't limit your folder structure, this is your choice.
|
||||
|
||||
Structuring depends on your own needs. We can't tell you how to design your own application for sure but you're free to take a closer look to one typical example below;
|
||||
|
||||
[![folder structure example](_examples/mvc/overview/folder_structure.png)](_examples/mvc/overview)
|
||||
|
||||
Shhh, let's spread the code itself.
|
||||
|
||||
#### Data Model Layer
|
||||
|
||||
```go
|
||||
// file: datamodels/movie.go
|
||||
|
||||
package datamodels
|
||||
|
||||
// Movie is our sample data structure.
|
||||
// Keep note that the tags for public-use (for our web app)
|
||||
// should be kept in other file like "web/viewmodels/movie.go"
|
||||
// which could wrap by embedding the datamodels.Movie or
|
||||
// declare new fields instead butwe will use this datamodel
|
||||
// as the only one Movie model in our application,
|
||||
// for the shake of simplicty.
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Source / Data Store Layer
|
||||
|
||||
```go
|
||||
// file: datasource/movies.go
|
||||
|
||||
package datasource
|
||||
|
||||
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
// Movies is our imaginary data source.
|
||||
var Movies = map[int64]datamodels.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Repositories
|
||||
|
||||
The layer which has direct access to the "datasource" and can manipulate data directly.
|
||||
|
||||
```go
|
||||
// file: repositories/movie_repository.go
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
)
|
||||
|
||||
// Query represents the visitor and action queries.
|
||||
type Query func(datamodels.Movie) bool
|
||||
|
||||
// MovieRepository handles the basic operations of a movie entity/model.
|
||||
// It's an interface in order to be testable, i.e a memory movie repository or
|
||||
// a connected to an sql database.
|
||||
type MovieRepository interface {
|
||||
Exec(query Query, action Query, limit int, mode int) (ok bool)
|
||||
|
||||
Select(query Query) (movie datamodels.Movie, found bool)
|
||||
SelectMany(query Query, limit int) (results []datamodels.Movie)
|
||||
|
||||
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
|
||||
Delete(query Query, limit int) (deleted bool)
|
||||
}
|
||||
|
||||
// NewMovieRepository returns a new movie memory-based repository,
|
||||
// the one and only repository type in our example.
|
||||
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
|
||||
return &movieMemoryRepository{source: source}
|
||||
}
|
||||
|
||||
// movieMemoryRepository is a "MovieRepository"
|
||||
// which manages the movies using the memory data source (map).
|
||||
type movieMemoryRepository struct {
|
||||
source map[int64]datamodels.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
const (
|
||||
// ReadOnlyMode will RLock(read) the data .
|
||||
ReadOnlyMode = iota
|
||||
// ReadWriteMode will Lock(read/write) the data.
|
||||
ReadWriteMode
|
||||
)
|
||||
|
||||
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
|
||||
loops := 0
|
||||
|
||||
if mode == ReadOnlyMode {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
} else {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
}
|
||||
|
||||
for _, movie := range r.source {
|
||||
ok = query(movie)
|
||||
if ok {
|
||||
if action(movie) {
|
||||
loops++
|
||||
if actionLimit >= loops {
|
||||
break // break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Select receives a query function
|
||||
// which is fired for every single movie model inside
|
||||
// our imaginary data source.
|
||||
// When that function returns true then it stops the iteration.
|
||||
//
|
||||
// It returns the query's return last known "found" value
|
||||
// and the last known movie model
|
||||
// to help callers to reduce the LOC.
|
||||
//
|
||||
// It's actually a simple but very clever prototype function
|
||||
// I'm using everywhere since I firstly think of it,
|
||||
// hope you'll find it very useful as well.
|
||||
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
|
||||
found = r.Exec(query, func(m datamodels.Movie) bool {
|
||||
movie = m
|
||||
return true
|
||||
}, 1, ReadOnlyMode)
|
||||
|
||||
// set an empty datamodels.Movie if not found at all.
|
||||
if !found {
|
||||
movie = datamodels.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SelectMany same as Select but returns one or more datamodels.Movie as a slice.
|
||||
// If limit <=0 then it returns everything.
|
||||
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
|
||||
r.Exec(query, func(m datamodels.Movie) bool {
|
||||
results = append(results, m)
|
||||
return true
|
||||
}, limit, ReadOnlyMode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||
//
|
||||
// Returns the new movie and an error if any.
|
||||
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// find the biggest ID in order to not have duplications
|
||||
// in productions apps you can use a third-party
|
||||
// library to generate a UUID as string.
|
||||
r.mu.RLock()
|
||||
for _, item := range r.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = movie
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// Update action based on the movie.ID,
|
||||
// here we will allow updating the poster and genre if not empty.
|
||||
// Alternatively we could do pure replace instead:
|
||||
// r.source[id] = movie
|
||||
// and comment the code below;
|
||||
current, exists := r.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
|
||||
if !exists { // ID is not a real one, return an error.
|
||||
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// or comment these and r.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = current
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
|
||||
return r.Exec(query, func(m datamodels.Movie) bool {
|
||||
delete(r.source, m.ID)
|
||||
return true
|
||||
}, limit, ReadWriteMode)
|
||||
}
|
||||
```
|
||||
|
||||
#### Services
|
||||
|
||||
The layer which has access to call functions from the "repositories" and "models" (or even "datamodels" if simple application). It should contain the most of the domain logic.
|
||||
|
||||
```go
|
||||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
)
|
||||
|
||||
// MovieService handles some of the CRUID operations of the movie datamodel.
|
||||
// It depends on a movie repository for its actions.
|
||||
// It's here to decouple the data source from the higher level compoments.
|
||||
// As a result a different repository type can be used with the same logic without any aditional changes.
|
||||
// It's an interface and it's used as interface everywhere
|
||||
// because we may need to change or try an experimental different domain logic at the future.
|
||||
type MovieService interface {
|
||||
GetAll() []datamodels.Movie
|
||||
GetByID(id int64) (datamodels.Movie, bool)
|
||||
DeleteByID(id int64) bool
|
||||
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
|
||||
}
|
||||
|
||||
// NewMovieService returns the default movie service.
|
||||
func NewMovieService(repo repositories.MovieRepository) MovieService {
|
||||
return &movieService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
type movieService struct {
|
||||
repo repositories.MovieRepository
|
||||
}
|
||||
|
||||
// GetAll returns all movies.
|
||||
func (s *movieService) GetAll() []datamodels.Movie {
|
||||
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
|
||||
return true
|
||||
}, -1)
|
||||
}
|
||||
|
||||
// GetByID returns a movie based on its id.
|
||||
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
|
||||
return s.repo.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePosterAndGenreByID updates a movie's poster and genre.
|
||||
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
|
||||
// update the movie and return it.
|
||||
return s.repo.InsertOrUpdate(datamodels.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByID deletes a movie by its id.
|
||||
//
|
||||
// Returns true if deleted otherwise false.
|
||||
func (s *movieService) DeleteByID(id int64) bool {
|
||||
return s.repo.Delete(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
}, 1)
|
||||
}
|
||||
```
|
||||
|
||||
#### View Models
|
||||
|
||||
There should be the view models, the structure that the client will be able to see.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
datamodels.Movie
|
||||
}
|
||||
|
||||
func (m Movie) IsValid() bool {
|
||||
/* do some checks and return true if it's valid... */
|
||||
return m.ID > 0
|
||||
}
|
||||
```
|
||||
|
||||
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
|
||||
so theoretically, something like the following is permitted if it's really necessary;
|
||||
|
||||
```go
|
||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||
// Sends a `Movie` as a controlled http response.
|
||||
// If its ID is zero or less then it returns a 404 not found error
|
||||
// else it returns its json representation,
|
||||
// (just like the controller's functions do for custom types by default).
|
||||
//
|
||||
// Don't overdo it, the application's logic should not be here.
|
||||
// It's just one more step of validation before the response,
|
||||
// simple checks can be added here.
|
||||
//
|
||||
// It's just a showcase,
|
||||
// imagine the potentials this feature gives when designing a bigger application.
|
||||
//
|
||||
// This is called where the return value from a controller's method functions
|
||||
// is type of `Movie`.
|
||||
// For example the `controllers/movie_controller.go#GetBy`.
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if !m.IsValid() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
```
|
||||
|
||||
However, we will use the "datamodels" as the only one models package because
|
||||
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
|
||||
and we don't need any extra functionality or validation inside it.
|
||||
|
||||
#### Controllers
|
||||
|
||||
Handles web requests, bridge between the services and the client.
|
||||
|
||||
```go
|
||||
// file: web/controllers/movie_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MovieController is our /movies controller.
|
||||
type MovieController struct {
|
||||
mvc.C
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
//
|
||||
// The correct way if you have sensitive data:
|
||||
// func (c *MovieController) Get() (results []viewmodels.Movie) {
|
||||
// data := c.Service.GetAll()
|
||||
//
|
||||
// for _, movie := range data {
|
||||
// results = append(results, viewmodels.Movie{movie})
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// otherwise just return the datamodels.
|
||||
func (c *MovieController) Get() (results []datamodels.Movie) {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
||||
return c.Service.GetByID(id) // it will throw 404 if not found.
|
||||
}
|
||||
|
||||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// we don't need the file so close it now.
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
wasDel := c.Service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// return the deleted movie's ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
// right here we can see that a method function can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return iris.StatusBadRequest
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/controllers/hello_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// HelloController is our sample controller
|
||||
// it handles GET: /hello and GET: /hello/{name}
|
||||
type HelloController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
var helloView = mvc.View{
|
||||
Name: "hello/index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Hello Page",
|
||||
"MyMessage": "Welcome to my awesome website",
|
||||
},
|
||||
}
|
||||
|
||||
// Get will return a predefined view with bind data.
|
||||
//
|
||||
// `mvc.Result` is just an interface with a `Dispatch` function.
|
||||
// `mvc.Response` and `mvc.View` are the built'n result type dispatchers
|
||||
// you can even create custom response dispatchers by
|
||||
// implementing the `github.com/kataras/iris/mvc#Result` interface.
|
||||
func (c *HelloController) Get() mvc.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// you can define a standard error in order to be re-usable anywhere in your app.
|
||||
var errBadName = errors.New("bad name")
|
||||
|
||||
// you can just return it as error or even better
|
||||
// wrap this error with an mvc.Response to make it an mvc.Result compatible type.
|
||||
var badName = mvc.Response{Err: errBadName, Code: 400}
|
||||
|
||||
// GetBy returns a "Hello {name}" response.
|
||||
// Demos:
|
||||
// curl -i http://localhost:8080/hello/iris
|
||||
// curl -i http://localhost:8080/hello/anything
|
||||
func (c *HelloController) GetBy(name string) mvc.Result {
|
||||
if name != "iris" {
|
||||
return badName
|
||||
// or
|
||||
// GetBy(name string) (mvc.Result, error) {
|
||||
// return nil, errBadName
|
||||
// }
|
||||
}
|
||||
|
||||
// return mvc.Response{Text: "Hello " + name} OR:
|
||||
return mvc.View{
|
||||
Name: "hello/name.html",
|
||||
Data: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/middleware/basicauth.go
|
||||
|
||||
package middleware
|
||||
|
||||
import "github.com/kataras/iris/middleware/basicauth"
|
||||
|
||||
// BasicAuth middleware sample.
|
||||
var BasicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.Title}} - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>{{.MyMessage}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/name.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.}}' Portfolio - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello {{.}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
> Navigate to the [_examples/view](_examples/#view) for more examples
|
||||
like shared layouts, tmpl funcs, reverse routing and more!
|
||||
|
||||
#### Main
|
||||
|
||||
This file creates any necessary component and links them together.
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
|
||||
// Create our movie repository with some (memory) data from the datasource.
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// Create our movie service, we will bind it to the movie controller.
|
||||
movieService := services.NewMovieService(repo)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// Bind the "movieService" to the MovieController's Service (interface) field.
|
||||
movieService,
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
middleware.BasicAuth)
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/movies
|
||||
// http://localhost:8080/movies/1
|
||||
app.Run(
|
||||
iris.Addr("localhost:8080"),
|
||||
iris.WithoutVersionChecker,
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // enables faster json serialization and more
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
More folder structure guidelines can be found at the [_examples/#structuring](_examples/#structuring) section.
|
||||
|
||||
## Now you are ready to move to the next step and get closer to becoming a pro gopher
|
||||
|
||||
Congratulations, since you've made it so far, we've crafted just for you some next level content to turn you into a real pro gopher 😃
|
||||
|
|
828
README_ZH.md
828
README_ZH.md
|
@ -147,834 +147,6 @@ $ go run main.go
|
|||
|
||||
Iris的一些开发约定可以看看这里[_examples/structuring](_examples/#structuring)。
|
||||
|
||||
### MVC指南
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Controller("/helloworld", new(HelloWorldController))
|
||||
|
||||
app.Run(iris.Addr("localhost:8080"))
|
||||
}
|
||||
|
||||
type HelloWorldController struct {
|
||||
mvc.C
|
||||
|
||||
// [ Your fields here ]
|
||||
// Request lifecycle data
|
||||
// Models
|
||||
// Database
|
||||
// Global properties
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld
|
||||
|
||||
func (c *HelloWorldController) Get() string {
|
||||
return "This is my default action..."
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/{name:string}
|
||||
|
||||
func (c *HelloWorldController) GetBy(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome
|
||||
|
||||
func (c *HelloWorldController) GetWelcome() (string, int) {
|
||||
return "This is the GetWelcome action func...", iris.StatusOK
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome/{name:string}/{numTimes:int}
|
||||
|
||||
func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
|
||||
// Access to the low-level Context,
|
||||
// output arguments are optional of course so we don't have to use them here.
|
||||
c.Ctx.Writef("Hello %s, NumTimes is: %d", name, numTimes)
|
||||
}
|
||||
|
||||
```
|
||||
> [_examples/mvc](_examples/mvc) 和 [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) 两个简单的例子可以让你更好的了解 Iris MVC 的使用方式
|
||||
|
||||
每一个在controller中导出的Go方法名都和HTTP方法(`Get`, `Post`, `Put`, `Delete`...) 一一对应
|
||||
|
||||
在Web应用中一个HTTP访问的资源就是一个URL(统一资源定位符),比如`http://localhost:8080/helloworld`是由HTTP协议、Web服务网络位置(包括TCP端口):`localhost:8080`以及资源名称URI(统一资源标志符) `/helloworld`组成的。
|
||||
|
||||
上面例子第一个方法映射到[HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp)方法,访问资源是"/helloworld",第三个方法映射到[HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp)方法,访问资源是"/helloworld/welcome"
|
||||
|
||||
|
||||
Controller在处理`GetBy`方法时可以识别路径‘name’参数,`GetWelcomeBy`方法可以识别路径‘name’和‘numTimes’参数,因为Controller在识别`By`关键字后可以动态灵活的处理路由;上面第四个方法指示使用 [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp)方法,而且只处理以"/helloworld/welcome"开头的资源位置路径,并且此路径还得包括两部分,第一部分类型没有限制,第二部分只能是数字类型,比如"http://localhost:8080/helloworld/welcome/golang/32719" 是合法的,其它的就会给客户端返回[404 找不到](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5)的提示
|
||||
|
||||
|
||||
### MVC 快速指南 2
|
||||
|
||||
Iris对MVC的支持非常**棒[看看基准测试](_benchmarks)** ,Iris通过方法的返回值,可以给客户端返回任意类型的数据:
|
||||
|
||||
* 如果返回的是 `string` 类型,就直接给客户端返回字符串
|
||||
* 如果第二个返回值是 `string` 类型,那么这个值就是ContentType(HTTP header)的值
|
||||
* 如果返回的是 `int` 类型,这个值就是HTTP状态码
|
||||
* 如果返回 `error` 值不是空,Iris 将会把这个值作为HTTP 400页面的返回值内容
|
||||
* 如果返回 `(int, error)` 类型,并且error不为空,那么Iris返回error的内容,同时把 `int` 值作为HTTP状态码
|
||||
* 如果返回 `bool` 类型,并且值是 false ,Iris直接返回404页面
|
||||
* 如果返回自定义` struct` 、 `interface{}` 、 `slice` 及 `map` ,Iris 将按照JSON的方式返回,注意如果第二个返回值是 `string`,那么Iris就按照这个 `string` 值的ContentType处理了(不一定是'application/json')
|
||||
* 如果 `mvc.Result` 调用了 `Dispatch` 函数, 就会按照自己的逻辑重新处理
|
||||
|
||||
下面这些例子仅供参考,生产环境谨慎使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/middleware/basicauth"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// Movie 是自定义数据结构
|
||||
type Movie struct {
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// movies 对象模拟数据源
|
||||
var movies = []Movie{
|
||||
{
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
var basicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Use(basicAuth)
|
||||
|
||||
app.Controller("/movies", new(MoviesController))
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
// MoviesController 是 /movies controller.
|
||||
type MoviesController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
// 返回 movies列表
|
||||
// 例子:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []Movie {
|
||||
return movies
|
||||
}
|
||||
|
||||
// GetBy 返回一个 movie
|
||||
// 例子:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) Movie {
|
||||
return movies[id]
|
||||
}
|
||||
|
||||
// PutBy 更新一个 movie
|
||||
// 例子:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MoviesController) PutBy(id int) Movie {
|
||||
// 获取一个 movie
|
||||
m := movies[id]
|
||||
|
||||
// 获取一个poster文件
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
c.Ctx.StatusCode(iris.StatusInternalServerError)
|
||||
return Movie{}
|
||||
}
|
||||
file.Close() // 我们不需要这个文件
|
||||
poster := info.Filename // 比如这就是上传的文件url
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// 更新poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
movies[id] = m
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteBy 删除一个 movie
|
||||
// 例子:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
//从movies slice中删除索引
|
||||
deleted := movies[id].Name
|
||||
movies = append(movies[:id], movies[id+1:]...)
|
||||
// 返回删除movie的名称
|
||||
return iris.Map{"deleted": deleted}
|
||||
}
|
||||
```
|
||||
|
||||
### MVC 快速指南 3
|
||||
|
||||
Iris是一个底层的Web开发框架,如果你喜欢按 **目录结构** 的约定方式开发,那么Iris框架对此毫无影响。
|
||||
|
||||
你可以根据自己的需求来创建目录结构,但是我建议你还是最好看看如下的目录结构例子:
|
||||
|
||||
[![目录结构例子](_examples/mvc/overview/folder_structure.png)](_examples/mvc/overview)
|
||||
|
||||
好了,直接上代码。
|
||||
|
||||
|
||||
#### 数据模型层
|
||||
|
||||
```go
|
||||
// file: datamodels/movie.go
|
||||
|
||||
package datamodels
|
||||
|
||||
// Movie是我们例子数据结构
|
||||
// 此Movie可能会定义在类似"web/viewmodels/movie.go"的文件
|
||||
// Movie的数据模型在应用中只有一个,这样使用就很简单了
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据层 / 数据存储层
|
||||
|
||||
```go
|
||||
// file: datasource/movies.go
|
||||
|
||||
package datasource
|
||||
|
||||
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
// Movies是模拟的数据源
|
||||
var Movies = map[int64]datamodels.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据仓库
|
||||
|
||||
数据仓库层直接访问数据源
|
||||
|
||||
```go
|
||||
// file: repositories/movie_repository.go
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
)
|
||||
|
||||
// Query 是数据访问的集合入口
|
||||
type Query func(datamodels.Movie) bool
|
||||
|
||||
// MovieRepository 中会有对movie实体的基本操作
|
||||
type MovieRepository interface {
|
||||
Exec(query Query, action Query, limit int, mode int) (ok bool)
|
||||
|
||||
Select(query Query) (movie datamodels.Movie, found bool)
|
||||
SelectMany(query Query, limit int) (results []datamodels.Movie)
|
||||
|
||||
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
|
||||
Delete(query Query, limit int) (deleted bool)
|
||||
}
|
||||
|
||||
// NewMovieRepository 返回movie内存数据
|
||||
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
|
||||
return &movieMemoryRepository{source: source}
|
||||
}
|
||||
|
||||
// movieMemoryRepository 就是 "MovieRepository",它管理movie的内存数据
|
||||
type movieMemoryRepository struct {
|
||||
source map[int64]datamodels.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
const (
|
||||
// 只读模式
|
||||
ReadOnlyMode = iota
|
||||
// 读写模式
|
||||
ReadWriteMode
|
||||
)
|
||||
|
||||
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
|
||||
loops := 0
|
||||
|
||||
if mode == ReadOnlyMode {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
} else {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
}
|
||||
|
||||
for _, movie := range r.source {
|
||||
ok = query(movie)
|
||||
if ok {
|
||||
if action(movie) {
|
||||
loops++
|
||||
if actionLimit >= loops {
|
||||
break // break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Select方法返回从模拟数据源找出的一个movie数据。
|
||||
// 当找到时就返回true,并停止迭代
|
||||
//
|
||||
// Select 将会返回查询到的最新找到的movie数据,这样可以减少代码量
|
||||
//
|
||||
// 自从我第一次想到用这种简单的原型函数后,我就经常用它了,希望这也对你有用
|
||||
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
|
||||
found = r.Exec(query, func(m datamodels.Movie) bool {
|
||||
movie = m
|
||||
return true
|
||||
}, 1, ReadOnlyMode)
|
||||
|
||||
// 如果没有找到就让datamodels.Movie为空
|
||||
// set an empty datamodels.Movie if not found at all.
|
||||
if !found {
|
||||
movie = datamodels.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 如果要查找很多值,用法基本一致,不过会返回datamodels.Movie slice。
|
||||
// 如果limit<=0,将返回全部数据
|
||||
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
|
||||
r.Exec(query, func(m datamodels.Movie) bool {
|
||||
results = append(results, m)
|
||||
return true
|
||||
}, limit, ReadOnlyMode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 插入或更新数据
|
||||
//
|
||||
// 返回一个新的movie对象和error对象
|
||||
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// 为了数据不重复,找到最大的ID。
|
||||
// 生产环境你可以用第三方库生成一个UUID字串
|
||||
r.mu.RLock()
|
||||
for _, item := range r.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = movie
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
//通过movie.ID更新数据
|
||||
//这里举个例子看如果更新非空的poster和genre
|
||||
//其实我们可以直接更新对象r.source[id] = movie
|
||||
//用Select的话如下所示
|
||||
current, exists := r.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
|
||||
if !exists { // ID不存在,返回error ID
|
||||
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// 或者直接对象操作替换
|
||||
// or comment these and r.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// 类map结构的处理
|
||||
r.mu.Lock()
|
||||
r.source[id] = current
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
|
||||
return r.Exec(query, func(m datamodels.Movie) bool {
|
||||
delete(r.source, m.ID)
|
||||
return true
|
||||
}, limit, ReadWriteMode)
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务层
|
||||
|
||||
服务层主要调用“数据仓库”和“数据模型”的方法(即使是数据模型很简单的应用)。这一层将包含主要的数据处理逻辑。
|
||||
|
||||
|
||||
```go
|
||||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
)
|
||||
|
||||
// MovieService主要包括对movie的CRUID(增删改查)操作。
|
||||
// MovieService主要调用movie 数据仓库的方法。
|
||||
// 下面例子的数据源是更高级别的组件
|
||||
// 这样可以用同样的逻辑可以返回不同的数据仓库
|
||||
// MovieService是一个接口,任何实现的地方都能用,这样可以替换不同的业务逻辑用来测试
|
||||
type MovieService interface {
|
||||
GetAll() []datamodels.Movie
|
||||
GetByID(id int64) (datamodels.Movie, bool)
|
||||
DeleteByID(id int64) bool
|
||||
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
|
||||
}
|
||||
|
||||
// NewMovieService 返回一个 movie 服务.
|
||||
func NewMovieService(repo repositories.MovieRepository) MovieService {
|
||||
return &movieService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
type movieService struct {
|
||||
repo repositories.MovieRepository
|
||||
}
|
||||
|
||||
// GetAll 返回所有 movies.
|
||||
func (s *movieService) GetAll() []datamodels.Movie {
|
||||
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
|
||||
return true
|
||||
}, -1)
|
||||
}
|
||||
|
||||
// GetByID 是通过id找到movie.
|
||||
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
|
||||
return s.repo.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// UpdatePosterAndGenreByID 更新一个 movie的 poster 和 genre.
|
||||
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
|
||||
// update the movie and return it.
|
||||
return s.repo.InsertOrUpdate(datamodels.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByID 通过id删除一个movie
|
||||
//
|
||||
// 返回true表示成功,其它都是失败
|
||||
func (s *movieService) DeleteByID(id int64) bool {
|
||||
return s.repo.Delete(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
}, 1)
|
||||
}
|
||||
```
|
||||
|
||||
#### 视图模型
|
||||
|
||||
视图模型将处理结果返回给客户端
|
||||
|
||||
例子:
|
||||
Example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
datamodels.Movie
|
||||
}
|
||||
|
||||
func (m Movie) IsValid() bool {
|
||||
/* 做一些检测,如果ID合法就返回true */
|
||||
return m.ID > 0
|
||||
}
|
||||
```
|
||||
|
||||
Iris允许在HTTP Response Dispatcher中使用任何自定义数据结构,
|
||||
所以理论上来说,除非万不得已,下面的代码不建议使用
|
||||
|
||||
```go
|
||||
// Dispatch实现了`kataras/iris/mvc#Result`接口。在函数最后发送了一个`Movie`对象作为http response对象。
|
||||
// 如果ID小于等于0就回返回404,或者就返回json数据。
|
||||
//(这样就像控制器的方法默认返回自定义类型一样)
|
||||
//
|
||||
// 不要在这里写过多的代码,应用的主要逻辑不在这里
|
||||
// 在方法返回之前可以做个简单验证处理等等;
|
||||
//
|
||||
// 这里只是一个小例子,想想这个优势在设计大型应用是很有作用的
|
||||
//
|
||||
// 这个方法是在`Movie`类型的控制器调用的。
|
||||
// 例子在这里:`controllers/movie_controller.go#GetBy`。
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if !m.IsValid() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
```
|
||||
然而,我们仅仅用"datamodels"作为一个数据模型包,是因为Movie数据结构没有包含敏感数据,客户端可以访问到其所有字段,我们不需要再有额外的功能去做验证处理了
|
||||
|
||||
|
||||
#### 控制器
|
||||
|
||||
控制器处理Web请求,它是服务层和客户端之间的桥梁
|
||||
|
||||
```go
|
||||
// file: web/controllers/movie_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MovieController是/movies的控制器
|
||||
type MovieController struct {
|
||||
mvc.C
|
||||
|
||||
// MovieService是一个接口,主app对象会持有它
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// 获取movies列表
|
||||
// 例子:
|
||||
// curl -i http://localhost:8080/movies
|
||||
//
|
||||
// 如果你有一些敏感的数据要处理的话,可以按照如下所示的方式:
|
||||
// func (c *MovieController) Get() (results []viewmodels.Movie) {
|
||||
// data := c.Service.GetAll()
|
||||
//
|
||||
// for _, movie := range data {
|
||||
// results = append(results, viewmodels.Movie{movie})
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//否则直接返回数据模型
|
||||
func (c *MovieController) Get() (results []datamodels.Movie) {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy返回一个movie对象
|
||||
// 例子:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
||||
return c.Service.GetByID(id) // 404 没有找到
|
||||
}
|
||||
|
||||
// PutBy更新一个movie.
|
||||
// 例子:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
||||
// 从请求中获取poster和genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// 关闭文件
|
||||
file.Close()
|
||||
|
||||
//想象这就是一个上传文件的url
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
||||
// DeleteBy删除一个movie对象
|
||||
// 例子:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
wasDel := c.Service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// 返回要删除的ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
//现在我们可以看到这里可以返回一个有2个返回值(map或int)的函数
|
||||
//我们并没有指定一个返回的类型
|
||||
return iris.StatusBadRequest
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/controllers/hello_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// HelloController是控制器的例子
|
||||
// 下面会处理GET: /hello and GET: /hello/{name}
|
||||
type HelloController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
var helloView = mvc.View{
|
||||
Name: "hello/index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Hello Page",
|
||||
"MyMessage": "Welcome to my awesome website",
|
||||
},
|
||||
}
|
||||
|
||||
// Get会返回预定义绑定数据的视图
|
||||
//
|
||||
// `mvc.Result`是一个含有`Dispatch`方法的接口
|
||||
// `mvc.Response` 和 `mvc.View` dispatchers 内置类型
|
||||
// 你也可以通过实现`github.com/kataras/iris/mvc#Result`接口来自定义dispatchers
|
||||
func (c *HelloController) Get() mvc.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// 你可以定义一个标准通用的error
|
||||
var errBadName = errors.New("bad name")
|
||||
|
||||
//你也可以将error包裹在mvc.Response中,这样就和mvc.Result类型兼容了
|
||||
var badName = mvc.Response{Err: errBadName, Code: 400}
|
||||
|
||||
// GetBy 返回 "Hello {name}" response
|
||||
// 例子:
|
||||
// curl -i http://localhost:8080/hello/iris
|
||||
// curl -i http://localhost:8080/hello/anything
|
||||
func (c *HelloController) GetBy(name string) mvc.Result {
|
||||
if name != "iris" {
|
||||
return badName
|
||||
// 或者
|
||||
// GetBy(name string) (mvc.Result, error) {
|
||||
// return nil, errBadName
|
||||
// }
|
||||
}
|
||||
|
||||
// 返回 mvc.Response{Text: "Hello " + name} 或者:
|
||||
return mvc.View{
|
||||
Name: "hello/name.html",
|
||||
Data: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/middleware/basicauth.go
|
||||
|
||||
package middleware
|
||||
|
||||
import "github.com/kataras/iris/middleware/basicauth"
|
||||
|
||||
// BasicAuth 中间件例
|
||||
var BasicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.Title}} - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>{{.MyMessage}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/name.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.}}' Portfolio - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello {{.}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
> 戳[_examples/view](_examples/#view) 可以找到更多关于layouts,tmpl,routing的例子
|
||||
|
||||
|
||||
#### 程序入口
|
||||
|
||||
程序入口可以将任何组件包含进来
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// 加载模板文件
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// 注册控制器
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
|
||||
// 创建movie 数据仓库,次仓库包含的是内存级的数据源
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// 创建movie服务, 然后将其与控制器绑定
|
||||
movieService := services.NewMovieService(repo)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// 将"movieService"绑定在 MovieController的Service接口
|
||||
movieService,
|
||||
// 为/movies请求添加basic authentication(admin:password)中间件
|
||||
middleware.BasicAuth)
|
||||
|
||||
// 启动应用localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/movies
|
||||
// http://localhost:8080/movies/1
|
||||
app.Run(
|
||||
iris.Addr("localhost:8080"),
|
||||
iris.WithoutVersionChecker,
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // 可以启用快速json序列化等优化配置
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
更多指南戳 [_examples/#structuring](_examples/#structuring)
|
||||
|
||||
## 现在你已经准备好进入下一阶段,又向专家级gopher迈进一步了
|
||||
|
||||
|
|
|
@ -133,872 +133,13 @@ By creating components that are independent of one another, developers are able
|
|||
|
||||
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).
|
||||
|
||||
## Quick MVC Tutorial Part 1 (without output result)
|
||||
## Examples
|
||||
|
||||
```go
|
||||
package main
|
||||
- [Hello world](mvc/hello-world/main.go) **UPDATED**
|
||||
- [Session Controller](mvc/session-controller/main.go) **UPDATED**
|
||||
- [Overview - Plus Repository and Service layers](mvc/overview) **UPDATED**
|
||||
- [Login showcase - Plus Repository and Service layers](mvc/login) **UPDATED**
|
||||
- [Singleton](mvc/singleton) **NEW**
|
||||
- [Websocket Controller](mvc/websocket) **NEW**
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Controller("/helloworld", new(HelloWorldController))
|
||||
|
||||
app.Run(iris.Addr("localhost:8080"))
|
||||
}
|
||||
|
||||
type HelloWorldController struct {
|
||||
mvc.C
|
||||
|
||||
// [ Your fields here ]
|
||||
// Request lifecycle data
|
||||
// Models
|
||||
// Database
|
||||
// Global properties
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld
|
||||
|
||||
func (c *HelloWorldController) Get() string {
|
||||
return "This is my default action..."
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/{name:string}
|
||||
|
||||
func (c *HelloWorldController) GetBy(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome
|
||||
|
||||
func (c *HelloWorldController) GetWelcome() (string, int) {
|
||||
return "This is the GetWelcome action func...", iris.StatusOK
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome/{name:string}/{numTimes:int}
|
||||
|
||||
func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
|
||||
// Access to the low-level Context,
|
||||
// output arguments are optional of course so we don't have to use them here.
|
||||
c.Ctx.Writef("Hello %s, NumTimes is: %d", name, numTimes)
|
||||
}
|
||||
|
||||
/*
|
||||
func (c *HelloWorldController) Post() {} handles HTTP POST method requests
|
||||
func (c *HelloWorldController) Put() {} handles HTTP PUT method requests
|
||||
func (c *HelloWorldController) Delete() {} handles HTTP DELETE method requests
|
||||
func (c *HelloWorldController) Connect() {} handles HTTP CONNECT method requests
|
||||
func (c *HelloWorldController) Head() {} handles HTTP HEAD method requests
|
||||
func (c *HelloWorldController) Patch() {} handles HTTP PATCH method requests
|
||||
func (c *HelloWorldController) Options() {} handles HTTP OPTIONS method requests
|
||||
func (c *HelloWorldController) Trace() {} handles HTTP TRACE method requests
|
||||
*/
|
||||
|
||||
/*
|
||||
func (c *HelloWorldController) All() {} handles All method requests
|
||||
// OR
|
||||
func (c *HelloWorldController) Any() {} handles All method requests
|
||||
*/
|
||||
```
|
||||
|
||||
> The [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advantage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
|
||||
Every `exported` func prefixed with an HTTP Method(`Get`, `Post`, `Put`, `Delete`...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
|
||||
|
||||
An HTTP endpoint is a targetable URL in the web application, such as `http://localhost:8080/helloworld`, and combines the protocol used: HTTP, the network location of the web server (including the TCP port): `localhost:8080` and the target URI `/helloworld`.
|
||||
|
||||
The first comment states this is an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld" to the base URL. The third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld/welcome" to the URL.
|
||||
|
||||
Controller knows how to handle the "name" on `GetBy` or the "name" and "numTimes" at `GetWelcomeBy`, because of the `By` keyword, and builds the dynamic route without boilerplate; the third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) dynamic method that is invoked by any URL that starts with "/helloworld/welcome" and followed by two more path parts, the first one can accept any value and the second can accept only numbers, i,e: "http://localhost:8080/helloworld/welcome/golang/32719", otherwise a [404 Not Found HTTP Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5) will be sent to the client instead.
|
||||
|
||||
----
|
||||
|
||||
### Quick MVC Tutorial #2
|
||||
|
||||
Iris has a very powerful and **blazing [fast](_benchmarks)** MVC support, you can return any value of any type from a method function
|
||||
and it will be sent to the client as expected.
|
||||
|
||||
* if `string` then it's the body.
|
||||
* if `string` is the second output argument then it's the content type.
|
||||
* if `int` then it's the status code.
|
||||
* if `error` and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead.
|
||||
* if `(int, error)` and error is not nil then the response result will be the error's text with the status code as `int`.
|
||||
* if `bool` is false then it throws 404 not found http error by skipping everything else.
|
||||
* if `custom struct` or `interface{}` or `slice` or `map` then it will be rendered as json, unless a `string` content type is following.
|
||||
* if `mvc.Result` then it executes its `Dispatch` function, so good design patters can be used to split the model's logic where needed.
|
||||
|
||||
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/middleware/basicauth"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// Movie is our sample data structure.
|
||||
type Movie struct {
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// movies contains our imaginary data source.
|
||||
var movies = []Movie{
|
||||
{
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
var basicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Use(basicAuth)
|
||||
|
||||
app.Controller("/movies", new(MoviesController))
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
// MoviesController is our /movies controller.
|
||||
type MoviesController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
// Get returns list of the movies
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []Movie {
|
||||
return movies
|
||||
}
|
||||
|
||||
// GetBy returns a movie
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) Movie {
|
||||
return movies[id]
|
||||
}
|
||||
|
||||
// PutBy updates a movie
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MoviesController) PutBy(id int) Movie {
|
||||
// get the movie
|
||||
m := movies[id]
|
||||
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
c.Ctx.StatusCode(iris.StatusInternalServerError)
|
||||
return Movie{}
|
||||
}
|
||||
file.Close() // we don't need the file
|
||||
poster := info.Filename // imagine that as the url of the uploaded file...
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
movies[id] = m
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
// delete the entry from the movies slice
|
||||
deleted := movies[id].Name
|
||||
movies = append(movies[:id], movies[id+1:]...)
|
||||
// and return the deleted movie's name
|
||||
return iris.Map{"deleted": deleted}
|
||||
}
|
||||
```
|
||||
|
||||
### Quick MVC Tutorial #3
|
||||
|
||||
Nothing stops you from using your favorite **folder structure**. Iris is a low level web framework, it has got MVC first-class support but it doesn't limit your folder structure, this is your choice.
|
||||
|
||||
Structuring depends on your own needs. We can't tell you how to design your own application for sure but you're free to take a closer look to one typical example below;
|
||||
|
||||
[![folder structure example](_examples/mvc/overview/folder_structure.png)](_examples/mvc/overview)
|
||||
|
||||
Shhh, let's spread the code itself.
|
||||
|
||||
#### Data Model Layer
|
||||
|
||||
```go
|
||||
// file: datamodels/movie.go
|
||||
|
||||
package datamodels
|
||||
|
||||
// Movie is our sample data structure.
|
||||
// Keep note that the tags for public-use (for our web app)
|
||||
// should be kept in other file like "web/viewmodels/movie.go"
|
||||
// which could wrap by embedding the datamodels.Movie or
|
||||
// declare new fields instead butwe will use this datamodel
|
||||
// as the only one Movie model in our application,
|
||||
// for the shake of simplicty.
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Source / Data Store Layer
|
||||
|
||||
```go
|
||||
// file: datasource/movies.go
|
||||
|
||||
package datasource
|
||||
|
||||
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
// Movies is our imaginary data source.
|
||||
var Movies = map[int64]datamodels.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Repositories
|
||||
|
||||
The layer which has direct access to the "datasource" and can manipulate data directly.
|
||||
|
||||
```go
|
||||
// file: repositories/movie_repository.go
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
)
|
||||
|
||||
// Query represents the visitor and action queries.
|
||||
type Query func(datamodels.Movie) bool
|
||||
|
||||
// MovieRepository handles the basic operations of a movie entity/model.
|
||||
// It's an interface in order to be testable, i.e a memory movie repository or
|
||||
// a connected to an sql database.
|
||||
type MovieRepository interface {
|
||||
Exec(query Query, action Query, limit int, mode int) (ok bool)
|
||||
|
||||
Select(query Query) (movie datamodels.Movie, found bool)
|
||||
SelectMany(query Query, limit int) (results []datamodels.Movie)
|
||||
|
||||
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
|
||||
Delete(query Query, limit int) (deleted bool)
|
||||
}
|
||||
|
||||
// NewMovieRepository returns a new movie memory-based repository,
|
||||
// the one and only repository type in our example.
|
||||
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
|
||||
return &movieMemoryRepository{source: source}
|
||||
}
|
||||
|
||||
// movieMemoryRepository is a "MovieRepository"
|
||||
// which manages the movies using the memory data source (map).
|
||||
type movieMemoryRepository struct {
|
||||
source map[int64]datamodels.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
const (
|
||||
// ReadOnlyMode will RLock(read) the data .
|
||||
ReadOnlyMode = iota
|
||||
// ReadWriteMode will Lock(read/write) the data.
|
||||
ReadWriteMode
|
||||
)
|
||||
|
||||
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
|
||||
loops := 0
|
||||
|
||||
if mode == ReadOnlyMode {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
} else {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
}
|
||||
|
||||
for _, movie := range r.source {
|
||||
ok = query(movie)
|
||||
if ok {
|
||||
if action(movie) {
|
||||
loops++
|
||||
if actionLimit >= loops {
|
||||
break // break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Select receives a query function
|
||||
// which is fired for every single movie model inside
|
||||
// our imaginary data source.
|
||||
// When that function returns true then it stops the iteration.
|
||||
//
|
||||
// It returns the query's return last known "found" value
|
||||
// and the last known movie model
|
||||
// to help callers to reduce the LOC.
|
||||
//
|
||||
// It's actually a simple but very clever prototype function
|
||||
// I'm using everywhere since I firstly think of it,
|
||||
// hope you'll find it very useful as well.
|
||||
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
|
||||
found = r.Exec(query, func(m datamodels.Movie) bool {
|
||||
movie = m
|
||||
return true
|
||||
}, 1, ReadOnlyMode)
|
||||
|
||||
// set an empty datamodels.Movie if not found at all.
|
||||
if !found {
|
||||
movie = datamodels.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SelectMany same as Select but returns one or more datamodels.Movie as a slice.
|
||||
// If limit <=0 then it returns everything.
|
||||
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
|
||||
r.Exec(query, func(m datamodels.Movie) bool {
|
||||
results = append(results, m)
|
||||
return true
|
||||
}, limit, ReadOnlyMode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||
//
|
||||
// Returns the new movie and an error if any.
|
||||
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// find the biggest ID in order to not have duplications
|
||||
// in productions apps you can use a third-party
|
||||
// library to generate a UUID as string.
|
||||
r.mu.RLock()
|
||||
for _, item := range r.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = movie
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// Update action based on the movie.ID,
|
||||
// here we will allow updating the poster and genre if not empty.
|
||||
// Alternatively we could do pure replace instead:
|
||||
// r.source[id] = movie
|
||||
// and comment the code below;
|
||||
current, exists := r.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
|
||||
if !exists { // ID is not a real one, return an error.
|
||||
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// or comment these and r.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = current
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
|
||||
return r.Exec(query, func(m datamodels.Movie) bool {
|
||||
delete(r.source, m.ID)
|
||||
return true
|
||||
}, limit, ReadWriteMode)
|
||||
}
|
||||
```
|
||||
|
||||
#### Services
|
||||
|
||||
The layer which has access to call functions from the "repositories" and "models" (or even "datamodels" if simple application). It should contain the most of the domain logic.
|
||||
|
||||
```go
|
||||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
)
|
||||
|
||||
// MovieService handles some of the CRUID operations of the movie datamodel.
|
||||
// It depends on a movie repository for its actions.
|
||||
// It's here to decouple the data source from the higher level compoments.
|
||||
// As a result a different repository type can be used with the same logic without any aditional changes.
|
||||
// It's an interface and it's used as interface everywhere
|
||||
// because we may need to change or try an experimental different domain logic at the future.
|
||||
type MovieService interface {
|
||||
GetAll() []datamodels.Movie
|
||||
GetByID(id int64) (datamodels.Movie, bool)
|
||||
DeleteByID(id int64) bool
|
||||
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
|
||||
}
|
||||
|
||||
// NewMovieService returns the default movie service.
|
||||
func NewMovieService(repo repositories.MovieRepository) MovieService {
|
||||
return &movieService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
type movieService struct {
|
||||
repo repositories.MovieRepository
|
||||
}
|
||||
|
||||
// GetAll returns all movies.
|
||||
func (s *movieService) GetAll() []datamodels.Movie {
|
||||
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
|
||||
return true
|
||||
}, -1)
|
||||
}
|
||||
|
||||
// GetByID returns a movie based on its id.
|
||||
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
|
||||
return s.repo.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePosterAndGenreByID updates a movie's poster and genre.
|
||||
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
|
||||
// update the movie and return it.
|
||||
return s.repo.InsertOrUpdate(datamodels.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByID deletes a movie by its id.
|
||||
//
|
||||
// Returns true if deleted otherwise false.
|
||||
func (s *movieService) DeleteByID(id int64) bool {
|
||||
return s.repo.Delete(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
}, 1)
|
||||
}
|
||||
```
|
||||
|
||||
#### View Models
|
||||
|
||||
There should be the view models, the structure that the client will be able to see.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
datamodels.Movie
|
||||
}
|
||||
|
||||
func (m Movie) IsValid() bool {
|
||||
/* do some checks and return true if it's valid... */
|
||||
return m.ID > 0
|
||||
}
|
||||
```
|
||||
|
||||
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
|
||||
so theoretically, something like the following is permitted if it's really necessary;
|
||||
|
||||
```go
|
||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||
// Sends a `Movie` as a controlled http response.
|
||||
// If its ID is zero or less then it returns a 404 not found error
|
||||
// else it returns its json representation,
|
||||
// (just like the controller's functions do for custom types by default).
|
||||
//
|
||||
// Don't overdo it, the application's logic should not be here.
|
||||
// It's just one more step of validation before the response,
|
||||
// simple checks can be added here.
|
||||
//
|
||||
// It's just a showcase,
|
||||
// imagine the potentials this feature gives when designing a bigger application.
|
||||
//
|
||||
// This is called where the return value from a controller's method functions
|
||||
// is type of `Movie`.
|
||||
// For example the `controllers/movie_controller.go#GetBy`.
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if !m.IsValid() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
```
|
||||
|
||||
However, we will use the "datamodels" as the only one models package because
|
||||
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
|
||||
and we don't need any extra functionality or validation inside it.
|
||||
|
||||
#### Controllers
|
||||
|
||||
Handles web requests, bridge between the services and the client.
|
||||
|
||||
```go
|
||||
// file: web/controllers/movie_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MovieController is our /movies controller.
|
||||
type MovieController struct {
|
||||
mvc.C
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
//
|
||||
// The correct way if you have sensitive data:
|
||||
// func (c *MovieController) Get() (results []viewmodels.Movie) {
|
||||
// data := c.Service.GetAll()
|
||||
//
|
||||
// for _, movie := range data {
|
||||
// results = append(results, viewmodels.Movie{movie})
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// otherwise just return the datamodels.
|
||||
func (c *MovieController) Get() (results []datamodels.Movie) {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
||||
return c.Service.GetByID(id) // it will throw 404 if not found.
|
||||
}
|
||||
|
||||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// we don't need the file so close it now.
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
wasDel := c.Service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// return the deleted movie's ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
// right here we can see that a method function can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return iris.StatusBadRequest
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/controllers/hello_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// HelloController is our sample controller
|
||||
// it handles GET: /hello and GET: /hello/{name}
|
||||
type HelloController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
var helloView = mvc.View{
|
||||
Name: "hello/index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Hello Page",
|
||||
"MyMessage": "Welcome to my awesome website",
|
||||
},
|
||||
}
|
||||
|
||||
// Get will return a predefined view with bind data.
|
||||
//
|
||||
// `mvc.Result` is just an interface with a `Dispatch` function.
|
||||
// `mvc.Response` and `mvc.View` are the built'n result type dispatchers
|
||||
// you can even create custom response dispatchers by
|
||||
// implementing the `github.com/kataras/iris/mvc#Result` interface.
|
||||
func (c *HelloController) Get() mvc.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// you can define a standard error in order to be re-usable anywhere in your app.
|
||||
var errBadName = errors.New("bad name")
|
||||
|
||||
// you can just return it as error or even better
|
||||
// wrap this error with an mvc.Response to make it an mvc.Result compatible type.
|
||||
var badName = mvc.Response{Err: errBadName, Code: 400}
|
||||
|
||||
// GetBy returns a "Hello {name}" response.
|
||||
// Demos:
|
||||
// curl -i http://localhost:8080/hello/iris
|
||||
// curl -i http://localhost:8080/hello/anything
|
||||
func (c *HelloController) GetBy(name string) mvc.Result {
|
||||
if name != "iris" {
|
||||
return badName
|
||||
// or
|
||||
// GetBy(name string) (mvc.Result, error) {
|
||||
// return nil, errBadName
|
||||
// }
|
||||
}
|
||||
|
||||
// return mvc.Response{Text: "Hello " + name} OR:
|
||||
return mvc.View{
|
||||
Name: "hello/name.html",
|
||||
Data: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/middleware/basicauth.go
|
||||
|
||||
package middleware
|
||||
|
||||
import "github.com/kataras/iris/middleware/basicauth"
|
||||
|
||||
// BasicAuth middleware sample.
|
||||
var BasicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.Title}} - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>{{.MyMessage}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/name.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.}}' Portfolio - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello {{.}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
> Navigate to the [_examples/view](https://github.com/kataras/iris/tree/master/_examples/#view) for more examples
|
||||
like shared layouts, tmpl funcs, reverse routing and more!
|
||||
|
||||
#### Main
|
||||
|
||||
This file creates any necessary component and links them together.
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
|
||||
// Create our movie repository with some (memory) data from the datasource.
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// Create our movie service, we will bind it to the movie controller.
|
||||
movieService := services.NewMovieService(repo)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// Bind the "movieService" to the MovieController's Service (interface) field.
|
||||
movieService,
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
middleware.BasicAuth)
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/movies
|
||||
// http://localhost:8080/movies/1
|
||||
app.Run(
|
||||
iris.Addr("localhost:8080"),
|
||||
iris.WithoutVersionChecker,
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // enables faster json serialization and more
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
More folder structure guidelines can be found at the [_examples/#structuring](https://github.com/kataras/iris/tree/master/_examples/#structuring) section.
|
||||
Folder structure guidelines can be found at the [_examples/#structuring](https://github.com/kataras/iris/tree/master/_examples/#structuring) section.
|
Loading…
Reference in New Issue
Block a user