create one generic package for dependency injection which can be used outside of Iris too - worked but unfished

Former-commit-id: a9d600321c07d7c9f39105416f14ae91528a16a3
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-14 23:04:42 +02:00
parent a5fac270cf
commit 4e15f4ea88
11 changed files with 73 additions and 419 deletions

View File

@ -1,8 +1,10 @@
package controllers package controllers
// import "github.com/kataras/iris/mvc2"
// ValuesController is the equivalent // ValuesController is the equivalent
// `ValuesController` of the .net core 2.0 mvc application. // `ValuesController` of the .net core 2.0 mvc application.
type ValuesController struct{} type ValuesController struct{} //{ mvc2.C }
/* on windows tests(older) the Get was: /* on windows tests(older) the Get was:
func (vc *ValuesController) Get() { func (vc *ValuesController) Get() {

View File

@ -46,7 +46,7 @@ func main() {
// using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool), // using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool),
// iris context implements the io.Writer by its ResponseWriter // iris context implements the io.Writer by its ResponseWriter
// which is an enhanced version of the standar http.ResponseWriter // which is an enhanced version of the standard http.ResponseWriter
// but still 100% compatible. // but still 100% compatible.
template.UserListToWriter(userList, ctx) template.UserListToWriter(userList, ctx)
}) })

View File

@ -1,258 +1,34 @@
package mvc2 package mvc2
import "reflect" import (
"github.com/kataras/di"
type bindType uint32 "reflect"
const (
objectType bindType = iota // simple assignable value.
functionResultType // dynamic value, depends on the context.
) )
type bindObject struct { var (
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' . typeChecker = func(fn reflect.Type) bool {
Value reflect.Value // invalid if that single input arg is not a typeof context.Context.
return isContext(fn.In(0))
BindType bindType
ReturnValue func(ctx []reflect.Value) reflect.Value
}
// makeReturnValue takes any function
// that accept a context and returns something
// and returns a binder function, which accepts the context as slice of reflect.Value
// and returns a reflect.Value for that.
// Iris uses to
// resolve and set the input parameters when a handler is executed.
//
// The "fn" can have the following form:
// `func(iris.Context) UserViewModel`.
//
// The return type of the "fn" 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 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. hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) {
if typ.NumOut() != 1 { if isContext(fieldOrFuncInput) {
return nil, typ, errBad return newContextBindObject(), true
}
// 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 {
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
} }
return nil, false
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
} }
)
return bf, outTyp, nil
}
func makeBindObject(v reflect.Value) (b bindObject, err error) {
if isFunc(v) {
b.BindType = functionResultType
b.ReturnValue, b.Type, err = makeReturnValue(v)
} else {
b.BindType = objectType
b.Type = v.Type()
b.Value = v
}
return
}
// newContextBindObject is being used on both targetFunc and targetStruct. // newContextBindObject is being used on both targetFunc and targetStruct.
// if the func's input argument or the struct's field is a type of Context // if the func's input argument or the struct's field is a type of Context
// then we can do a fast binding using the ctxValue // then we can do a fast binding using the ctxValue
// which is used as slice of reflect.Value, because of the final method's `Call`. // which is used as slice of reflect.Value, because of the final method's `Call`.
func newContextBindObject() *bindObject { func newContextBindObject() *di.BindObject {
return &bindObject{ return &di.BindObject{
Type: contextTyp, Type: contextTyp,
BindType: functionResultType, BindType: di.Dynamic,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value { ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
return ctxValue[0] return ctxValue[0]
}, },
} }
} }
func (b *bindObject) IsAssignable(to reflect.Type) bool {
return equalTypes(b.Type, to)
}
func (b *bindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
if b.BindType == functionResultType {
toSetter(b.ReturnValue(ctx))
return
}
toSetter(b.Value)
}
type (
targetField struct {
Object *bindObject
FieldIndex []int
}
targetFuncInput struct {
Object *bindObject
InputIndex int
}
)
type targetStruct struct {
Fields []*targetField
Valid bool // is True when contains fields and it's a valid target struct.
}
func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *targetStruct {
typ := indirectTyp(v.Type())
s := &targetStruct{}
fields := lookupFields(typ, nil)
for _, f := range fields {
// if it's context then bind it directly here and continue to the next field.
if isContext(f.Type) {
s.Fields = append(s.Fields, &targetField{
FieldIndex: f.Index,
Object: newContextBindObject(),
})
continue
}
for _, val := range bindValues {
// the binded values to the struct's fields.
b, err := makeBindObject(val)
if err != nil {
return s // if error stop here.
}
if b.IsAssignable(f.Type) {
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
s.Fields = append(s.Fields, &targetField{
FieldIndex: f.Index,
Object: &b,
})
break
}
}
}
s.Valid = len(s.Fields) > 0
return s
}
func (s *targetStruct) Fill(destElem reflect.Value, ctx ...reflect.Value) {
for _, f := range s.Fields {
f.Object.Assign(ctx, func(v reflect.Value) {
// if isContext(v.Type()) {
// println("WTF BIND CONTEXT TYPE WHEN BASE CONTROLLER?")
// }
destElem.FieldByIndex(f.FieldIndex).Set(v)
})
}
}
type targetFunc struct {
Inputs []*targetFuncInput
Valid bool // is True when contains func inputs and it's a valid target func.
}
func newTargetFunc(fn reflect.Value, bindValues ...reflect.Value) *targetFunc {
typ := indirectTyp(fn.Type())
s := &targetFunc{
Valid: false,
}
if !isFunc(typ) {
return s
}
n := typ.NumIn()
// function input can have many values of the same types,
// so keep track of them in order to not set a func input to a next bind value,
// i.e (string, string) with two different binder funcs because of the different param's name.
consumedValues := make(map[int]bool, n)
for i := 0; i < n; i++ {
inTyp := typ.In(i)
// if it's context then bind it directly here and continue to the next func's input arg.
if isContext(inTyp) {
s.Inputs = append(s.Inputs, &targetFuncInput{
InputIndex: i,
Object: newContextBindObject(),
})
continue
}
for valIdx, val := range bindValues {
if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
continue
}
inTyp := typ.In(i)
// the binded values to the func's inputs.
b, err := makeBindObject(val)
if err != nil {
return s // if error stop here.
}
if b.IsAssignable(inTyp) {
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
// i, b.Type.String(), val.String(), val.Pointer())
s.Inputs = append(s.Inputs, &targetFuncInput{
InputIndex: i,
Object: &b,
})
consumedValues[valIdx] = true
break
}
}
}
s.Valid = len(s.Inputs) > 0
return s
}
func (s *targetFunc) Fill(in *[]reflect.Value, ctx ...reflect.Value) {
args := *in
for _, input := range s.Inputs {
input.Object.Assign(ctx, func(v reflect.Value) {
// fmt.Printf("assign input index: %d for value: %v\n",
// input.InputIndex, v.String())
args[input.InputIndex] = v
})
}
*in = args
}

View File

@ -1,99 +0,0 @@
package mvc2
import (
"reflect"
)
/// TODO:
// create another package because these bindings things are useful
// for other libraries I'm working on, so something like github.com/kataras/di
// will be great, combine these with the bind.go and controller's inside handler
// but generic things.
type ValueStore []reflect.Value
// Bind binds values to this controller, if you want to share
// binding values between controllers use the Engine's `Bind` function instead.
func (bv *ValueStore) Bind(values ...interface{}) {
for _, val := range values {
bv.bind(reflect.ValueOf(val))
}
}
func (bv *ValueStore) bind(v reflect.Value) {
if !goodVal(v) {
return
}
*bv = append(*bv, v)
}
// Unbind unbinds a binding value based on the type,
// it returns true if at least one field is not binded anymore.
//
// The "n" indicates the number of elements to remove, if <=0 then it's 1,
// this is useful because you may have bind more than one value to two or more fields
// with the same type.
func (bv *ValueStore) Unbind(value interface{}, n int) bool {
return bv.unbind(reflect.TypeOf(value), n)
}
func (bv *ValueStore) unbind(typ reflect.Type, n int) (ok bool) {
input := *bv
for i, in := range input {
if equalTypes(in.Type(), typ) {
ok = true
input = input[:i+copy(input[i:], input[i+1:])]
if n > 1 {
continue
}
break
}
}
*bv = input
return
}
// BindExists returns true if a binder responsible to
// bind and return a type of "typ" is already registered to this controller.
func (bv *ValueStore) BindExists(value interface{}) bool {
return bv.bindTypeExists(reflect.TypeOf(value))
}
func (bv *ValueStore) bindTypeExists(typ reflect.Type) bool {
input := *bv
for _, in := range input {
if equalTypes(in.Type(), typ) {
return true
}
}
return false
}
// BindIfNotExists bind a value to the controller's field with the same type,
// if it's not binded already.
//
// Returns false if binded already or the value is not the proper one for binding,
// otherwise true.
func (bv *ValueStore) BindIfNotExists(value interface{}) bool {
return bv.bindIfNotExists(reflect.ValueOf(value))
}
func (bv *ValueStore) bindIfNotExists(v reflect.Value) bool {
var (
typ = v.Type() // no element, raw things here.
)
if !goodVal(v) {
return false
}
if bv.bindTypeExists(typ) {
return false
}
bv.bind(v)
return true
}

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/kataras/di"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router"
"github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro"
@ -89,17 +91,16 @@ type ControllerActivator struct {
// the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation. // the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
reservedMethods []string reservedMethods []string
// input are always empty after the `activate` // the bindings that comes from the Engine and the controller's filled fields if any.
// are used to build the bindings, and we need this field // Can be binded to the the new controller's fields and method that is fired
// because we have 3 states (Engine.Input, OnActivate, Bind) // on incoming requests.
// that we can add or override binding values. Dependencies *di.D
ValueStore // TODO: or ... this is dirty code I will have to re format it a bit tomorrow.
// the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields. // on activate.
bindings *targetStruct injector *di.StructInjector
} }
func newControllerActivator(router router.Party, controller interface{}, bindValues ...reflect.Value) *ControllerActivator { func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator {
var ( var (
val = reflect.ValueOf(controller) val = reflect.ValueOf(controller)
typ = val.Type() typ = val.Type()
@ -115,7 +116,7 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal
// the end-developer when declaring the controller, // the end-developer when declaring the controller,
// activate listeners needs them in order to know if something set-ed already or not, // activate listeners needs them in order to know if something set-ed already or not,
// look `BindTypeExists`. // look `BindTypeExists`.
bindValues = append(lookupNonZeroFieldsValues(val), bindValues...) d.Values = append(lookupNonZeroFieldsValues(val), d.Values...)
c := &ControllerActivator{ c := &ControllerActivator{
// give access to the Router to the end-devs if they need it for some reason, // give access to the Router to the end-devs if they need it for some reason,
@ -133,20 +134,22 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal
// //
// TODO: now that BaseController is totally optionally // TODO: now that BaseController is totally optionally
// we have to check if BeginRequest and EndRequest should be here. // we have to check if BeginRequest and EndRequest should be here.
reservedMethods: []string{ reservedMethods: whatReservedMethods(typ),
"BeginRequest", Dependencies: d,
"EndRequest",
"OnActivate",
},
// set the input as []reflect.Value in order to be able
// to check if a bind type is already exists, or even
// override the structBindings that are being generated later on.
ValueStore: bindValues,
} }
return c return c
} }
func whatReservedMethods(typ reflect.Type) []string {
methods := []string{"OnActivate"}
if isBaseController(typ) {
methods = append(methods, "BeginRequest", "EndRequest")
}
return methods
}
// checks if a method is already registered. // checks if a method is already registered.
func (c *ControllerActivator) isReservedMethod(name string) bool { func (c *ControllerActivator) isReservedMethod(name string) bool {
for _, s := range c.reservedMethods { for _, s := range c.reservedMethods {
@ -178,15 +181,8 @@ func (c *ControllerActivator) parseMethods() {
} }
} }
// SetBindings will override any bindings with the new "values".
func (c *ControllerActivator) SetBindings(values ...reflect.Value) {
// set field index with matching binders, if any.
c.ValueStore = values
c.bindings = newTargetStruct(c.Value, values...)
}
func (c *ControllerActivator) activate() { func (c *ControllerActivator) activate() {
c.SetBindings(c.ValueStore...) c.injector = c.Dependencies.Struct(c.Value)
c.parseMethods() c.parseMethods()
} }
@ -236,11 +232,13 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// end-dev's controller pointer. // end-dev's controller pointer.
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...) pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
// get the function's input arguments' bindings. // get the function's input arguments' bindings.
funcBindings := newTargetFunc(m.Func, pathParams...) funcDependencies := c.Dependencies.Clone()
funcDependencies.Add(pathParams...)
funcInjector := funcDependencies.Func(m.Func)
// we will make use of 'n' to make a slice of reflect.Value // we will make use of 'n' to make a slice of reflect.Value
// to pass into if the function has input arguments that // to pass into if the function has input arguments that
// are will being filled by the funcBindings. // are will being filled by the funcDependencies.
n := len(funcIn) n := len(funcIn)
// the element value, not the pointer, wil lbe used to create a // the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request. // new controller on each incoming request.
@ -249,19 +247,8 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
implementsBase := isBaseController(c.Type) implementsBase := isBaseController(c.Type)
handler := func(ctx context.Context) { handler := func(ctx context.Context) {
// create a new controller instance of that type(>ptr).
ctrl := reflect.New(elemTyp) ctrl := reflect.New(elemTyp)
// // the Interface(). is faster than MethodByName or pre-selected methods.
// b := ctrl.Interface().(BaseController)
// // init the request.
// b.BeginRequest(ctx)
// // if begin request stopped the execution.
// if ctx.IsStopped() {
// return
// }
if implementsBase { if implementsBase {
// the Interface(). is faster than MethodByName or pre-selected methods. // the Interface(). is faster than MethodByName or pre-selected methods.
b := ctrl.Interface().(BaseController) b := ctrl.Interface().(BaseController)
@ -273,34 +260,32 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return return
} }
// EndRequest will be called at any case except the `BeginRequest` is
// stopped.
defer b.EndRequest(ctx) defer b.EndRequest(ctx)
} }
if !c.bindings.Valid && !funcBindings.Valid { if !c.injector.Valid && !funcInjector.Valid {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else { } else {
ctxValue := reflect.ValueOf(ctx) ctxValue := reflect.ValueOf(ctx)
if c.bindings.Valid { if c.injector.Valid {
elem := ctrl.Elem() elem := ctrl.Elem()
c.bindings.Fill(elem, ctxValue) c.injector.InjectElem(elem, ctxValue)
if ctx.IsStopped() { if ctx.IsStopped() {
return return
} }
// we do this in order to reduce in := make... // we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args. // if not func input binders, we execute the handler with empty input args.
if !funcBindings.Valid { if !funcInjector.Valid {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} }
} }
// otherwise, it has one or more valid input binders, // otherwise, it has one or more valid input binders,
// make the input and call the func using those. // make the input and call the func using those.
if funcBindings.Valid { if funcInjector.Valid {
in := make([]reflect.Value, n, n) in := make([]reflect.Value, n, n)
in[0] = ctrl in[0] = ctrl
funcBindings.Fill(&in, ctxValue) funcInjector.Inject(&in, ctxValue)
if ctx.IsStopped() { if ctx.IsStopped() {
return return
} }
@ -309,12 +294,6 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
} }
} }
// if ctx.IsStopped() {
// return
// }
// b.EndRequest(ctx)
} }
// register the handler now. // register the handler now.

View File

@ -421,8 +421,8 @@ type testControllerActivateListener struct {
} }
func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) { func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
if !ca.BindExists(&testBindType{}) { if !ca.Dependencies.BindExists(&testBindType{}) {
ca.Bind(&testBindType{ ca.Dependencies.Bind(&testBindType{
title: "default title", title: "default title",
}) })
} }

View File

@ -2,9 +2,10 @@ package mvc2
import ( import (
"errors" "errors"
"reflect"
"github.com/kataras/di"
"github.com/kataras/golog" "github.com/kataras/golog"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router"
) )
@ -16,38 +17,28 @@ var (
) )
type Engine struct { type Engine struct {
Input []reflect.Value dependencies *di.D
} }
func New() *Engine { func New() *Engine {
return new(Engine) return &Engine{
dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
} }
func (e *Engine) Bind(values ...interface{}) *Engine { func (e *Engine) Bind(values ...interface{}) *Engine {
for _, val := range values { e.dependencies.Bind(values...)
if v := reflect.ValueOf(val); goodVal(v) {
e.Input = append(e.Input, v)
}
}
return e return e
} }
func (e *Engine) Child() *Engine { func (e *Engine) Child() *Engine {
child := New() child := New()
child.dependencies = e.dependencies.Clone()
// copy the current parent's ctx func binders and services to this new child.
if n := len(e.Input); n > 0 {
input := make([]reflect.Value, n, n)
copy(input, e.Input)
child.Input = input
}
return child return child
} }
func (e *Engine) Handler(handler interface{}) context.Handler { func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.Input...) h, err := MakeHandler(handler, e.dependencies.Values...)
if err != nil { if err != nil {
golog.Errorf("mvc handler: %v", err) golog.Errorf("mvc handler: %v", err)
} }
@ -55,7 +46,7 @@ func (e *Engine) Handler(handler interface{}) context.Handler {
} }
func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) { func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Input...) ca := newControllerActivator(router, controller, e.dependencies)
// give a priority to the "onActivate" // give a priority to the "onActivate"
// callbacks, if any. // callbacks, if any.

View File

@ -267,7 +267,7 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
func TestControllerViewResultRespectCtxViewData(t *testing.T) { func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New() app := iris.New()
New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) { New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
ca.Bind(t) ca.Dependencies.Bind(t)
}) })
e := httptest.New(t, app) e := httptest.New(t, app)

View File

@ -2,6 +2,7 @@ package mvc2
import ( import (
"fmt" "fmt"
"github.com/kataras/di"
"reflect" "reflect"
"runtime" "runtime"
@ -64,7 +65,7 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand
return h, nil return h, nil
} }
s := newTargetFunc(fn, bindValues...) s := di.MakeFuncInjector(fn, hijacker, typeChecker, bindValues...)
if !s.Valid { if !s.Valid {
pc := fn.Pointer() pc := fn.Pointer()
fpc := runtime.FuncForPC(pc) fpc := runtime.FuncForPC(pc)
@ -72,14 +73,14 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand
callerName := fpc.Name() callerName := fpc.Name()
err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s", err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s",
n, len(s.Inputs), fn.Type().String(), callerFileName, callerLineNumber, callerName) n, s.Length, fn.Type().String(), callerFileName, callerLineNumber, callerName)
return nil, err return nil, err
} }
h := func(ctx context.Context) { h := func(ctx context.Context) {
in := make([]reflect.Value, n, n) in := make([]reflect.Value, n, n)
s.Fill(&in, reflect.ValueOf(ctx)) s.Inject(&in, reflect.ValueOf(ctx))
if ctx.IsStopped() { if ctx.IsStopped() {
return return
} }

View File

@ -110,6 +110,10 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
for i, n := 0, elemTyp.NumField(); i < n; i++ { for i, n := 0, elemTyp.NumField(); i < n; i++ {
f := elemTyp.Field(i) f := elemTyp.Field(i)
if f.PkgPath != "" {
continue // skip unexported.
}
if indirectTyp(f.Type).Kind() == reflect.Struct && if indirectTyp(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) { !structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...) fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)

View File

@ -22,7 +22,7 @@ type SessionController struct {
// It makes sure that its "Manager" field is filled // It makes sure that its "Manager" field is filled
// even if the caller didn't provide any sessions manager via the `app.Controller` function. // even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) OnActivate(ca *ControllerActivator) { func (s *SessionController) OnActivate(ca *ControllerActivator) {
if didntBindManually := ca.BindIfNotExists(defaultSessionManager); didntBindManually { if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually {
ca.Router.GetReporter().Add( ca.Router.GetReporter().Add(
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, `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. therefore this controller is using the default sessions manager instead.