2012-08-29

ASP MVC Silverlight control not appearing–404 not found

Just a quick tip in case you have deployed your ASP MVC app with silverlight controls in it which are not appearing.  I experienced this recently when deploying to IIS6.  The solution is Open the IIS manager app (Start->Admin->Internet Information Services (IIS) Manager) Expand the local computer node Then expand the Websites nodes Right-click your website and select “Properties” Click the “Http Headers” tab At the bottom of the page click the “MIME Types” button Click the...

Creating a pooled lifetime manager for Unity

My application has a child container per request, this is because there are a lot of services used in each request which depend upon an IUnitOfWork.  I thought it would be nice if I could define a pool of these IUnitOfWork instances so that any cost involved in creating them is reduced, they can just be reused in a round-robin fashion.  Well, more accurately, the object space (EcoSpace) on which they depend can anyway.

A pool can now be registered like so…

//Must be called once, when the container is created
container.AddNewExtension<PooledLifetimeExtension>();
//To register a pooled item
container.RegisterType
<
ISomeItemThatIsExpensiveToCreate,
SomeItemThatIsExpensiveToCreate
>(new PooledLifetimeManager());


The number 10 specifies how many instances at once may be stored in the pool.  If an instance requests an item from the pool when it is empty it will get a new instance, it’s only at the point where an instance is returned to the pool that the MaxPoolSize is honoured – if the pool is not full then the instance goes back into the pool, if the pool is already full then Dispose is called on it instead.  Of course you should be careful when pooling that your pooled items don’t hold onto other injected references which are also pooled as that will cause problems.



I have also added the following interface so that an instance can (optionally) specify whether or not its current state should permit it to go back into the pool, and two methods which indicate to the instance when it has been retrieved from a pool or is about to be returned to the pool – in my case I deactivated the object space upon returning in order to flush any object cache, and reactivated it again when retrieved from the pool.



public interface IPooledResource
{
bool CanReturnToPool { get; }
void RetrievedFromPool();
void ReturningToPool();
}


It’s important to note that this only works with child containers.  You should create your main controller as a kind of template by registering your request specific services (like IUnitOfWork) with HierarchicalLifetimeManager.  The PooledLifetimeManager will work in the same way as HierarchicalLifetimeManager except for the addition of pooling, and it will automatically attempt to return items to the pool once the child container is disposed of.



//PooledLifetimeExtension.cs
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.ObjectBuilder;

namespace YourNameHere.Infrastructure.Unity
{
public class PooledLifetimeExtension : UnityContainerExtension
{
protected override void Initialize()
{
// Add to type mapping stage so that it runs before the lifetime stage
Context.Strategies.AddNew<PooledLifetimeStrategy>
(UnityBuildStage.TypeMapping);
}
}
}



//PooledLifetimeManager.cs
using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder;

namespace YourNameHere.Infrastructure.Unity
{
public class PooledLifetimeManager : LifetimeManager
{
readonly PooledLifetimeManagerPool Pool;

public PooledLifetimeManager(int maxPoolSize)
{
Pool
= new PooledLifetimeManagerPool(maxPoolSize);
}

internal PooledLifetimeManagerPoolConsumer CreatePoolUserForChildContainer()
{
return new PooledLifetimeManagerPoolConsumer(Pool);
}

#region GetValue/SetValue/RemoveValue all redundant
public override object GetValue()
{
throw new InvalidOperationException();
}

public override void SetValue(object newValue)
{
throw new InvalidOperationException();
}

public override void RemoveValue()
{
throw new InvalidOperationException();
}
#endregion
}

internal class PooledLifetimeManagerPool
{
readonly int MaxPoolSize;
readonly object SyncRoot = new object();
readonly Queue<object> Queue = new Queue<object>();

public PooledLifetimeManagerPool(int maxPoolSize)
{
if (maxPoolSize < )
throw new ArgumentOutOfRangeException(
"MaxPoolSize", "Cannot be less than zero");
MaxPoolSize
= maxPoolSize;
}

internal object RetrieveFromPool()
{
object result = null;
lock (SyncRoot)
if (Queue.Count > )
result
= Queue.Dequeue();
IPooledResource resultAsIPooledResource
= result as IPooledResource;
if (resultAsIPooledResource != null)
resultAsIPooledResource.RetrievedFromPool();
return result;
}

internal void ReturnToPool(object instance)
{
if (instance == null)
throw new ArgumentNullException("Instance");
IPooledResource instanceAsIPooledResource
= instance as IPooledResource;
if (instanceAsIPooledResource != null)
{
if (!instanceAsIPooledResource.CanReturnToPool)
return;
instanceAsIPooledResource.ReturningToPool();
}
bool wentIntoPool = false;
lock (SyncRoot)
if (Queue.Count < MaxPoolSize)
{
Queue.Enqueue(instance);
wentIntoPool
= true;
}

//Dispose any instances which did not go back into the pool
if (!wentIntoPool && instance is IDisposable)
((IDisposable)instance).Dispose();
}
}

public class PooledLifetimeManagerPoolConsumer :
ContainerControlledLifetimeManager, IDisposable
{
readonly PooledLifetimeManagerPool SharedPool;
object Instance;

internal PooledLifetimeManagerPoolConsumer(
PooledLifetimeManagerPool sharedPool)
{
if (sharedPool == null)
throw new ArgumentNullException("SharedPool");
SharedPool
= sharedPool;
}

protected override object SynchronizedGetValue()
{
if (Instance == null)
Instance
= SharedPool.RetrieveFromPool();
return Instance;
}

protected override void SynchronizedSetValue(object newValue)
{
if (Instance != null && newValue == null)
SharedPool.ReturnToPool(Instance);
Instance
= newValue;
}

public override void RemoveValue()
{
throw new InvalidOperationException();
}

void IDisposable.Dispose()
{
SetValue(
null);
base.Dispose();
}
}
}



//PooledLifetimeStrategy.cs
using Microsoft.Practices.ObjectBuilder;

namespace YourNameHere.Infrastructure.Unity
{
public class PooledLifetimeStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
PooledLifetimeManager activeLifetime
=
context.PersistentPolicies.Get
<ILifetimePolicy>(context.BuildKey)
as PooledLifetimeManager;
if (activeLifetime != null)
{
// did this come from the local container or the parent?
var localLifetime =
context.PersistentPolicies.Get
<ILifetimePolicy>(
context.BuildKey,
true);
if (localLifetime == null)
{
// came from parent, add a ContainerControlledLifetime here
var newLifeTime = activeLifetime.CreatePoolUserForChildContainer();
context.PersistentPolicies.Set
<ILifetimePolicy>(
newLifeTime, context.BuildKey);
//Make sure it gets disposed
//so that it can return its reference to the pool
context.Lifetime.Add(newLifeTime);
}
}
}
}
}



//PooledResourceIntf.cs
namespace YourNameHere.Infrastructure.Unity
{
public interface IPooledResource
{
bool CanReturnToPool { get; }
void RetrievedFromPool();
void ReturningToPool();
}
}

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;
}
}
}