I was making some speed improvements to my current ASP MVC application. One of the things I did was to change from creating a completely new IUnityContainer for each request over to creating one master (template) container with all the services registered with HierarchicalLifetimeManager. Then whenever a controller is required my ControllerFactory does this
1 if (controllerType == null)
2 return null;
3 var requestContainer = Container.CreateChildContainer();
4 return (IController) requestContainer.Resolve(controllerType);
That’s all nice and simple, however it causes a memory leak. The reason is that the parent container holds a reference to all of its children. The only way to resolve this is to Dispose of the child container. Now obviously we cannot dispose of it in the controller factory because we need to return a fully working Controller. So the next thing I tried was to dispose of the request container in IControllerFactory.ReleaseController. The problem with this is that the controller is released before the view is rendered, so if your view depends on objects which retrieve their state from some kind of object space then this is too soon to dispose of the request container. So ultimately I couldn’t find anywhere in the ASP MVC framework into which I could hook an event which gets fired after the request has finished.
To solve the problem my IControllerFactory no longer returns the controller type requested. Instead at runtime it creates a descendant class which implements IController directly and then passes the request on to the original controller class. The type returned stores a reference to the per-request IUnityContainer and then does this
1 void IController.Execute(RequestContext context)
2 {
3 try
4 {
5 base.Execute(context);
6 }
7 finally
8 {
9 RequestUnityContainer.Dispose();
10 }
11 }
So now we have a bit of code in our controller which disposes of the per-request unity container after the entire request has been processed. The ControllerFactory code only needs to be changed like so
1 protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
2 {
3 if (controllerType == null)
4 return null;
5 IUnityContainer requestContainer = Container.CreateChildContainer();
6 ControllerBase controller = ContainerDisposingControllerFactory.Create(requestContainer, controllerType);
7 return controller;
8 }
1 //ContainerDisposingControllerIntf.cs
2 using Microsoft.Practices.Unity;
3
4 namespace YourNameHere.Infrastructure.Web
5 {
6 public interface IContainerDisposingController
7 {
8 IUnityContainer IContainerDisposingController_UnityContainer { get; set; }
9 }
10 }
11
12
13 //ContainerDisposingControllerFactory.cs
14 using System;
15 using System.Collections.Generic;
16 using System.Linq;
17 using System.Reflection;
18 using System.Reflection.Emit;
19 using System.Threading;
20 using System.Web.Mvc;
21 using Microsoft.Practices.Unity;
22
23 namespace YourNameHere.Infrastructure.Web
24 {
25 public static class ContainerDisposingControllerFactory
26 {
27 const string UnityContainerBackingFieldName = "IContainerDisposingController_BackingField";
28 static MethodInfo DisposeMethodInfo = typeof(IDisposable).GetMethod("Dispose");
29 static readonly Dictionary<Type, Type> InterceptingType_BySuperClass = new Dictionary<Type, Type>();
30 static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
31
32 public static ControllerBase Create(IUnityContainer containerToDispose, Type controllerType)
33 {
34 try
35 {
36 return CreateTypeAndInstance(containerToDispose, controllerType);
37 }
38 catch
39 {
40 containerToDispose.Dispose();
41 throw;
42 }
43 }
44
45 private static ControllerBase CreateTypeAndInstance(IUnityContainer containerToDispose, Type controllerType)
46 {
47 Type interceptingControllerType;
48 Locker.EnterUpgradeableReadLock();
49 try
50 {
51 if (!InterceptingType_BySuperClass.TryGetValue(controllerType, out interceptingControllerType))
52 {
53 Locker.EnterWriteLock();
54 try
55 {
56 interceptingControllerType = CreateInterceptingControllerType(controllerType);
57 InterceptingType_BySuperClass[controllerType] = interceptingControllerType;
58 }
59 finally
60 {
61 Locker.ExitWriteLock();
62 }
63 }
64 }
65 finally
66 {
67 Locker.ExitUpgradeableReadLock();
68 }
69
70 var result = (Controller)containerToDispose.Resolve(interceptingControllerType);
71 var resultAsIUnityContainerController = (IContainerDisposingController)result;
72 resultAsIUnityContainerController.IContainerDisposingController_UnityContainer = containerToDispose;
73 return result;
74 }
75
76 static Type CreateInterceptingControllerType(Type controllerType)
77 {
78 if (!typeof(ControllerBase).IsAssignableFrom(controllerType))
79 throw new ArgumentException("ControllerType does not descend from ControllerBase");
80
81 string guid = Guid.NewGuid().ToString();
82 var assemblyName = new AssemblyName(guid);
83 var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
84 assemblyName,
85 AssemblyBuilderAccess.Run);
86 var moduleBuilder = assemblyBuilder.DefineDynamicModule(guid);
87 var typeBuilder = moduleBuilder.DefineType(
88 guid,
89 TypeAttributes.Class | TypeAttributes.Public,
90 controllerType);
91 CreateConstructor(controllerType, typeBuilder);
92 FieldBuilder unityContainerBackingFieldBuilder;
93 ImplementIContainerDisposingController(typeBuilder, out unityContainerBackingFieldBuilder);
94 ImplementIController(controllerType, typeBuilder, unityContainerBackingFieldBuilder);
95 return typeBuilder.CreateType();
96 }
97
98 static void CreateConstructor(Type controllerType, TypeBuilder typeBuilder)
99 {
100 var constructorInfo = controllerType.GetConstructors()
101 .OrderByDescending(x => x.GetParameters().Count())
102 .FirstOrDefault();
103 if (constructorInfo == null)
104 return;
105 ParameterInfo[] constructorParameters =
106 constructorInfo.GetParameters().ToArray();
107 Type[] parameterTypes = constructorParameters.Select(x => x.ParameterType).ToArray();
108
109 var constructorBuilder = typeBuilder.DefineConstructor(
110 MethodAttributes.Public,
111 CallingConventions.Standard,
112 parameterTypes);
113
114 //Define the parameters for our new constructor
115 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++)
116 constructorBuilder.DefineParameter(
117 argumentIndex + 1,
118 constructorParameters[argumentIndex].Attributes,
119 constructorParameters[argumentIndex].Name);
120
121 var bodyGenerator = constructorBuilder.GetILGenerator();
122 bodyGenerator.Emit(OpCodes.Ldarg_0);
123 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++)
124 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1);
125 bodyGenerator.Emit(OpCodes.Call, constructorInfo);
126 bodyGenerator.Emit(OpCodes.Ret);
127 }
128
129 static void ImplementIContainerDisposingController(TypeBuilder typeBuilder, out FieldBuilder unityContainerBackingFieldBuilder)
130 {
131 typeBuilder.AddInterfaceImplementation(typeof(IContainerDisposingController));
132 unityContainerBackingFieldBuilder = typeBuilder.DefineField(
133 UnityContainerBackingFieldName,
134 typeof(IUnityContainer),
135 FieldAttributes.Private);
136
137 var propertyAccessorAttributes =
138 MethodAttributes.Public | MethodAttributes.SpecialName |
139 MethodAttributes.HideBySig | MethodAttributes.Virtual;
140
141 var getterBuilder = typeBuilder.DefineMethod(
142 "get_IContainerDisposingController_UnityContainer",
143 propertyAccessorAttributes,
144 typeof(IUnityContainer),
145 Type.EmptyTypes);
146 var getterGenerator = getterBuilder.GetILGenerator();
147 getterGenerator.Emit(OpCodes.Ldarg_0);
148 getterGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder);
149 getterGenerator.Emit(OpCodes.Ret);
150
151 var setterBuilder = typeBuilder.DefineMethod(
152 "set_IContainerDisposingController_UnityContainer",
153 propertyAccessorAttributes,
154 null,
155 new Type[] { typeof(IUnityContainer) });
156 var setterGenerator = setterBuilder.GetILGenerator();
157 setterGenerator.Emit(OpCodes.Ldarg_0);
158 setterGenerator.Emit(OpCodes.Ldarg_1);
159 setterGenerator.Emit(OpCodes.Stfld, unityContainerBackingFieldBuilder);
160 setterGenerator.Emit(OpCodes.Ret);
161 }
162
163 static void ImplementIController(Type controllerType, TypeBuilder typeBuilder, FieldBuilder unityContainerBackingFieldBuilder)
164 {
165 typeBuilder.AddInterfaceImplementation(typeof(IController));
166 MethodInfo baseMethod = null;
167 Type implementingType = controllerType;
168 do
169 {
170 baseMethod = implementingType.GetMethod(
171 "Execute",
172 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
173 implementingType = implementingType.BaseType;
174 }
175 while (baseMethod == null && implementingType != null);
176 if (baseMethod == null)
177 throw new NotImplementedException(controllerType.Name + " does not implement IController.Execute");
178
179 var methodParameters = baseMethod.GetParameters();
180 var methodBuilder = typeBuilder.DefineMethod(
181 typeof(IController).Name + ".Execute",
182 MethodAttributes.Public | MethodAttributes.Virtual |
183 MethodAttributes.ReuseSlot | MethodAttributes.HideBySig,
184 null,
185 methodParameters.Select(x => x.ParameterType).ToArray());
186 //Define the parameters for our new constructor
187 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++)
188 methodBuilder.DefineParameter(
189 argumentIndex + 1,
190 methodParameters[argumentIndex].Attributes,
191 methodParameters[argumentIndex].Name);
192
193 var bodyGenerator = methodBuilder.GetILGenerator();
194 bodyGenerator.BeginExceptionBlock();
195 bodyGenerator.Emit(OpCodes.Ldarg_0);
196 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++)
197 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1);
198 bodyGenerator.Emit(OpCodes.Call, baseMethod);
199 bodyGenerator.BeginFinallyBlock();
200 bodyGenerator.Emit(OpCodes.Ldarg_0);
201 bodyGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder);
202 bodyGenerator.Emit(OpCodes.Call, DisposeMethodInfo);
203 bodyGenerator.EndExceptionBlock();
204 bodyGenerator.Emit(OpCodes.Ret);
205 typeBuilder.DefineMethodOverride(methodBuilder, baseMethod);
206 }
207 }
208 }