diff --git a/HISTORY.md b/HISTORY.md
index 4a6b5a18..ab79cfcf 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -23,6 +23,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
Changes apply to `main` branch.
+- A new way to customize the handler's parameter among with the `hero` and `mvc` packages. New `iris.NewContextWrapper` method and `iris.DefaultContextPool` struct were added to wrap a handler and use a custom context instead of the iris.Context directly.
+
- The `cache` sub-package has an update, after 4 years:
- Add support for custom storage on `cache` package, through the `Handler#Store` method.
diff --git a/_examples/README.md b/_examples/README.md
index 7b6f22f2..10539429 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -48,6 +48,7 @@
* [Import from TOML](configuration/from-toml-file/main.go)
* [Multi Environment Configuration](configuration/multi-environments) **NEW**
* Routing
+ * [Custom Context](routing/custom-context/main.go) **HOT/NEW**
* [Party Controller](routing/party-controller) **NEW**
* [Overview](routing/overview/main.go)
* [Basic](routing/basic/main.go)
diff --git a/_examples/routing/custom-context/main.go b/_examples/routing/custom-context/main.go
new file mode 100644
index 00000000..89b9c207
--- /dev/null
+++ b/_examples/routing/custom-context/main.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+ "github.com/kataras/iris/v12"
+)
+
+func main() {
+ // 1. Create the iris app instance.
+ app := iris.New()
+
+ /*
+ w := iris.NewContextWrapper(&iris.DefaultContextPool[*myCustomContext]{
+ AcquireFunc: func(ctx iris.Context) *myCustomContext {
+ return &myCustomContext{
+ Context: ctx,
+ // custom fields here...
+ }
+ },
+ ReleaseFunc: func(t *myCustomContext) {
+ // do nothing
+ },
+ })
+ OR: */
+ // 2. Create the Context Wrapper which will be used to wrap the handlers
+ // that expect a *myCustomContext instead of iris.Context.
+ w := iris.NewContextWrapper(&myCustomContextPool{})
+
+ // 3. Register the handler(s) which expects a *myCustomContext instead of iris.Context.
+ // The `w.Handler` will wrap the handler and will call the `Acquire` and `Release`
+ // methods of the `myCustomContextPool` to get and release the *myCustomContext.
+ app.Get("/", w.Handler(index))
+
+ // 4. Start the server.
+ app.Listen(":8080")
+}
+
+func index(ctx *myCustomContext) {
+ ctx.HTML("
Hello, World!
")
+}
+
+// Create a custom context.
+type myCustomContext struct {
+ // It's just an embedded field which is set on AcquireFunc,
+ // so you can use myCustomContext with the same methods as iris.Context,
+ // override existing iris.Context's methods or add custom methods.
+ // You can use the `Context` field to access the original context.
+ iris.Context
+}
+
+func (c *myCustomContext) HTML(format string, args ...interface{}) (int, error) {
+ c.Application().Logger().Info("HTML was called from custom Context")
+
+ return c.Context.HTML(format, args...)
+}
+
+// Create the context memory pool for your custom context,
+// the pool must contain Acquire() T and Release(T) methods.
+type myCustomContextPool struct{}
+
+// Acquire returns a new custom context from the pool.
+func (p *myCustomContextPool) Acquire(ctx iris.Context) *myCustomContext {
+ return &myCustomContext{
+ Context: ctx,
+ // custom fields here...
+ }
+}
+
+// Release puts a custom context back to the pool.
+func (p *myCustomContextPool) Release(t *myCustomContext) {
+ // You can take advantage of this method to clear the context
+ // and re-use it on the Acquire method, use the sync.Pool.
+ //
+ // We do nothing for the shake of the exampel.
+}
diff --git a/context_wrapper.go b/context_wrapper.go
new file mode 100644
index 00000000..4a8236ce
--- /dev/null
+++ b/context_wrapper.go
@@ -0,0 +1,91 @@
+package iris
+
+// ContextPool is a pool of T.
+//
+// See `NewContextWrapper` and `ContextPool` for more.
+type ContextPool[T any] interface {
+ Acquire(ctx Context) T
+ Release(T)
+}
+
+// DefaultContextPool is a pool of T.
+// It's used to acquire and release T.
+// The T is acquired from the pool and released back to the pool after the handler's execution.
+// The T is passed to the handler as an argument.
+// The T is not shared between requests.
+type DefaultContextPool[T any] struct {
+ AcquireFunc func(Context) T
+ ReleaseFunc func(T)
+}
+
+// Ensure that DefaultContextPool[T] implements ContextPool[T].
+var _ ContextPool[any] = (*DefaultContextPool[any])(nil)
+
+// Acquire returns a new T from the pool's AcquireFunc.
+func (p *DefaultContextPool[T]) Acquire(ctx Context) T {
+ acquire := p.AcquireFunc
+ if p.AcquireFunc == nil {
+ acquire = func(ctx Context) T {
+ var t T
+ return t
+ }
+ }
+
+ return acquire(ctx)
+}
+
+// Release does nothing if the pool's ReleaseFunc is nil.
+func (p *DefaultContextPool[T]) Release(t T) {
+ release := p.ReleaseFunc
+ if p.ReleaseFunc == nil {
+ release = func(t T) {}
+ }
+
+ release(t)
+}
+
+// ContextWrapper is a wrapper for handlers which expect a T instead of iris.Context.
+//
+// See the `NewContextWrapper` function for more.
+type ContextWrapper[T any] struct {
+ pool ContextPool[T]
+}
+
+// NewContextWrapper returns a new ContextWrapper.
+// If pool is nil, a default pool is used.
+// The default pool's AcquireFunc returns a zero value of T.
+// The default pool's ReleaseFunc does nothing.
+// The default pool is used when the pool is nil.
+// Use the `&iris.DefaultContextPool{...}` to pass a simple context pool.
+//
+// See the `Handler` method for more.
+// Example: https://github.com/kataras/iris/tree/main/_examples/routing/custom-context
+func NewContextWrapper[T any](pool ContextPool[T]) *ContextWrapper[T] {
+ if pool == nil {
+ pool = &DefaultContextPool[T]{
+ AcquireFunc: func(ctx Context) T {
+ var t T
+ return t
+ },
+ ReleaseFunc: func(t T) {},
+ }
+ }
+
+ return &ContextWrapper[T]{
+ pool: pool,
+ }
+}
+
+// Handler wraps the handler with the pool's Acquire and Release methods.
+// It returns a new handler which expects a T instead of iris.Context.
+// The T is the type of the pool.
+// The T is acquired from the pool and released back to the pool after the handler's execution.
+// The T is passed to the handler as an argument.
+// The T is not shared between requests.
+func (w *ContextWrapper[T]) Handler(handler func(T)) Handler {
+ return func(ctx Context) {
+ newT := w.pool.Acquire(ctx)
+ handler(newT)
+ w.pool.Release(newT)
+ }
+}