That is easy.
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace E
{
public class PoolingControllerFactory : DefaultControllerFactory
{
private static Queue<IController> pool = new Queue<IController>();
public override IController CreateController(RequestContext
requestContext, string controllerName)
{
lock(pool)
{
if(pool.Count > 0) {
return pool.Dequeue();
}
}
Type controllerType = Type.GetType("E.Controllers." +
controllerName + "Controller");
IController controller =
(IController)Activator.CreateInstance(controllerType);
return controller;
}
public override void ReleaseController(IController controller)
{
lock(pool)
{
pool.Enqueue(controller);
}
}
}
public class TestApplication : HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.Ignore("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "Test",
action = "Index",
id = UrlParameter.Optional
});
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(PoolingControllerFactory));
}
}
}
But it does not solve your problem.
The framework test for it so it gives:
[InvalidOperationException: A single instance of controller
'E.Controllers.TestController' cannot be used to handle multiple
requests. If a custom controller factory is in use, make sure that it
creates a new instance of the controller for each request.]
:-(
And a simple attempt to just wrap the same instance in a different
delegating object does not work either.
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.Core;
using Castle.DynamicProxy;
namespace E
{
public class PoolingControllerFactory : DefaultControllerFactory
{
private static Queue<IController> pool = new Queue<IController>();
private IController Wrap(IController controller)
{
ProxyGenerator gen = new ProxyGenerator();
return
(IController)gen.CreateInterfaceProxyWithTarget(typeof(IController),
controller, new IInterceptor[0]);
}
private IController UnWrap(IController controller)
{
return (IController)ProxyUtil.GetUnproxiedInstance(controller);
}
public override IController CreateController(RequestContext
requestContext, string controllerName)
{
lock(pool)
{
if(pool.Count > 0) {
return Wrap(pool.Dequeue());
}
}
Type controllerType = Type.GetType("E.Controllers." +
controllerName + "Controller");
IController controller =
(IController)Activator.CreateInstance(controllerType);
return Wrap(controller);
}
public override void ReleaseController(IController controller)
{
lock(pool)
{
pool.Enqueue(UnWrap(controller));
}
}
}
public class TestApplication : HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.Ignore("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "Test",
action = "Index",
id = UrlParameter.Optional
});
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(PoolingControllerFactory));
}
}
}
Still gives:
[InvalidOperationException: A single instance of controller
'E.Controllers.TestController' cannot be used to handle multiple
requests. If a custom controller factory is in use, make sure that it
creates a new instance of the controller for each request.]
:-(
But then we have to startup a decompiler and we see that our
controller extend Controller that extend ControllerBase that has:
public abstract class ControllerBase : IController
{
private readonly SingleEntryGate _executeWasCalledGate = new
SingleEntryGate();
...
protected virtual void Execute(RequestContext requestContext)
{
if (requestContext == null)
throw new ArgumentNullException("requestContext");
this.VerifyExecuteCalledOnce();
this.Initialize(requestContext);
this.ExecuteCore();
}
...
internal void VerifyExecuteCalledOnce()
{
if (!this._executeWasCalledGate.TryEnter())
throw new
InvalidOperationException(string.Format((IFormatProvider)
CultureInfo.CurrentUICulture,
MvcResources.ControllerBase_CannotHandleMultipleRequests, new object[1]
{
(object) this.GetType()
}));
}
...
}
And now we know what to do:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace E
{
public class PoolingControllerFactory : DefaultControllerFactory
{
private static Queue<IController> pool = new Queue<IController>();
public override IController CreateController(RequestContext
requestContext, string controllerName)
{
lock(pool)
{
if(pool.Count > 0) {
return pool.Dequeue();
}
}
Type controllerType = Type.GetType("E.Controllers." +
controllerName + "Controller");
IController controller =
(IController)Activator.CreateInstance(controllerType);
return controller;
}
public override void ReleaseController(IController controller)
{
lock(pool)
{
// very dirty hack
Type seg =
Type.GetType("System.Web.Mvc.Async.SingleEntryGate, System.Web.Mvc");
ConstructorInfo segctor =
seg.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public, null, new Type[0], null);
typeof(ControllerBase).GetField("_executeWasCalledGate",
BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public).SetValue(controller, segctor.Invoke(null));
//
pool.Enqueue(controller);
}
}
}
public class TestApplication : HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.Ignore("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "Test",
action = "Index",
id = UrlParameter.Optional
});
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(PoolingControllerFactory));
}
}
}
Standard disclaimer:
* I have not tested the code beyond the most basic
* the pool can only grow not shrink
* all sorts of error handling is missing
* certain things are hardcoded
Specific disclaimer:
* the usage of reflection may cost more than the object
initialization that you try to avoid
* there are probably a good reason MS spend so much effort
preventing this so you may encounter serious problems
if you go this route
I will not recommend this.
Arne