2012-08-29

Unity child containers + ASP MVC = Memory leak

Update 2014-12-27 : In later versions of ASP MVC framework you need to implement IAsyncController.EndExecute instead of IController.Execute

I don't tend to do it this way any more, I use the Unity ASP MVC NuGet package.

-----

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
if (controllerType == null) return null; var requestContainer = Container.CreateChildContainer();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


void IController.Execute(RequestContext context) { try { base.Execute(context); } finally { RequestUnityContainer.Dispose(); }


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


protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) return null; IUnityContainer requestContainer = Container.CreateChildContainer(); ControllerBase controller = ContainerDisposingControllerFactory.Create(requestContainer, controllerType); return controller; }


The code required is listed below…


//ContainerDisposingControllerIntf.cs using Microsoft.Practices.Unity; namespace YourNameHere.Infrastructure.Web { public interface IContainerDisposingController { IUnityContainer IContainerDisposingController_UnityContainer { get; set; } } } //ContainerDisposingControllerFactory.cs using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; using System.Web.Mvc; using Microsoft.Practices.Unity; namespace YourNameHere.Infrastructure.Web { public static class ContainerDisposingControllerFactory { const string UnityContainerBackingFieldName = "IContainerDisposingController_BackingField"; static MethodInfo DisposeMethodInfo = typeof(IDisposable).GetMethod("Dispose"); static readonly Dictionary<Type, Type> InterceptingType_BySuperClass = new Dictionary<Type, Type>(); static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); public static ControllerBase Create(IUnityContainer containerToDispose, Type controllerType) { try { return CreateTypeAndInstance(containerToDispose, controllerType); } catch { containerToDispose.Dispose(); throw; } } private static ControllerBase CreateTypeAndInstance(IUnityContainer containerToDispose, Type controllerType) { Type interceptingControllerType; Locker.EnterUpgradeableReadLock(); try { if (!InterceptingType_BySuperClass.TryGetValue(controllerType, out interceptingControllerType)) { Locker.EnterWriteLock(); try { interceptingControllerType = CreateInterceptingControllerType(controllerType); InterceptingType_BySuperClass[controllerType] = interceptingControllerType; } finally { Locker.ExitWriteLock(); } } } finally { Locker.ExitUpgradeableReadLock(); } var result = (Controller)containerToDispose.Resolve(interceptingControllerType); var resultAsIUnityContainerController = (IContainerDisposingController)result; resultAsIUnityContainerController.IContainerDisposingController_UnityContainer = containerToDispose; return result; } static Type CreateInterceptingControllerType(Type controllerType) { if (!typeof(ControllerBase).IsAssignableFrom(controllerType)) throw new ArgumentException("ControllerType does not descend from ControllerBase"); string guid = Guid.NewGuid().ToString(); var assemblyName = new AssemblyName(guid); var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(guid); var typeBuilder = moduleBuilder.DefineType( guid, TypeAttributes.Class | TypeAttributes.Public, controllerType); CreateConstructor(controllerType, typeBuilder); FieldBuilder unityContainerBackingFieldBuilder; ImplementIContainerDisposingController(typeBuilder, out unityContainerBackingFieldBuilder); ImplementIController(controllerType, typeBuilder, unityContainerBackingFieldBuilder); return typeBuilder.CreateType(); } static void CreateConstructor(Type controllerType, TypeBuilder typeBuilder) { var constructorInfo = controllerType.GetConstructors() .OrderByDescending(x => x.GetParameters().Count()) .FirstOrDefault(); if (constructorInfo == null) return; ParameterInfo[] constructorParameters = constructorInfo.GetParameters().ToArray(); Type[] parameterTypes = constructorParameters.Select(x => x.ParameterType).ToArray(); var constructorBuilder = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, parameterTypes); //Define the parameters for our new constructor for (int argumentIndex = ; argumentIndex < parameterTypes.Length; argumentIndex++) constructorBuilder.DefineParameter( argumentIndex + , constructorParameters[argumentIndex].Attributes, constructorParameters[argumentIndex].Name); var bodyGenerator = constructorBuilder.GetILGenerator(); bodyGenerator.Emit(OpCodes.Ldarg_); for (int argumentIndex = ; argumentIndex < parameterTypes.Length; argumentIndex++) bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + ); bodyGenerator.Emit(OpCodes.Call, constructorInfo); bodyGenerator.Emit(OpCodes.Ret); } static void ImplementIContainerDisposingController(TypeBuilder typeBuilder, out FieldBuilder unityContainerBackingFieldBuilder) { typeBuilder.AddInterfaceImplementation(typeof(IContainerDisposingController)); unityContainerBackingFieldBuilder = typeBuilder.DefineField( UnityContainerBackingFieldName, typeof(IUnityContainer), FieldAttributes.Private); var propertyAccessorAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual; var getterBuilder = typeBuilder.DefineMethod( "get_IContainerDisposingController_UnityContainer", propertyAccessorAttributes, typeof(IUnityContainer), Type.EmptyTypes); var getterGenerator = getterBuilder.GetILGenerator(); getterGenerator.Emit(OpCodes.Ldarg_); getterGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); getterGenerator.Emit(OpCodes.Ret); var setterBuilder = typeBuilder.DefineMethod( "set_IContainerDisposingController_UnityContainer", propertyAccessorAttributes, null, new Type[] { typeof(IUnityContainer) }); var setterGenerator = setterBuilder.GetILGenerator(); setterGenerator.Emit(OpCodes.Ldarg_); setterGenerator.Emit(OpCodes.Ldarg_); setterGenerator.Emit(OpCodes.Stfld, unityContainerBackingFieldBuilder); setterGenerator.Emit(OpCodes.Ret); } static void ImplementIController(Type controllerType, TypeBuilder typeBuilder, FieldBuilder unityContainerBackingFieldBuilder) { typeBuilder.AddInterfaceImplementation(typeof(IController)); MethodInfo baseMethod = null; Type implementingType = controllerType; do { baseMethod = implementingType.GetMethod( "Execute", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); implementingType = implementingType.BaseType; } while (baseMethod == null && implementingType != null); if (baseMethod == null) throw new NotImplementedException(controllerType.Name + " does not implement IController.Execute"); var methodParameters = baseMethod.GetParameters(); var methodBuilder = typeBuilder.DefineMethod( typeof(IController).Name + ".Execute", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.ReuseSlot | MethodAttributes.HideBySig, null, methodParameters.Select(x => x.ParameterType).ToArray()); //Define the parameters for our new constructor for (int argumentIndex = ; argumentIndex < methodParameters.Length; argumentIndex++) methodBuilder.DefineParameter( argumentIndex + , methodParameters[argumentIndex].Attributes, methodParameters[argumentIndex].Name); var bodyGenerator = methodBuilder.GetILGenerator(); bodyGenerator.BeginExceptionBlock(); bodyGenerator.Emit(OpCodes.Ldarg_); for (int argumentIndex = ; argumentIndex < methodParameters.Length; argumentIndex++) bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + ); bodyGenerator.Emit(OpCodes.Call, baseMethod); bodyGenerator.BeginFinallyBlock(); bodyGenerator.Emit(OpCodes.Ldarg_); bodyGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); bodyGenerator.Emit(OpCodes.Call, DisposeMethodInfo); bodyGenerator.EndExceptionBlock(); bodyGenerator.Emit(OpCodes.Ret); typeBuilder.DefineMethodOverride(methodBuilder, baseMethod); } } }

ECO atomic operations

I’ve just created a simple class which I thought I’d share.

Account1.Balance += 10;
Account2.Balance
-= 10;


In this case we might expect the second line to throw an exception if the adjustment is not permitted, but the first line has already executed.  Obviously this isn’t a problem because we simply wouldn’t update the database, but sometimes you want an operation to occur in memory as an atomic operation; like this



using (var atomicOperation = new AtomicOperation(EcoSpace))
{
Account1.Balance
+= 10;
Account2.Balance
-= 10;
atomicOperation.Commit();
}


And so that is exactly what I wrote.  The class uses the UndoService to create/merge/remove undo blocks so it is even possible to nest atomic operations.



public class AtomicOperation
{
readonly IEcoServiceProvider EcoServiceProvider;
readonly IUndoService UndoService;
readonly IUnitOfWorkValidator UnitOfWorkValidator;
bool IsActive;
string UndoBlockName;

public AtomicOperation(IUnitOfWork unitOfWork, IUnitOfWorkValidator unitOfWorkValidator)
{
if (unitOfWork == null)
throw new ArgumentNullException("UnitOfWork");
if (unitOfWorkValidator == null)
throw new ArgumentNullException("UnitOfWorkValidator");

this.UndoBlockName = Guid.NewGuid().ToString();
this.EcoServiceProvider = unitOfWork.ObjectSpace;
this.UndoService = EcoServiceProvider.Resolve<IUndoService>();
this.UnitOfWorkValidator = unitOfWorkValidator;
this.UndoService.StartUndoBlock(this.UndoBlockName);
this.IsActive = true;
}

public bool Commit(List<ValidationError> validationErrors, IEcoObject rootObjectToValidate, PathStack currentPath = null)
{
if (validationErrors == null)
throw new ArgumentNullException("ValidationErrors");
if (currentPath == null)
throw new ArgumentNullException("CurrentPath");

int index = UndoService.UndoList.IndexOf(UndoBlockName);
if (index == -1)
throw new InvalidOperationException("Undo block not found");
EnsureIsLatestAtomicOperation();

IUndoBlock undoBlock
= UndoService.UndoList[index];
if (rootObjectToValidate != null)
UnitOfWorkValidator.EnsureObjectIsValidated(rootObjectToValidate);

var newErrors
= new List<ValidationError>();
newErrors.AddRange(UnitOfWorkValidator.Validate(
this, currentPath));
validationErrors.AddRange(newErrors);

if (index > 0)
{
string nameOfBlockAbove = UndoService.UndoList[index - 1].Name;
UndoService.UndoList.MergeBlocks(nameOfBlockAbove, UndoBlockName);
}
RemoveUndoBlocks();

IsActive
= false;
return newErrors.Count == 0;
}

public void RollBack()
{
if (!IsActive)
throw new InvalidOperationException("AtomicOperation has already been committed or rolled back");

IUndoBlock undoBlock
= UndoService.UndoList[UndoBlockName];
if (undoBlock == null)
throw new InvalidOperationException("Undo block not found");

UndoService.UndoBlock(UndoBlockName);
RemoveUndoBlocks();
IsActive
= false;
}

void IDisposable.Dispose()
{
GC.SuppressFinalize(
this);
if (IsActive)
RollBack();
}

~AtomicOperation()
{
throw new InvalidOperationException("AtomicOperation was not disposed");
}

public IEnumerable<Eco.ObjectRepresentation.IObject> GetModifiedObjects()
{
var undoBlock
= UndoService.UndoList[UndoBlockName];
Eco.ObjectRepresentation.IObjectList result
= undoBlock.GetChangedObjects();
Contract.Assume(result
!= null);
return result;
}

void RemoveUndoBlocks()
{
if (UndoService.UndoList.IndexOf(UndoBlockName) > -1)
UndoService.UndoList.RemoveBlock(UndoBlockName);
if (UndoService.RedoList.IndexOf(UndoBlockName) > -1)
UndoService.RedoList.RemoveBlock(UndoBlockName);
}

void EnsureIsLatestAtomicOperation()
{
int undoBlockIndex = UndoService.UndoList.IndexOf(UndoBlockName);
if (undoBlockIndex != -1 && undoBlockIndex != UndoService.UndoList.Count - 1)
throw new InvalidOperationException("AtomicOperation contains child atomic operations");
}
}
}

ASP MVC CheckListBox

I needed to present the user with a list of objects from which they could select multiple items.  There is a MultiSelectList class in ASP MVC so I looked into how to use that.  It would seem that to use this class we need to use Html.ListBox.  I think this is a poor choice because it requires the user to hold down the Control key to select additional options, and it is too easy to deselect all of your values accidentally by clicking the control accidentally without the Control key held down.

What I really wanted was something like a CheckListBox, a list of items with a check box next to them, so that’s what I have implemented.  Here is an example of how to set up the view data for my CheckListBox extension.

public ActionResult Index()
{
var availableItems
= new List<MyItem>();
availableItems.Add(
new MyItem("A", "One"));
availableItems.Add(
new MyItem("B", "Two"));
availableItems.Add(
new MyItem("C", "Three"));
availableItems.Add(
new MyItem("D", "Four"));
var selectedItems
= availableItems.Skip(1).Take(2);
ViewData[
"Items"] = CheckListBoxItems.Create(
availableItems,
x
=> x.Code,
x
=> x.Name,
x
=> selectedItems.Contains(x));
return View();
}

[HttpPost]
public ActionResult Index(CheckListBoxItems items)
{
return Index();
}


Make sure that when your application starts you register the custom binder for CheckListBoxItems



protected void Application_Start()
{
...
ModelBinders.Binders.Add(
typeof(CheckListBoxItems),
new CheckListBoxItemsModelBinder());
}


Here is the first example of how you can mark up your view html.



<%: Html.CheckListBox("Items", (CheckListBoxItems)ViewData["Items"]) %>
<!-- Outputs the following HTML
<input type="hidden" name="Items[A].Key" value="A"/>
<input type="checkbox" name="Items[A].Selected" value="true" />&nbsp;One<br/>
<input type="hidden" name="Items[B].Key" value="B"/>
<input type="checkbox" name="Items[B].Selected" value="true" checked />&nbsp;Two<br/>
<input type="hidden" name="Items[C].Key" value="C"/>
<input type="checkbox" name="Items[C].Selected" value="true" checked />&nbsp;Three<br/>
<input type="hidden" name="Items[D].Key" value="D"/>
<input type="checkbox" name="Items[D].Selected" value="true" />&nbsp;Four<br/>
-->


And here is another example where you can pass in the HTML to use for each item in the list.  The HTML is just a string used in String.Format where {0} is the check box html and {1} is where the text will be displayed.  In the following example I pass the parameters in in the order 1,0 because I want the text first followed by the check box control.



<table>
<tr>
<th>Item</th>
<th>Selected</th>
</tr>
<%: Html.CheckListBox(
"Items",
(CheckListBoxItems)ViewData[
"Items"],
"<tr><td>{1}</td><td>{0}</td></tr>") %>
</table>
<!-- Outputs the following HTML
<table>
<tr>
<th>Item</th>
<th>Selected</th>
</tr>
<tr>
<td>One</td>
<td>
<input type="hidden" name="Items[A].Key" value="A"/>
<input type="checkbox" name="Items[A].Selected" value="true" />
</td>
</tr>
<tr>
<td>Two</td>
<td>
<input type="hidden" name="Items[B].Key" value="B"/>
<input type="checkbox" name="Items[B].Selected" value="true" checked />
</td>
</tr>
<tr>
<td>Three</td>
<td>
<input type="hidden" name="Items[C].Key" value="C"/>
<input type="checkbox" name="Items[C].Selected" value="true" checked />
</td>
</tr>
<tr>
<td>Four</td>
<td>
<input type="hidden" name="Items[D].Key" value="D"/>
<input type="checkbox" name="Items[D].Selected" value="true" />
</td>
</tr>
</table>
-->


Here is the source code:



//CheckListBoxItem.cs
using System.Collections.Generic;

namespace System.Web.Mvc
{
public class CheckListBoxItem
{
public string Key { get; set; }
public string Text { get; set; }
public bool Selected { get; set; }
}

public class CheckListBoxItems : List
<CheckListBoxItem>
{
public static CheckListBoxItems Create
<T>(
IEnumerable
<T> source,
Func
<T, string> key,
Func
<T, string> text,
Func
<T, bool> selected)
{
var result = new CheckListBoxItems();
foreach (T item in source)
{
CheckListBoxItem newItem = new CheckListBoxItem();
result.Add(newItem);
newItem.Key = key(item);
newItem.Text = text(item);
newItem.Selected = selected(item);
}
return result;
}
}
}

//CheckListBoxItems.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc.Html
{
public static class CheckListBoxExtensions
{
public static MvcHtmlString CheckListBox(this HtmlHelper htmlHelper, string name, IEnumerable
<CheckListBoxItem> items)
{
return htmlHelper.CheckListBox(name, items, "{0}
&nbsp;{1}<br/>");
}

public static MvcHtmlString CheckListBox(this HtmlHelper htmlHelper, string name, IEnumerable
<CheckListBoxItem> items, string itemFormat)
{
name = htmlHelper.Encode(name);
var resultBuilder = new StringBuilder();
var itemList = items.ToList();
for (int index = 0; index
< itemList.Count; index++)
{
CheckListBoxItem item
= itemList[index];
string encodedKey = htmlHelper.Encode(item.Key);
string encodedText = htmlHelper.Encode(item.Text);
string keyHtml =
string.Format("<input
type=\"hidden\" name=\"{0}[{1}].Key\" value=\"{2}\"/>", name, encodedKey, encodedKey);
string checkBoxHtml =
string.Format(
"
<input type=\"checkbox\" name=\"{0}[{1}].Selected\" value=\"true\" {2} />",
name, encodedKey, item.Selected ? "checked" : "");
resultBuilder.AppendFormat(itemFormat, keyHtml + checkBoxHtml, encodedText);
}
return MvcHtmlString.Create(resultBuilder.ToString());
}
}
}

//CheckListBoxItemsModelBinder.cs
using System.Linq;
using System.Text.RegularExpressions;

namespace System.Web.Mvc
{
public class CheckListBoxItemsModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = new CheckListBoxItems();
string modelName = bindingContext.ModelName;
string regexKeyPattern = "^" + modelName + @"\[.+?\]\.Key$";
var keyRegex = new Regex(regexKeyPattern, RegexOptions.IgnoreCase);
var keys = controllerContext.RequestContext.HttpContext.Request.Form.AllKeys.Where(x => keyRegex.IsMatch(x));
foreach (string key in keys)
{
var valueSubmittedForKey = bindingContext.ValueProvider.GetValue(key);
bindingContext.ModelState.SetModelValue(key, valueSubmittedForKey);

string valueKey = key.Substring(0, key.Length - 4) + ".Selected";
var valueSubmittedForValueKey = bindingContext.ValueProvider.GetValue(valueKey);
bindingContext.ModelState.SetModelValue(valueKey, valueSubmittedForValueKey);

var checkListBoxItem = new CheckListBoxItem();
result.Add(checkListBoxItem);
checkListBoxItem.Key = valueSubmittedForKey.AttemptedValue;
checkListBoxItem.Selected = valueSubmittedForValueKey != null;
}
return result;
}
}
}

Unity.BuildUp–Ambiguous constructor

Sometimes you have no control over the instantiation of your classes and therefore cannot use Unity. For this reason the BuildUp method was added to Unity in order to either call the method marked with [InjectionMethod] or to set all properties marked with [Dependency].  Unfortunately in the latest build this is broken.  For some reason BuildUp searches for a suitable constructor even though the instance has already been created.  In my case I have two constructors both with only 1 parameter so I get an ambiguous constructor exception.

So, I created this helper method to call the InjectionMethod on the instance…

public static class UnityContainerHelper
{
public static void CallInjectionMethod(this IUnityContainer unityContainer, object instance, params ResolverOverride[] overrides)
{
if (instance == null)
throw new ArgumentNullException("Instance");

var injectionMethodInfo
= instance.GetType().GetMethods().Where(x => x.GetCustomAttributes(typeof(InjectionMethodAttribute), true).Any()).SingleOrDefault();
if (injectionMethodInfo == null)
return;
var parameters
= injectionMethodInfo.GetParameters();
if (parameters.Length == 0)
return;

var dependencies
= new object[parameters.Length];
int index = 0;
foreach (Type parameterType in parameters.Select(x => x.ParameterType))
{
dependencies[index]
= unityContainer.Resolve(parameterType, overrides);
index
++;
}
injectionMethodInfo.Invoke(instance, dependencies);
}
}

ASP MVC encoding route values

I’ve recently been using ASP MVC 2 to develop a business application.  As you may already know the ASP MVC routing system works with URLs which look like this

http://localhost/Client/Details/IBM

In standard ASPX apps the URL would look more like this

http://localhost/Client/Details.aspx?code=IBM

The first URL obviously looks much nicer than the 2nd, however it comes at a cost.  What if the “code” of your object is something like “N/A” for “Not applicable”?  You end up with a URL that looks like this

http://localhost/AbsenceCode/Details/N/A

What we really need is to have ASP MVC encode the value “N/A” as “N%2FA”.  The problem is that even if it did do this then by the time ASP MVC receives the value it has already been decoded, so we get a “400 bad request” error.  This is a real pain, because in business we can’t really tell a customer “You can only use characters A to Z, 0 to 9, underscore and minus in your codes” because they will tell you that they have used the code N/A for not-applicable for the past 20 years.

Now if the code had been after the ? in the URL (a query string value) it would get encoded/decoded and there would be no problem, but unfortunately ASP MVC won’t allow you to include ? in your routes.  So I spent today working on a solution to this and it looks like I’ve just cracked it.  It requires the following…

//1: Register a custom value provider in global.asax.cs
protected void Application_Start()
{
EncodedRouteValueProviderFactory.Register();
...
}

//2: Use the following code in your views instead of Html.ActionLink
//this will ensure that all values before the ? query string part of your
//URL are properly encoded

<%: Html.EncodedActionLink(.....) %>
//3: Use this special redirect action when redirecting from a method
return this.EncodedActionLink(.....);


Those are the only changes required to your web app.  In fact they are so simple that you should be able to change your app in seconds with a find/replace in files.



I’ll list all of my code below, there are two hacky bits.  In one place I type cast an IRoute to a Route object so that I can get its URL, and in another place I use reflection so that I can set two properties which have a protected setter.  The trick is to ensure that whenever we produce a URL in our website it encodes any special characters before the ?, we can’t use %xx so I use !xx.  In the above example N/A would become N!2fA, if the value N/A appears after the ? then it will be URL encoded as normal (N%2fA).  When the user navigates to a new page these route values are passed in this specially encoded format so they need to be converted back, so I register my own ValueProvider which looks at the current route (e.g. {controller}/{action}/{make}/{model}/{registration}) and decodes all values which appear in the route only.  This is done only once (the first time any value is requested) so N!2fA will be converted back to N/A as if we had been passed that value to start with.



I only finished working on this last night, but it seems to be working okay in my current app though.  If you spot any bugs in my source or add any missing features make sure to let me know on my gmail.com account (mrpmorris).



//EncodedActionLinkExtensions.cs
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Routing;

namespace System.Web.Mvc.Html
{
public static class EncodedActionLinkExtensions
{
public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action)
{
return htmlHelper.EncodedActionLink(linkText, action, (object)null);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName)
{
return htmlHelper.EncodedActionLink(linkText, action, controllerName, (object)null);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, object explicitRouteValues)
{
object routeValueObj;
if (!htmlHelper.ViewContext.RequestContext.RouteData.Values.TryGetValue("controller", out routeValueObj))
throw new InvalidOperationException("Could not determine controller");

string controllerName = (string)routeValueObj;
return htmlHelper.EncodedActionLink(linkText, action, controllerName, explicitRouteValues);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, object explicitRouteValues)
{
return htmlHelper.EncodedActionLink(linkText, action, controllerName, new RouteValueDictionary(explicitRouteValues));
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, RouteValueDictionary explicitRouteValues)
{
string url = EncodedUrlHelper.GenerateUrl(
htmlHelper.ViewContext.RequestContext,
controllerName, action, explicitRouteValues);
string result = string.Format("<a href=\"{0}\">{1}</a>", url, linkText);
return MvcHtmlString.Create(result);
}
}
}


//EncodedRedirectToRouteExtensions.cs
using System.Web.Routing;
namespace System.Web.Mvc
{
public static class EncodedRedirectToRouteExtensions
{
public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
(RouteValueDictionary)null //routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, object routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
new RouteValueDictionary(routeValues)
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, RouteValueDictionary routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName)
{
return controller.EncodedRedirectToAction(
actionName,
controllerName,
(RouteValueDictionary)
null //routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, object routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
controllerName,
new RouteValueDictionary(routeValues)
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, RouteValueDictionary routeValues)
{
RouteValueDictionary dictionary;
if (routeValues != null)
dictionary
= new RouteValueDictionary(routeValues);
else
dictionary
= new RouteValueDictionary();
dictionary[
"controller"] = controllerName;
dictionary[
"action"] = actionName;

var result
= new EncodedRedirectToRouteResult(dictionary);
return result;
}

}
}

//EncodedRedirectToRouteResult.cs
using System.Web.Mvc;
using System.Web.Routing;
namespace System.Web.Mvc
{
public class EncodedRedirectToRouteResult : ActionResult
{
readonly string RouteName;
readonly RouteValueDictionary RouteValues;

public EncodedRedirectToRouteResult(RouteValueDictionary routeValues)
:
this(null, routeValues)
{
}

public EncodedRedirectToRouteResult(string routeName, RouteValueDictionary routeValues)
{
RouteName
= routeName ?? "";
RouteValues
= routeValues != null ? routeValues : new RouteValueDictionary();
}

public override void ExecuteResult(ControllerContext context)
{
string url = EncodedUrlHelper.GenerateUrl(context.RequestContext, null, null, RouteValues);
context.Controller.TempData.Keep();
context.HttpContext.Response.Redirect(url,
false);
}
}
}

//EncodedRouteValueProvider.cs
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Routing;
using System.Reflection;
namespace System.Web.Mvc
{
public class EncodedRouteValueProvider : IValueProvider
{
readonly ControllerContext ControllerContext;
bool Activated = false;

public EncodedRouteValueProvider(ControllerContext controllerContext)
{
ControllerContext
= controllerContext;
}

public bool ContainsPrefix(string prefix)
{
if (!Activated)
DecodeRouteValues();
return false;
}

public ValueProviderResult GetValue(string key)
{
if (!Activated)
DecodeRouteValues();
return null;
}

void DecodeRouteValues()
{
Activated
= true;
var route
= (Route)ControllerContext.RouteData.Route;
string url = route.Url;
var keysToDecode
= new HashSet<string>();
var regex
= new Regex(@"\{.+?\}");
foreach (Match match in regex.Matches(url))
keysToDecode.Add(match.Value.Substring(
1, match.Value.Length - 2));
foreach (string key in keysToDecode)
{
object valueObj = ControllerContext.RequestContext.RouteData.Values[key];
if (valueObj == null)
continue;
string value = valueObj.ToString();
value
= UrlValueEncoderDecoder.DecodeString(value);
ControllerContext.RouteData.Values[key]
= value;
ValueProviderResult valueProviderResult
= ControllerContext.Controller.ValueProvider.GetValue(key);
if (valueProviderResult == null)
continue;
PropertyInfo attemptedValueProperty
= valueProviderResult.GetType().GetProperty("AttemptedValue");
attemptedValueProperty.SetValue(valueProviderResult, value,
null);
PropertyInfo rawValueProperty
= valueProviderResult.GetType().GetProperty("RawValue");
rawValueProperty.SetValue(valueProviderResult, value,
null);
}
}

}
}

//EncodedRouteValueProviderFactory.cs
namespace System.Web.Mvc
{
public class EncodedRouteValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new EncodedRouteValueProvider(controllerContext);
}

public static void Register()
{
ValueProviderFactories.Factories.Insert(
0, new EncodedRouteValueProviderFactory());
}
}
}

//EncodedUrlHelper.cs
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace System.Web.Routing
{
public static class EncodedUrlHelper
{
public static string GenerateUrl(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues)
{
if (requestContext == null)
throw new ArgumentNullException("RequestContext");

var newRouteValues
= RouteHelper.GetRouteValueDictionary(
requestContext, controllerName, action, explicitRouteValues);
var route
= RouteHelper.GetRoute(requestContext, controllerName, action, newRouteValues);
string url = route.Url;
//Replace the {values} in the main part of the URL with request values
var regex = new Regex(@"\{.+?\}");
url
= regex.Replace(url,
match
=>
{
string key = match.Value.Substring(1, match.Value.Length - 2);
object value;
if (!newRouteValues.TryGetValue(key, out value))
throw new ArgumentNullException("Cannot reconcile value for key: " + key);
string replaceWith;
if (value == UrlParameter.Optional)
replaceWith
= "";
else
replaceWith
= UrlValueEncoderDecoder.EncodeObject(value);
explicitRouteValues.Remove(key);
return replaceWith;
});

//2: Add additional values after the ?
explicitRouteValues.Remove("controller");
explicitRouteValues.Remove(
"action");
var urlBuilder
= new StringBuilder();
urlBuilder.Append(
"/" + url);
string separator = "?";
foreach (var kvp in explicitRouteValues)
{
if (kvp.Value != UrlParameter.Optional)
{
urlBuilder.AppendFormat(
"{0}{1}={2}", separator, kvp.Key, kvp.Value == null ? "" : HttpUtility.UrlEncode(kvp.Value.ToString()));
separator
= "&";
}
}
return urlBuilder.ToString();
}
}
}

//RouteHelper.cs
namespace System.Web.Routing
{
public static class RouteHelper
{
public static RouteValueDictionary GetRouteValueDictionary(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues)
{
var newRouteValues
= new RouteValueDictionary();
var route
= GetRoute(requestContext, controllerName, action, explicitRouteValues);
MergeValues(route.Defaults, newRouteValues);
MergeValues(requestContext.RouteData.Values, newRouteValues);
if (explicitRouteValues != null)
MergeValues(explicitRouteValues, newRouteValues);
if (controllerName != null)
newRouteValues[
"controller"] = controllerName;
if (action != null)
newRouteValues[
"action"] = action;
return newRouteValues;
}

public static Route GetRoute(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues
)
{
var routeValues
= new RouteValueDictionary(requestContext.RouteData.Values);
if (explicitRouteValues != null)
MergeValues(explicitRouteValues, routeValues);
if (controllerName != null)
routeValues[
"controller"] = controllerName;
if (action != null)
routeValues[
"action"] = action;
var virtualPath
= RouteTable.Routes.GetVirtualPath(requestContext, routeValues);
return (Route)virtualPath.Route;
}

static void MergeValues(RouteValueDictionary routeValues, RouteValueDictionary result)
{
foreach (var kvp in routeValues)
{
if (kvp.Value != null)
result[kvp.Key]
= kvp.Value;
else
{
object value;
if (!result.TryGetValue(kvp.Key, out value))
result[kvp.Key]
= null;
}
}
}
}
}

//UrlValueEncoderDecoder.cs
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace System.Web.Mvc
{
public static class UrlValueEncoderDecoder
{
static HashSet<char> ValidChars;

static UrlValueEncoderDecoder()
{
string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
ValidChars
= new HashSet<char>(chars.ToCharArray());
}

public static string EncodeObject(object value)
{
if (value == null)
return null;
return EncodeString(value.ToString());
}

public static string EncodeString(string value)
{
if (value == null)
return null;
var resultBuilder
= new StringBuilder();
foreach (char currentChar in value.ToCharArray())
if (ValidChars.Contains(currentChar))
resultBuilder.Append(currentChar);
else
{
byte[] bytes = System.Text.UnicodeEncoding.UTF8.GetBytes(currentChar.ToString());
foreach (byte currentByte in bytes)
resultBuilder.AppendFormat(
"${0:x2}", currentByte);
}
string result = resultBuilder.ToString();
//Special case, use + for spaces as it is shorter and spaces are common
return result.Replace("$20", "+");
}

public static string DecodeString(string value)
{
if (value == null)
return value;
//Special case, change + back to a space
value = value.Replace("+", " ");
var regex
= new Regex(@"\$[0-9a-fA-F]{2}");
value
= regex.Replace(value,
match
=>
{
string hexCode = match.Value.Substring(1, 2);
byte byteValue = byte.Parse(hexCode, NumberStyles.AllowHexSpecifier);
string decodedChar = System.Text.UnicodeEncoding.UTF8.GetString(new byte[] { byteValue });
return decodedChar;
});
return value;
}
}
}