diff --git a/mvc2/binder/binder/input.go b/mvc2/binder/binder/input.go
new file mode 100644
index 00000000..1e583530
--- /dev/null
+++ b/mvc2/binder/binder/input.go
@@ -0,0 +1,4 @@
+package binder
+
+type Input interface {
+}
diff --git a/mvc2/binder/binding.go b/mvc2/binder/binding.go
new file mode 100644
index 00000000..9fe0eed3
--- /dev/null
+++ b/mvc2/binder/binding.go
@@ -0,0 +1,19 @@
+package binder
+
+import (
+ "reflect"
+)
+
+type Binding interface {
+ AddSource(v reflect.Value, source ...reflect.Value)
+}
+
+type StructValue struct {
+ Type reflect.Type
+ Value reflect.Value
+}
+
+type FuncResultValue struct {
+ Type reflect.Type
+ ReturnValue func(ctx []reflect.Value) reflect.Value
+}
diff --git a/mvc2/binder/func_input.go b/mvc2/binder/func_input.go
new file mode 100644
index 00000000..0587a0cc
--- /dev/null
+++ b/mvc2/binder/func_input.go
@@ -0,0 +1 @@
+package binder
diff --git a/mvc2/binder/func_result.go b/mvc2/binder/func_result.go
new file mode 100644
index 00000000..cb8ba5fe
--- /dev/null
+++ b/mvc2/binder/func_result.go
@@ -0,0 +1,53 @@
+package binder
+
+import (
+ "errors"
+ "reflect"
+)
+
+var (
+ errBad = errors.New("bad")
+)
+
+func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
+ typ := indirectTyp(fn.Type())
+
+ // invalid if not a func.
+ if typ.Kind() != reflect.Func {
+ return nil, typ, errBad
+ }
+
+ // invalid if not returns one single value.
+ if typ.NumOut() != 1 {
+ return nil, typ, errBad
+ }
+
+ // invalid if input args length is not one.
+ if typ.NumIn() != 1 {
+ return nil, typ, errBad
+ }
+
+ // invalid if that single input arg is not a typeof context.Context.
+ if !isContext(typ.In(0)) {
+ return nil, typ, errBad
+ }
+
+ outTyp := typ.Out(0)
+ zeroOutVal := reflect.New(outTyp).Elem()
+
+ bf := func(ctxValue []reflect.Value) reflect.Value {
+ // []reflect.Value{reflect.ValueOf(ctx)}
+ results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
+ if len(results) == 0 {
+ return zeroOutVal
+ }
+
+ v := results[0]
+ if !v.IsValid() {
+ return zeroOutVal
+ }
+ return v
+ }
+
+ return bf, outTyp, nil
+}
diff --git a/mvc2/binder/reflect.go b/mvc2/binder/reflect.go
new file mode 100644
index 00000000..20c75b9f
--- /dev/null
+++ b/mvc2/binder/reflect.go
@@ -0,0 +1,107 @@
+package binder
+
+import "reflect"
+
+func isContext(inTyp reflect.Type) bool {
+ return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported.
+}
+
+func indirectVal(v reflect.Value) reflect.Value {
+ return reflect.Indirect(v)
+}
+
+func indirectTyp(typ reflect.Type) reflect.Type {
+ switch typ.Kind() {
+ case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+ return typ.Elem()
+ }
+ return typ
+}
+
+func goodVal(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
+ if v.IsNil() {
+ return false
+ }
+ }
+
+ return v.IsValid()
+}
+
+func isFunc(typ reflect.Type) bool {
+ return typ.Kind() == reflect.Func
+}
+
+/*
+// no f. this, it's too complicated and it will be harder to maintain later on:
+func isSliceAndExpectedItem(got reflect.Type, in []reflect.Type, currentBindersIdx int) bool {
+ kind := got.Kind()
+ // if got result is slice or array.
+ return (kind == reflect.Slice || kind == reflect.Array) &&
+ // if has expected next input.
+ len(in)-1 > currentBindersIdx &&
+ // if the current input's type is not the same as got (if it's not a slice of that types or anything else).
+ equalTypes(got, in[currentBindersIdx])
+}
+*/
+
+func equalTypes(got reflect.Type, expected reflect.Type) bool {
+ if got == expected {
+ return true
+ }
+ // if accepts an interface, check if the given "got" type does
+ // implement this "expected" user handler's input argument.
+ if expected.Kind() == reflect.Interface {
+ // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String())
+ return got.Implements(expected)
+ }
+ return false
+}
+
+// for controller only.
+
+func structFieldIgnored(f reflect.StructField) bool {
+ if !f.Anonymous {
+ return true // if not anonymous(embedded), ignore it.
+ }
+
+ s := f.Tag.Get("ignore")
+ return s == "true" // if has an ignore tag then ignore it.
+}
+
+type field struct {
+ Type reflect.Type
+ Index []int // the index of the field, slice if it's part of a embedded struct
+ Name string // the actual name
+
+ // this could be empty, but in our cases it's not,
+ // it's filled with the service and it's filled from the lookupFields' caller.
+ AnyValue reflect.Value
+}
+
+func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
+ for i, n := 0, typ.NumField(); i < n; i++ {
+ f := typ.Field(i)
+
+ if f.Type.Kind() == reflect.Struct && !structFieldIgnored(f) {
+ fields = append(fields, lookupFields(f.Type, i)...)
+ continue
+ }
+
+ index := []int{i}
+ if parentIndex >= 0 {
+ index = append([]int{parentIndex}, index...)
+ }
+
+ field := field{
+ Type: f.Type,
+ Name: f.Name,
+ Index: index,
+ }
+
+ fields = append(fields, field)
+ }
+
+ return
+}
diff --git a/mvc2/binder/to_struct.go b/mvc2/binder/to_struct.go
new file mode 100644
index 00000000..cb85d0d6
--- /dev/null
+++ b/mvc2/binder/to_struct.go
@@ -0,0 +1,50 @@
+package binder
+
+import (
+ "reflect"
+)
+
+type StructBinding struct {
+ Field StructValue
+ Func FuncResultValue
+}
+
+func (b *StructBinding) AddSource(dest reflect.Value, source ...reflect.Value) {
+ typ := indirectTyp(dest.Type()) //indirectTyp(reflect.TypeOf(dest))
+ if typ.Kind() != reflect.Struct {
+ return
+ }
+
+ fields := lookupFields(typ, -1)
+ for _, f := range fields {
+ for _, s := range source {
+ if s.Type().Kind() == reflect.Func {
+ returnValue, outType, err := makeReturnValue(s)
+ if err != nil {
+ continue
+ }
+ gotTyp = outType
+ service.ReturnValue = returnValue
+ }
+
+ gotTyp := s.Type()
+
+ v := StructValue{
+ Type: gotTyp,
+ Value: s,
+ FieldIndex: f.Index,
+ }
+
+ if equalTypes(gotTyp, f.Type) {
+ service.Type = gotTyp
+ _serv = append(_serv, &service)
+ fmt.Printf("[2] Bind In=%s->%s for struct field[%d]\n", f.Type, gotTyp.String(), f.Index)
+ break
+ }
+ }
+ }
+ fmt.Printf("[2] Bind %d for %s\n", len(_serv), typ.String())
+ *serv = _serv
+
+ return
+}
diff --git a/mvc2/binder_in.go b/mvc2/binder_in.go
new file mode 100644
index 00000000..acc8dee8
--- /dev/null
+++ b/mvc2/binder_in.go
@@ -0,0 +1,173 @@
+package mvc2
+
+import (
+ "reflect"
+)
+
+// InputBinder is the result of `MakeBinder`.
+// It contains the binder wrapped information, like the
+// type that is responsible to bind
+// and a function which will accept a context and returns a value of something.
+type InputBinder struct {
+ BinderType binderType
+ BindType reflect.Type
+ BindFunc func(ctx []reflect.Value) reflect.Value
+}
+
+// key = the func input argument index, value is the responsible input binder.
+type bindersMap map[int]*InputBinder
+
+// joinBindersMap joins the "m2" to m1 and returns the result, it's the same "m1" map.
+// if "m2" is not nil and "m2" is not nil then it loops the "m2"'s keys and sets the values
+// to the "m1", if "m2" is not and not empty nil but m1 is nil then "m1" = "m2".
+// The result may be nil if the "m1" and "m2" are nil or "m2" is empty and "m1" is nil.
+func joinBindersMap(m1, m2 bindersMap) bindersMap {
+ if m2 != nil && len(m2) > 0 {
+ if m1 == nil {
+ m1 = m2
+ } else {
+ for k, v := range m2 {
+ m1[k] = v
+ }
+ }
+ }
+ return m1
+}
+
+// getBindersForInput returns a map of the responsible binders for the "expected" types,
+// which are the expected input parameters' types,
+// based on the available "binders" collection.
+//
+// It returns a map which its key is the index of the "expected" which
+// a valid binder for that in's type found,
+// the value is the pointer of the responsible `InputBinder`.
+//
+// Check of "a nothing responsible for those expected types"
+// should be done using the `len(m) == 0`.
+func getBindersForInput(binders []*InputBinder, expected ...reflect.Type) (m bindersMap) {
+ for idx, in := range expected {
+ if idx == 0 && isContext(in) {
+ // if the first is context then set it directly here.
+ m = make(bindersMap)
+ m[0] = &InputBinder{
+ BindType: contextTyp,
+ BindFunc: func(ctxValues []reflect.Value) reflect.Value {
+ return ctxValues[0]
+ },
+ }
+ continue
+ }
+
+ for _, b := range binders {
+ if equalTypes(b.BindType, in) {
+ if m == nil {
+ m = make(bindersMap)
+ }
+ // fmt.Printf("set index: %d to type: %s where input type is: %s\n", idx, b.BindType.String(), in.String())
+ m[idx] = b
+ break
+ }
+ }
+ }
+
+ return m
+}
+
+// MustMakeFuncInputBinder calls the `MakeFuncInputBinder` and returns its first result, see its docs.
+// It panics on error.
+func MustMakeFuncInputBinder(binder interface{}) *InputBinder {
+ b, err := MakeFuncInputBinder(binder)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+type binderType uint32
+
+const (
+ functionType binderType = iota
+ serviceType
+ invalidType
+)
+
+func resolveBinderType(binder interface{}) binderType {
+ if binder == nil {
+ return invalidType
+ }
+
+ return resolveBinderTypeFromKind(reflect.TypeOf(binder).Kind())
+}
+
+func resolveBinderTypeFromKind(k reflect.Kind) binderType {
+ switch k {
+ case reflect.Func:
+ return functionType
+ case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Array:
+ return serviceType
+ }
+
+ return invalidType
+}
+
+// MakeFuncInputBinder takes a binder function or a struct which contains a "Bind"
+// function and returns an `InputBinder`, which Iris uses to
+// resolve and set the input parameters when a handler is executed.
+//
+// The "binder" can have the following form:
+// `func(iris.Context) UserViewModel`.
+//
+// The return type of the "binder" should be a value instance, not a pointer, for your own protection.
+// The binder function should return only one value and
+// it can accept only one input argument, the Iris' Context (`context.Context` or `iris.Context`).
+func MakeFuncInputBinder(binder interface{}) (*InputBinder, error) {
+ v := reflect.ValueOf(binder)
+ return makeFuncInputBinder(v)
+}
+
+func makeFuncInputBinder(fn reflect.Value) (*InputBinder, error) {
+ typ := indirectTyp(fn.Type())
+
+ // invalid if not a func.
+ if typ.Kind() != reflect.Func {
+ return nil, errBad
+ }
+
+ // invalid if not returns one single value.
+ if typ.NumOut() != 1 {
+ return nil, errBad
+ }
+
+ // invalid if input args length is not one.
+ if typ.NumIn() != 1 {
+ return nil, errBad
+ }
+
+ // invalid if that single input arg is not a typeof context.Context.
+ if !isContext(typ.In(0)) {
+ return nil, errBad
+ }
+
+ outTyp := typ.Out(0)
+ zeroOutVal := reflect.New(outTyp).Elem()
+
+ bf := func(ctxValue []reflect.Value) reflect.Value {
+ // []reflect.Value{reflect.ValueOf(ctx)}
+ results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
+ if len(results) == 0 {
+ return zeroOutVal
+ }
+
+ v := results[0]
+ if !v.IsValid() {
+ return zeroOutVal
+ }
+ return v
+ }
+
+ return &InputBinder{
+ BinderType: functionType,
+ BindType: outTyp,
+ BindFunc: bf,
+ }, nil
+}
diff --git a/mvc2/binder_in_path_param.go b/mvc2/binder_in_path_param.go
new file mode 100644
index 00000000..52196564
--- /dev/null
+++ b/mvc2/binder_in_path_param.go
@@ -0,0 +1,135 @@
+package mvc2
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/memstore"
+ "github.com/kataras/iris/core/router/macro"
+ "github.com/kataras/iris/core/router/macro/interpreter/ast"
+)
+
+func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
+ n := funcTyp.NumIn()
+ funcIn := make([]reflect.Type, n, n)
+ for i := 0; i < n; i++ {
+ funcIn[i] = funcTyp.In(i)
+ }
+ return funcIn
+}
+
+func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) {
+ if len(funcIn) == 0 || len(params) == 0 {
+ return
+ }
+
+ funcInIdx := 0
+ // it's a valid param type.
+ for _, p := range params {
+ in := funcIn[funcInIdx]
+ paramType := p.Type
+ paramName := p.Name
+
+ // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
+ if p.Type.Assignable(in.Kind()) {
+
+ // b = append(b, &InputBinder{
+ // BindType: in, // or p.Type.Kind, should be the same.
+ // BindFunc: func(ctx []reflect.Value) reflect.Value {
+ // // I don't like this ctx[0].Interface(0)
+ // // it will be slow, and silly because we have ctx already
+ // // before the bindings at serve-time, so we will create
+ // // a func for each one of the param types, they are just 4 so
+ // // it worths some dublications.
+ // return getParamValueFromType(ctx[0].Interface(), paramType, paramName)
+ // },
+ // })
+
+ var fn interface{}
+
+ if paramType == ast.ParamTypeInt {
+ fn = func(ctx context.Context) int {
+ v, _ := ctx.Params().GetInt(paramName)
+ return v
+ }
+ } else if paramType == ast.ParamTypeLong {
+ fn = func(ctx context.Context) int64 {
+ v, _ := ctx.Params().GetInt64(paramName)
+ return v
+ }
+
+ } else if paramType == ast.ParamTypeBoolean {
+ fn = func(ctx context.Context) bool {
+ v, _ := ctx.Params().GetBool(paramName)
+ return v
+ }
+
+ } else {
+ // string, path...
+ fn = func(ctx context.Context) string {
+ return ctx.Params().Get(paramName)
+ }
+ }
+
+ fmt.Printf("binder_in_path_param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
+ values = append(values, reflect.ValueOf(fn))
+
+ // inputBinder, err := MakeFuncInputBinder(fn)
+ // if err != nil {
+ // fmt.Printf("err on make func binder: %v\n", err.Error())
+ // continue
+ // }
+
+ // if m == nil {
+ // m = make(bindersMap, 0)
+ // }
+
+ // // fmt.Printf("set param input binder for func arg index: %d\n", funcInIdx)
+ // m[funcInIdx] = inputBinder
+ }
+
+ funcInIdx++
+ }
+
+ return
+ // return m
+}
+
+// PathParams is the context's named path parameters, see `PathParamsBinder` too.
+type PathParams = context.RequestParams
+
+// PathParamsBinder is the binder which will bind the `PathParams` type value to the specific
+// handler's input argument, see `PathParams` as well.
+func PathParamsBinder(ctx context.Context) PathParams {
+ return *ctx.Params()
+}
+
+// PathParam describes a named path parameter, it's the result of the PathParamBinder and the expected
+// handler func's input argument's type, see `PathParamBinder` too.
+type PathParam struct {
+ memstore.Entry
+ Empty bool
+}
+
+// PathParamBinder is the binder which binds a handler func's input argument to a named path parameter
+// based on its name, see `PathParam` as well.
+func PathParamBinder(name string) func(ctx context.Context) PathParam {
+ return func(ctx context.Context) PathParam {
+ e, found := ctx.Params().GetEntry(name)
+ if !found {
+
+ // useless check here but it doesn't hurt,
+ // useful only when white-box tests run.
+ if ctx.Application() != nil {
+ ctx.Application().Logger().Warnf(ctx.HandlerName()+": expected parameter name '%s' to be described in the route's path in order to be received by the `ParamBinder`, please fix it.\n The main handler will not be executed for your own protection.", name)
+ }
+
+ ctx.StopExecution()
+ return PathParam{
+ Empty: true,
+ }
+ }
+ return PathParam{e, false}
+ }
+}
diff --git a/mvc2/binder_in_path_param_test.go b/mvc2/binder_in_path_param_test.go
new file mode 100644
index 00000000..582a1ee2
--- /dev/null
+++ b/mvc2/binder_in_path_param_test.go
@@ -0,0 +1,64 @@
+package mvc2
+
+import (
+ "testing"
+
+ "github.com/kataras/iris/context"
+)
+
+func TestPathParamsBinder(t *testing.T) {
+ m := New().Bind(PathParamsBinder)
+
+ got := ""
+
+ h := m.Handler(func(params PathParams) {
+ got = params.Get("firstname") + params.Get("lastname")
+ })
+
+ ctx := context.NewContext(nil)
+ ctx.Params().Set("firstname", "Gerasimos")
+ ctx.Params().Set("lastname", "Maropoulos")
+ h(ctx)
+ expected := "GerasimosMaropoulos"
+ if got != expected {
+ t.Fatalf("expected the params 'firstname' + 'lastname' to be '%s' but got '%s'", expected, got)
+ }
+}
+func TestPathParamBinder(t *testing.T) {
+ m := New().Bind(PathParamBinder("username"))
+
+ got := ""
+ executed := false
+ h := m.Handler(func(username PathParam) {
+ // this should not be fired at all if "username" param wasn't found at all.
+ // although router is responsible for that but the `ParamBinder` makes that check as well because
+ // the end-developer may put a param as input argument on her/his function but
+ // on its route's path didn't describe the path parameter,
+ // the handler fires a warning and stops the execution for the invalid handler to protect the user.
+ executed = true
+ got = username.String()
+ })
+
+ expectedUsername := "kataras"
+ ctx := context.NewContext(nil)
+ ctx.Params().Set("username", expectedUsername)
+ h(ctx)
+
+ if got != expectedUsername {
+ t.Fatalf("expected the param 'username' to be '%s' but got '%s'", expectedUsername, got)
+ }
+
+ // test the non executed if param not found.
+ executed = false
+ got = ""
+
+ ctx2 := context.NewContext(nil)
+ h(ctx2)
+
+ if got != "" {
+ t.Fatalf("expected the param 'username' to be entirely empty but got '%s'", got)
+ }
+ if executed {
+ t.Fatalf("expected the handler to not be executed")
+ }
+}
diff --git a/mvc2/binder_in_service.go b/mvc2/binder_in_service.go
new file mode 100644
index 00000000..db4fde08
--- /dev/null
+++ b/mvc2/binder_in_service.go
@@ -0,0 +1,81 @@
+package mvc2
+
+import (
+ "reflect"
+)
+
+type serviceFieldBinder struct {
+ Index []int
+ Binder *InputBinder
+}
+
+func getServicesBinderForStruct(binders []*InputBinder, typ reflect.Type) func(elem reflect.Value) {
+ fields := lookupFields(typ, -1)
+ var validBinders []*serviceFieldBinder
+
+ for _, b := range binders {
+ for _, f := range fields {
+ if b.BinderType != serviceType {
+ continue
+ }
+ if equalTypes(b.BindType, f.Type) {
+ validBinders = append(validBinders,
+ &serviceFieldBinder{Index: f.Index, Binder: b})
+ }
+ }
+
+ }
+
+ if len(validBinders) == 0 {
+ return func(_ reflect.Value) {}
+ }
+
+ return func(elem reflect.Value) {
+ for _, b := range validBinders {
+ elem.FieldByIndex(b.Index).Set(b.Binder.BindFunc(nil))
+ }
+ }
+}
+
+// MustMakeServiceInputBinder calls the `MakeServiceInputBinder` and returns its first result, see its docs.
+// It panics on error.
+func MustMakeServiceInputBinder(service interface{}) *InputBinder {
+ s, err := MakeServiceInputBinder(service)
+ if err != nil {
+ panic(err)
+ }
+ return s
+}
+
+// MakeServiceInputBinder uses a difference/or strange approach,
+// we make the services as bind functions
+// in order to keep the rest of the code simpler, however we have
+// a performance penalty when calling the function instead
+// of just put the responsible service to the certain handler's input argument.
+func MakeServiceInputBinder(service interface{}) (*InputBinder, error) {
+ if service == nil {
+ return nil, errNil
+ }
+
+ var (
+ val = reflect.ValueOf(service)
+ typ = val.Type()
+ )
+
+ if !goodVal(val) {
+ return nil, errBad
+ }
+
+ if indirectTyp(typ).Kind() != reflect.Struct {
+ // if the pointer's struct is not a struct then return err bad.
+ return nil, errBad
+ }
+
+ return &InputBinder{
+ BinderType: serviceType,
+ BindType: typ,
+ BindFunc: func(_ []reflect.Value) reflect.Value {
+ return val
+ },
+ }, nil
+}
diff --git a/mvc2/binder_in_service_test.go b/mvc2/binder_in_service_test.go
new file mode 100644
index 00000000..88779dce
--- /dev/null
+++ b/mvc2/binder_in_service_test.go
@@ -0,0 +1,46 @@
+package mvc2
+
+import (
+ "reflect"
+ "testing"
+)
+
+type (
+ testService interface {
+ say(string)
+ }
+ testServiceImpl struct {
+ prefix string
+ }
+)
+
+func (s *testServiceImpl) say(message string) string {
+ return s.prefix + ": " + message
+}
+
+func TestMakeServiceInputBinder(t *testing.T) {
+ expectedService := &testServiceImpl{"say"}
+ b := MustMakeServiceInputBinder(expectedService)
+ // in
+ var (
+ intType = reflect.TypeOf(1)
+ availableBinders = []*InputBinder{b}
+ )
+
+ // 1
+ testCheck(t, "test1", true, testGetBindersForInput(t, availableBinders,
+ []interface{}{expectedService}, reflect.TypeOf(expectedService)))
+ // 2
+ testCheck(t, "test2-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{42}))
+ // 3
+ testCheck(t, "test3-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{42}, intType))
+ // 4
+ testCheck(t, "test4-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{42}))
+ // 5 - check if nothing passed, so no valid binders at all.
+ testCheck(t, "test5", true, testGetBindersForInput(t, availableBinders,
+ []interface{}{}))
+
+}
diff --git a/mvc2/binder_in_test.go b/mvc2/binder_in_test.go
new file mode 100644
index 00000000..099a3578
--- /dev/null
+++ b/mvc2/binder_in_test.go
@@ -0,0 +1,143 @@
+package mvc2
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/kataras/iris/context"
+)
+
+type testUserStruct struct {
+ ID int64
+ Username string
+}
+
+func testBinderFunc(ctx context.Context) testUserStruct {
+ id, _ := ctx.Params().GetInt64("id")
+ username := ctx.Params().Get("username")
+ return testUserStruct{
+ ID: id,
+ Username: username,
+ }
+}
+
+func TestMakeFuncInputBinder(t *testing.T) {
+ testMakeFuncInputBinder(t, testBinderFunc)
+}
+
+func testMakeFuncInputBinder(t *testing.T, binder interface{}) {
+ b, err := MakeFuncInputBinder(binder)
+ if err != nil {
+ t.Fatalf("failed to make binder: %v", err)
+ }
+
+ if b == nil {
+ t.Fatalf("excepted non-nil *InputBinder but got nil")
+ }
+
+ if expected, got := reflect.TypeOf(testUserStruct{}), b.BindType; expected != got {
+ t.Fatalf("expected type of the binder's return value to be: %T but got: %T", expected, got)
+ }
+
+ expected := testUserStruct{
+ ID: 42,
+ Username: "kataras",
+ }
+ ctx := context.NewContext(nil)
+ ctx.Params().Set("id", fmt.Sprintf("%v", expected.ID))
+ ctx.Params().Set("username", expected.Username)
+ ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
+ v := b.BindFunc(ctxValue)
+ if !v.CanInterface() {
+ t.Fatalf("result of binder func cannot be interfaced: %#+v", v)
+ }
+
+ got, ok := v.Interface().(testUserStruct)
+ if !ok {
+ t.Fatalf("result of binder func should be a type of 'testUserStruct' but got: %#+v", v.Interface())
+ }
+
+ if got != expected {
+ t.Fatalf("invalid result of binder func, expected: %v but got: %v", expected, got)
+ }
+}
+
+func testCheck(t *testing.T, testName string, shouldPass bool, errString string) {
+ if shouldPass && errString != "" {
+ t.Fatalf("[%s] %s", testName, errString)
+ }
+ if !shouldPass && errString == "" {
+ t.Fatalf("[%s] expected not to pass", testName)
+ }
+}
+
+// TestGetBindersForInput will test two available binders, one for int
+// and other for a string,
+// the first input will contains both of them in the same order,
+// the second will contain both of them as well but with a different order,
+// the third will contain only the int input and should fail,
+// the forth one will contain only the string input and should fail,
+// the fifth one will contain two integers and should fail,
+// the last one will contain a struct and should fail,
+// that no of othe available binders will support it,
+// so no len of the result should be zero there.
+func TestGetBindersForInput(t *testing.T) {
+ // binders
+ var (
+ stringBinder = MustMakeFuncInputBinder(func(ctx context.Context) string {
+ return "a string"
+ })
+ intBinder = MustMakeFuncInputBinder(func(ctx context.Context) int {
+ return 42
+ })
+ )
+ // in
+ var (
+ stringType = reflect.TypeOf("string")
+ intType = reflect.TypeOf(1)
+ )
+
+ // 1
+ testCheck(t, "test1", true, testGetBindersForInput(t, []*InputBinder{intBinder, stringBinder},
+ []interface{}{"a string", 42}, stringType, intType))
+ availableBinders := []*InputBinder{stringBinder, intBinder} // different order than the fist test.
+ // 2
+ testCheck(t, "test2", true, testGetBindersForInput(t, availableBinders,
+ []interface{}{"a string", 42}, stringType, intType))
+ // 3
+ testCheck(t, "test-3-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{42}, stringType, intType))
+ // 4
+ testCheck(t, "test-4-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{"a string"}, stringType, intType))
+ // 5
+ testCheck(t, "test-5-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{42, 42}, stringType, intType))
+ // 6
+ testCheck(t, "test-6-fail", false, testGetBindersForInput(t, availableBinders,
+ []interface{}{testUserStruct{}}, stringType, intType))
+
+}
+
+func testGetBindersForInput(t *testing.T, binders []*InputBinder, expectingResults []interface{}, in ...reflect.Type) (errString string) {
+ m := getBindersForInput(binders, in...)
+
+ if expected, got := len(expectingResults), len(m); expected != got {
+ return fmt.Sprintf("expected results length(%d) and valid binders length(%d) to be equal, so each input has one binder", expected, got)
+ }
+
+ ctxValue := []reflect.Value{reflect.ValueOf(context.NewContext(nil))}
+ for idx, expected := range expectingResults {
+ if m[idx] != nil {
+ v := m[idx].BindFunc(ctxValue)
+ if got := v.Interface(); got != expected {
+ return fmt.Sprintf("expected result[%d] to be: %v but got: %v", idx, expected, got)
+ }
+ } else {
+ t.Logf("m[%d] = nil on input = %v\n", idx, expected)
+ }
+ }
+
+ return ""
+}
diff --git a/mvc2/engine.go b/mvc2/engine.go
new file mode 100644
index 00000000..f40816e0
--- /dev/null
+++ b/mvc2/engine.go
@@ -0,0 +1,103 @@
+package mvc2
+
+import (
+ "errors"
+ "reflect"
+
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/router"
+)
+
+var (
+ errNil = errors.New("nil")
+ errBad = errors.New("bad")
+ errAlreadyExists = errors.New("already exists")
+)
+
+type Engine struct {
+ binders []*InputBinder
+
+ Input []reflect.Value
+}
+
+func New() *Engine {
+ return new(Engine)
+}
+
+func (e *Engine) Child() *Engine {
+ child := New()
+
+ // copy the current parent's ctx func binders and services to this new child.
+ // if l := len(e.binders); l > 0 {
+ // binders := make([]*InputBinder, l, l)
+ // copy(binders, e.binders)
+ // child.binders = binders
+ // }
+ if l := len(e.Input); l > 0 {
+ input := make([]reflect.Value, l, l)
+ copy(input, e.Input)
+ child.Input = input
+ }
+ return child
+}
+
+func (e *Engine) Bind(binders ...interface{}) *Engine {
+ for _, binder := range binders {
+ // typ := resolveBinderType(binder)
+
+ // var (
+ // b *InputBinder
+ // err error
+ // )
+
+ // if typ == functionType {
+ // b, err = MakeFuncInputBinder(binder)
+ // } else if typ == serviceType {
+ // b, err = MakeServiceInputBinder(binder)
+ // } else {
+ // err = errBad
+ // }
+
+ // if err != nil {
+ // continue
+ // }
+
+ // e.binders = append(e.binders, b)
+
+ e.Input = append(e.Input, reflect.ValueOf(binder))
+ }
+
+ return e
+}
+
+// BindTypeExists returns true if a binder responsible to
+// bind and return a type of "typ" is already registered.
+func (e *Engine) BindTypeExists(typ reflect.Type) bool {
+ // for _, b := range e.binders {
+ // if equalTypes(b.BindType, typ) {
+ // return true
+ // }
+ // }
+ for _, in := range e.Input {
+ if equalTypes(in.Type(), typ) {
+ return true
+ }
+ }
+ return false
+}
+
+func (e *Engine) Handler(handler interface{}) context.Handler {
+ h, _ := MakeHandler(handler, e.binders) // it logs errors already, so on any error the "h" will be nil.
+ return h
+}
+
+type ActivateListener interface {
+ OnActivate(*ControllerActivator)
+}
+
+func (e *Engine) Controller(router router.Party, controller BaseController) {
+ ca := newControllerActivator(e, router, controller)
+ if al, ok := controller.(ActivateListener); ok {
+ al.OnActivate(ca)
+ }
+}
diff --git a/mvc2/handler_out.go b/mvc2/handler_out.go
new file mode 100644
index 00000000..eb7999ad
--- /dev/null
+++ b/mvc2/handler_out.go
@@ -0,0 +1,422 @@
+package mvc2
+
+import (
+ "reflect"
+ "strings"
+
+ "github.com/fatih/structs"
+ "github.com/kataras/iris/context"
+)
+
+// Result is a response dispatcher.
+// All types that complete this interface
+// can be returned as values from the method functions.
+//
+// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview.
+type Result interface {
+ // Dispatch should sends the response to the context's response writer.
+ Dispatch(ctx context.Context)
+}
+
+var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
+
+// Try will check if "fn" ran without any panics,
+// using recovery,
+// and return its result as the final response
+// otherwise it returns the "failure" response if any,
+// if not then a 400 bad request is being sent.
+//
+// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go.
+func Try(fn func() Result, failure ...Result) Result {
+ var failed bool
+ var actionResponse Result
+
+ func() {
+ defer func() {
+ if rec := recover(); rec != nil {
+ failed = true
+ }
+ }()
+ actionResponse = fn()
+ }()
+
+ if failed {
+ if len(failure) > 0 {
+ return failure[0]
+ }
+ return defaultFailureResponse
+ }
+
+ return actionResponse
+}
+
+const slashB byte = '/'
+
+type compatibleErr interface {
+ Error() string
+}
+
+// DefaultErrStatusCode is the default error status code (400)
+// when the response contains an error which is not nil.
+var DefaultErrStatusCode = 400
+
+// DispatchErr writes the error to the response.
+func DispatchErr(ctx context.Context, status int, err error) {
+ if status < 400 {
+ status = DefaultErrStatusCode
+ }
+ ctx.StatusCode(status)
+ if text := err.Error(); text != "" {
+ ctx.WriteString(text)
+ ctx.StopExecution()
+ }
+}
+
+// DispatchCommon is being used internally to send
+// commonly used data to the response writer with a smart way.
+func DispatchCommon(ctx context.Context,
+ statusCode int, contentType string, content []byte, v interface{}, err error, found bool) {
+
+ // if we have a false boolean as a return value
+ // then skip everything and fire a not found,
+ // we even don't care about the given status code or the object or the content.
+ if !found {
+ ctx.NotFound()
+ return
+ }
+
+ status := statusCode
+ if status == 0 {
+ status = 200
+ }
+
+ if err != nil {
+ DispatchErr(ctx, status, err)
+ return
+ }
+
+ // write the status code, the rest will need that before any write ofc.
+ ctx.StatusCode(status)
+ if contentType == "" {
+ // to respect any ctx.ContentType(...) call
+ // especially if v is not nil.
+ contentType = ctx.GetContentType()
+ }
+
+ if v != nil {
+ if d, ok := v.(Result); ok {
+ // write the content type now (internal check for empty value)
+ ctx.ContentType(contentType)
+ d.Dispatch(ctx)
+ return
+ }
+
+ if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
+ _, err = ctx.JSONP(v)
+ } else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
+ _, err = ctx.XML(v, context.XML{Indent: " "})
+ } else {
+ // defaults to json if content type is missing or its application/json.
+ _, err = ctx.JSON(v, context.JSON{Indent: " "})
+ }
+
+ if err != nil {
+ DispatchErr(ctx, status, err)
+ }
+
+ return
+ }
+
+ ctx.ContentType(contentType)
+ // .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
+ // it will not cost anything.
+ ctx.Write(content)
+}
+
+// DispatchFuncResult is being used internally to resolve
+// and send the method function's output values to the
+// context's response writer using a smart way which
+// respects status code, content type, content, custom struct
+// and an error type.
+// Supports for:
+// func(c *ExampleController) Get() string |
+// (string, string) |
+// (string, int) |
+// ...
+// int |
+// (int, string |
+// (string, error) |
+// ...
+// error |
+// (int, error) |
+// (customStruct, error) |
+// ...
+// bool |
+// (int, bool) |
+// (string, bool) |
+// (customStruct, bool) |
+// ...
+// customStruct |
+// (customStruct, int) |
+// (customStruct, string) |
+// Result or (Result, error) and so on...
+//
+// where Get is an HTTP METHOD.
+func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
+ numOut := len(values)
+ if numOut == 0 {
+ return
+ }
+
+ var (
+ // if statusCode > 0 then send this status code.
+ // Except when err != nil then check if status code is < 400 and
+ // if it's set it as DefaultErrStatusCode.
+ // Except when found == false, then the status code is 404.
+ statusCode int
+ // if not empty then use that as content type,
+ // if empty and custom != nil then set it to application/json.
+ contentType string
+ // if len > 0 then write that to the response writer as raw bytes,
+ // except when found == false or err != nil or custom != nil.
+ content []byte
+ // if not nil then check
+ // for content type (or json default) and send the custom data object
+ // except when found == false or err != nil.
+ custom interface{}
+ // if not nil then check for its status code,
+ // if not status code or < 400 then set it as DefaultErrStatusCode
+ // and fire the error's text.
+ err error
+ // if false then skip everything and fire 404.
+ found = true // defaults to true of course, otherwise will break :)
+ )
+
+ for _, v := range values {
+ // order of these checks matters
+ // for example, first we need to check for status code,
+ // secondly the string (for content type and content)...
+ if !v.IsValid() {
+ continue
+ }
+
+ f := v.Interface()
+
+ if b, ok := f.(bool); ok {
+ found = b
+ if !found {
+ // skip everything, we don't care about other return values,
+ // this boolean is the higher in order.
+ break
+ }
+ continue
+ }
+
+ if i, ok := f.(int); ok {
+ statusCode = i
+ continue
+ }
+
+ if s, ok := f.(string); ok {
+ // a string is content type when it contains a slash and
+ // content or custom struct is being calculated already;
+ // (string -> content, string-> content type)
+ // (customStruct, string -> content type)
+ if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 {
+ contentType = s
+ } else {
+ // otherwise is content
+ content = []byte(s)
+ }
+
+ continue
+ }
+
+ if b, ok := f.([]byte); ok {
+ // it's raw content, get the latest
+ content = b
+ continue
+ }
+
+ if e, ok := f.(compatibleErr); ok {
+ if e != nil { // it's always not nil but keep it here.
+ err = e
+ if statusCode < 400 {
+ statusCode = DefaultErrStatusCode
+ }
+ break // break on first error, error should be in the end but we
+ // need to know break the dispatcher if any error.
+ // at the end; we don't want to write anything to the response if error is not nil.
+ }
+ continue
+ }
+
+ // else it's a custom struct or a dispatcher, we'll decide later
+ // because content type and status code matters
+ // do that check in order to be able to correctly dispatch:
+ // (customStruct, error) -> customStruct filled and error is nil
+ if custom == nil && f != nil {
+ custom = f
+ }
+
+ }
+
+ DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
+}
+
+// Response completes the `methodfunc.Result` interface.
+// It's being used as an alternative return value which
+// wraps the status code, the content type, a content as bytes or as string
+// and an error, it's smart enough to complete the request and send the correct response to the client.
+type Response struct {
+ Code int
+ ContentType string
+ Content []byte
+
+ // if not empty then content type is the text/plain
+ // and content is the text as []byte.
+ Text string
+ // If not nil then it will fire that as "application/json" or the
+ // "ContentType" if not empty.
+ Object interface{}
+
+ // If Path is not empty then it will redirect
+ // the client to this Path, if Code is >= 300 and < 400
+ // then it will use that Code to do the redirection, otherwise
+ // StatusFound(302) or StatusSeeOther(303) for post methods will be used.
+ // Except when err != nil.
+ Path string
+
+ // if not empty then fire a 400 bad request error
+ // unless the Status is > 200, then fire that error code
+ // with the Err.Error() string as its content.
+ //
+ // if Err.Error() is empty then it fires the custom error handler
+ // if any otherwise the framework sends the default http error text based on the status.
+ Err error
+ Try func() int
+
+ // if true then it skips everything else and it throws a 404 not found error.
+ // Can be named as Failure but NotFound is more precise name in order
+ // to be visible that it's different than the `Err`
+ // because it throws a 404 not found instead of a 400 bad request.
+ // NotFound bool
+ // let's don't add this yet, it has its dangerous of missuse.
+}
+
+var _ Result = Response{}
+
+// Dispatch writes the response result to the context's response writer.
+func (r Response) Dispatch(ctx context.Context) {
+ if r.Path != "" && r.Err == nil {
+ // it's not a redirect valid status
+ if r.Code < 300 || r.Code >= 400 {
+ if ctx.Method() == "POST" {
+ r.Code = 303 // StatusSeeOther
+ }
+ r.Code = 302 // StatusFound
+ }
+ ctx.Redirect(r.Path, r.Code)
+ return
+ }
+
+ if s := r.Text; s != "" {
+ r.Content = []byte(s)
+ }
+
+ DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err, true)
+}
+
+// View completes the `methodfunc.Result` interface.
+// It's being used as an alternative return value which
+// wraps the template file name, layout, (any) view data, status code and error.
+// It's smart enough to complete the request and send the correct response to the client.
+//
+// Example at: https://github.com/kataras/iris/blob/master/_examples/mvc/overview/web/controllers/hello_controller.go.
+type View struct {
+ Name string
+ Layout string
+ Data interface{} // map or a custom struct.
+ Code int
+ Err error
+}
+
+var _ Result = View{}
+
+const dotB = byte('.')
+
+// DefaultViewExt is the default extension if `view.Name `is missing,
+// but note that it doesn't care about
+// the app.RegisterView(iris.$VIEW_ENGINE("./$dir", "$ext"))'s $ext.
+// so if you don't use the ".html" as extension for your files
+// you have to append the extension manually into the `view.Name`
+// or change this global variable.
+var DefaultViewExt = ".html"
+
+func ensureExt(s string) string {
+ if len(s) == 0 {
+ return "index" + DefaultViewExt
+ }
+
+ if strings.IndexByte(s, dotB) < 1 {
+ s += DefaultViewExt
+ }
+
+ return s
+}
+
+// Dispatch writes the template filename, template layout and (any) data to the client.
+// Completes the `Result` interface.
+func (r View) Dispatch(ctx context.Context) { // r as Response view.
+ if r.Err != nil {
+ if r.Code < 400 {
+ r.Code = DefaultErrStatusCode
+ }
+ ctx.StatusCode(r.Code)
+ ctx.WriteString(r.Err.Error())
+ ctx.StopExecution()
+ return
+ }
+
+ if r.Code > 0 {
+ ctx.StatusCode(r.Code)
+ }
+
+ if r.Name != "" {
+ r.Name = ensureExt(r.Name)
+
+ if r.Layout != "" {
+ r.Layout = ensureExt(r.Layout)
+ ctx.ViewLayout(r.Layout)
+ }
+
+ if r.Data != nil {
+ // In order to respect any c.Ctx.ViewData that may called manually before;
+ dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
+ if ctx.Values().Get(dataKey) == nil {
+ // if no c.Ctx.ViewData then it's empty do a
+ // pure set, it's faster.
+ ctx.Values().Set(dataKey, r.Data)
+ } else {
+ // else check if r.Data is map or struct, if struct convert it to map,
+ // do a range loop and set the data one by one.
+ // context.Map is actually a map[string]interface{} but we have to make that check;
+ if m, ok := r.Data.(map[string]interface{}); ok {
+ setViewData(ctx, m)
+ } else if m, ok := r.Data.(context.Map); ok {
+ setViewData(ctx, m)
+ } else if structs.IsStruct(r.Data) {
+ setViewData(ctx, structs.Map(r))
+ }
+ }
+ }
+
+ ctx.View(r.Name)
+ }
+}
+
+func setViewData(ctx context.Context, data map[string]interface{}) {
+ for k, v := range data {
+ ctx.ViewData(k, v)
+ }
+}
diff --git a/mvc2/handler_out_test.go b/mvc2/handler_out_test.go
new file mode 100644
index 00000000..bf5abda3
--- /dev/null
+++ b/mvc2/handler_out_test.go
@@ -0,0 +1,271 @@
+package mvc2_test
+
+// import (
+// "errors"
+// "testing"
+
+// "github.com/kataras/iris"
+// "github.com/kataras/iris/context"
+// "github.com/kataras/iris/httptest"
+// "github.com/kataras/iris/mvc2"
+// )
+
+// // activator/methodfunc/func_caller.go.
+// // and activator/methodfunc/func_result_dispatcher.go
+
+// type testControllerMethodResult struct {
+// mvc2.C
+// }
+
+// func (c *testControllerMethodResult) Get() mvc2.Result {
+// return mvc2.Response{
+// Text: "Hello World!",
+// }
+// }
+
+// func (c *testControllerMethodResult) GetWithStatus() mvc2.Response { // or mvc.Result again, no problem.
+// return mvc2.Response{
+// Text: "This page doesn't exist",
+// Code: iris.StatusNotFound,
+// }
+// }
+
+// type testCustomStruct struct {
+// Name string `json:"name" xml:"name"`
+// Age int `json:"age" xml:"age"`
+// }
+
+// func (c *testControllerMethodResult) GetJson() mvc2.Result {
+// var err error
+// if c.Ctx.URLParamExists("err") {
+// err = errors.New("error here")
+// }
+// return mvc2.Response{
+// Err: err, // if err != nil then it will fire the error's text with a BadRequest.
+// Object: testCustomStruct{Name: "Iris", Age: 2},
+// }
+// }
+
+// var things = []string{"thing 0", "thing 1", "thing 2"}
+
+// func (c *testControllerMethodResult) GetThingWithTryBy(index int) mvc2.Result {
+// failure := mvc2.Response{
+// Text: "thing does not exist",
+// Code: iris.StatusNotFound,
+// }
+
+// return mvc2.Try(func() mvc2.Result {
+// // if panic because of index exceed the slice
+// // then the "failure" response will be returned instead.
+// return mvc2.Response{Text: things[index]}
+// }, failure)
+// }
+
+// func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) mvc2.Result {
+// return mvc2.Try(func() mvc2.Result {
+// // if panic because of index exceed the slice
+// // then the default failure response will be returned instead (400 bad request).
+// return mvc2.Response{Text: things[index]}
+// })
+// }
+
+// func TestControllerMethodResult(t *testing.T) {
+// app := iris.New()
+// app.Controller("/", new(testControllerMethodResult))
+
+// e := httptest.New(t, app)
+
+// e.GET("/").Expect().Status(iris.StatusOK).
+// Body().Equal("Hello World!")
+
+// e.GET("/with/status").Expect().Status(iris.StatusNotFound).
+// Body().Equal("This page doesn't exist")
+
+// e.GET("/json").Expect().Status(iris.StatusOK).
+// JSON().Equal(iris.Map{
+// "name": "Iris",
+// "age": 2,
+// })
+
+// e.GET("/json").WithQuery("err", true).Expect().
+// Status(iris.StatusBadRequest).
+// Body().Equal("error here")
+
+// e.GET("/thing/with/try/1").Expect().
+// Status(iris.StatusOK).
+// Body().Equal("thing 1")
+// // failure because of index exceed the slice
+// e.GET("/thing/with/try/3").Expect().
+// Status(iris.StatusNotFound).
+// Body().Equal("thing does not exist")
+
+// e.GET("/thing/with/try/default/3").Expect().
+// Status(iris.StatusBadRequest).
+// Body().Equal("Bad Request")
+// }
+
+// type testControllerMethodResultTypes struct {
+// mvc2.C
+// }
+
+// func (c *testControllerMethodResultTypes) GetText() string {
+// return "text"
+// }
+
+// func (c *testControllerMethodResultTypes) GetStatus() int {
+// return iris.StatusBadGateway
+// }
+
+// func (c *testControllerMethodResultTypes) GetTextWithStatusOk() (string, int) {
+// return "OK", iris.StatusOK
+// }
+
+// // tests should have output arguments mixed
+// func (c *testControllerMethodResultTypes) GetStatusWithTextNotOkBy(first string, second string) (int, string) {
+// return iris.StatusForbidden, "NOT_OK_" + first + second
+// }
+
+// func (c *testControllerMethodResultTypes) GetTextAndContentType() (string, string) {
+// return "text", "text/html"
+// }
+
+// type testControllerMethodCustomResult struct {
+// HTML string
+// }
+
+// // The only one required function to make that a custom Response dispatcher.
+// func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) {
+// ctx.HTML(r.HTML)
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomResponse() testControllerMethodCustomResult {
+// return testControllerMethodCustomResult{"text"}
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
+// return testControllerMethodCustomResult{"OK"}, iris.StatusOK
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
+// return testControllerMethodCustomResult{"internal server error"}, iris.StatusInternalServerError
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomStruct() testCustomStruct {
+// return testCustomStruct{"Iris", 2}
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomStructWithStatusNotOk() (testCustomStruct, int) {
+// return testCustomStruct{"Iris", 2}, iris.StatusInternalServerError
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomStructWithContentType() (testCustomStruct, string) {
+// return testCustomStruct{"Iris", 2}, "text/xml"
+// }
+
+// func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCustomStruct, err error) {
+// s = testCustomStruct{"Iris", 2}
+// if c.Ctx.URLParamExists("err") {
+// err = errors.New("omit return of testCustomStruct and fire error")
+// }
+
+// // it should send the testCustomStruct as JSON if error is nil
+// // otherwise it should fire the default error(BadRequest) with the error's text.
+// return
+// }
+
+// func TestControllerMethodResultTypes(t *testing.T) {
+// app := iris.New()
+// app.Controller("/", new(testControllerMethodResultTypes))
+
+// e := httptest.New(t, app)
+
+// e.GET("/text").Expect().Status(iris.StatusOK).
+// Body().Equal("text")
+
+// e.GET("/status").Expect().Status(iris.StatusBadGateway)
+
+// e.GET("/text/with/status/ok").Expect().Status(iris.StatusOK).
+// Body().Equal("OK")
+
+// e.GET("/status/with/text/not/ok/first/second").Expect().Status(iris.StatusForbidden).
+// Body().Equal("NOT_OK_firstsecond")
+
+// e.GET("/text/and/content/type").Expect().Status(iris.StatusOK).
+// ContentType("text/html", "utf-8").
+// Body().Equal("text")
+
+// e.GET("/custom/response").Expect().Status(iris.StatusOK).
+// ContentType("text/html", "utf-8").
+// Body().Equal("text")
+// e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
+// ContentType("text/html", "utf-8").
+// Body().Equal("OK")
+// e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
+// ContentType("text/html", "utf-8").
+// Body().Equal("internal server error")
+
+// expectedResultFromCustomStruct := map[string]interface{}{
+// "name": "Iris",
+// "age": 2,
+// }
+// e.GET("/custom/struct").Expect().Status(iris.StatusOK).
+// JSON().Equal(expectedResultFromCustomStruct)
+// e.GET("/custom/struct/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
+// JSON().Equal(expectedResultFromCustomStruct)
+// e.GET("/custom/struct/with/content/type").Expect().Status(iris.StatusOK).
+// ContentType("text/xml", "utf-8")
+// e.GET("/custom/struct/with/error").Expect().Status(iris.StatusOK).
+// JSON().Equal(expectedResultFromCustomStruct)
+// e.GET("/custom/struct/with/error").WithQuery("err", true).Expect().
+// Status(iris.StatusBadRequest). // the default status code if error is not nil
+// // the content should be not JSON it should be the status code's text
+// // it will fire the error's text
+// Body().Equal("omit return of testCustomStruct and fire error")
+// }
+
+// type testControllerViewResultRespectCtxViewData struct {
+// T *testing.T
+// mvc2.C
+// }
+
+// func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) {
+// t.C.BeginRequest(ctx)
+// ctx.ViewData("name_begin", "iris_begin")
+// }
+
+// func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) {
+// t.C.EndRequest(ctx)
+// // check if data is not overridden by return mvc.View {Data: context.Map...}
+
+// dataWritten := ctx.GetViewData()
+// if dataWritten == nil {
+// t.T.Fatalf("view data is nil, both BeginRequest and Get failed to write the data")
+// return
+// }
+
+// if dataWritten["name_begin"] == nil {
+// t.T.Fatalf(`view data[name_begin] is nil,
+// BeginRequest's ctx.ViewData call have been overridden by Get's return mvc.View {Data: }.
+// Total view data: %v`, dataWritten)
+// }
+
+// if dataWritten["name"] == nil {
+// t.T.Fatalf("view data[name] is nil, Get's return mvc.View {Data: } didn't work. Total view data: %v", dataWritten)
+// }
+// }
+
+// func (t *testControllerViewResultRespectCtxViewData) Get() mvc2.Result {
+// return mvc2.View{
+// Name: "doesnt_exists.html",
+// Data: context.Map{"name": "iris"}, // we care about this only.
+// Code: iris.StatusInternalServerError,
+// }
+// }
+
+// func TestControllerViewResultRespectCtxViewData(t *testing.T) {
+// app := iris.New()
+// app.Controller("/", new(testControllerViewResultRespectCtxViewData), t)
+// e := httptest.New(t, app)
+
+// e.GET("/").Expect().Status(iris.StatusInternalServerError)
+// }
diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go
new file mode 100644
index 00000000..5ea5f5c4
--- /dev/null
+++ b/mvc2/session_controller.go
@@ -0,0 +1,47 @@
+package mvc2
+
+import (
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/sessions"
+ "reflect"
+
+ "github.com/kataras/golog"
+)
+
+var defaultManager = sessions.New(sessions.Config{})
+
+// SessionController is a simple `Controller` implementation
+// which requires a binded session manager in order to give
+// direct access to the current client's session via its `Session` field.
+type SessionController struct {
+ C
+
+ Manager *sessions.Sessions
+ Session *sessions.Session
+}
+
+// OnActivate called, once per application lifecycle NOT request,
+// every single time the dev registers a specific SessionController-based controller.
+// It makes sure that its "Manager" field is filled
+// even if the caller didn't provide any sessions manager via the `app.Controller` function.
+func (s *SessionController) OnActivate(ca *ControllerActivator) {
+ if !ca.Engine.BindTypeExists(reflect.TypeOf(defaultManager)) {
+ ca.Engine.Bind(defaultManager)
+ golog.Warnf(`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
+therefore this controller is using the default sessions manager instead.
+Please refer to the documentation to learn how you can provide the session manager`)
+ }
+}
+
+// BeginRequest calls the Controller's BeginRequest
+// and tries to initialize the current user's Session.
+func (s *SessionController) BeginRequest(ctx context.Context) {
+ s.C.BeginRequest(ctx)
+ if s.Manager == nil {
+ ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
+because the SessionController should predict this on its activation state and use a default one automatically`)
+ return
+ }
+
+ s.Session = s.Manager.Start(ctx)
+}