During the IT Camp 2013 “MVC – Common pitfalls and how to resolve them”, Andrei Ignat, the speaker gave us some homework. His question was something like this: “How can you auto-correct misspelled actions inside the controller and redirect the user to the right action”. For example if the user types “URL/Company/Edt” instead of “URL/Company/Edit”, how can we, as software developers handle this and redirect the user to the correct action? Is Auto-correcting unknown actions so hard to do?
The answer is pretty simple. All you have to do is to override the HandleUnknownAction method inside the controller, enumerate the available actions using .NET Reflection and get the action name that “sounds like” the action the user typed.
For the “sounds like” part all you have to do is to implement the Levenshtein distance algorithm. The Levenshtein distance is a string metric used to measure the difference between two strings. The smaller the distance the better the “sounds like” part. So, here is my auto-correcting unknown actions homework.
I took the implementation for Levenshtein distance from Sten Hjelmqvist`s article on CodeProject.
You can view the article here: Fast, memory efficient Levenshtein algorithm
Below is the full source code for the Levenshtein distance implementation.
public class Levenshtein
{
/// <summary>
/// Calculates the levenstein distance between two strings
/// </summary>
public int Compute(string inputA, string inputB)
{
int aLen = inputA.Length;
int bLen = inputB.Length;
int aIdx;
int bIdx;
char a_i;
char b_j;
int cost;
/// Test string length
if (Math.Max(inputA.Length, inputB.Length) > Math.Pow(2, 31))
throw (new Exception("\nMaximum string length exceded." ));
// Step 1
if (aLen == 0)
{
return bLen;
}
if (bLen == 0)
{
return aLen;
}
/// Create the two vectors
int[] v0 = new int[aLen + 1];
int[] v1 = new int[aLen + 1];
int[] vTmp;
/// Step 2
/// Initialize the first vector
for (aIdx = 1; aIdx <= aLen; aIdx++)
{
v0[aIdx] = aIdx;
}
// Step 3
/// Fore each column
for (bIdx = 1; bIdx <= bLen; bIdx++)
{
/// Set the 0'th element to the column number
v1[0] = bIdx;
b_j = inputB[bIdx - 1];
// Step 4
/// Fore each row
for (aIdx = 1; aIdx <= aLen; aIdx++)
{
a_i = inputA[aIdx - 1];
// Step 5
if (a_i == b_j)
{
cost = 0;
}
else
{
cost = 1;
}
// Step 6
/// Find minimum
int m_min = v0[aIdx] + 1;
int b = v1[aIdx - 1] + 1;
int c = v0[aIdx - 1] + cost;
if (b < m_min)
{
m_min = b;
}
if (c < m_min)
{
m_min = c;
}
v1[aIdx] = m_min;
}
/// Swap the vectors
vTmp = v0;
v0 = v1;
v1 = vTmp;
}
// Step 7
/// Value between 0 - 100
/// 100==perfect match 0==totaly different
///
/// The vectors where swaped one last time at the end of the last loop,
/// that is why the result is now in v0 rather than in v1
System.Console.WriteLine("iDist=" + v0[aLen]);
int max = System.Math.Max(aLen, bLen);
return 100 - ((100 * v0[aLen]) / max);
}
}
After handling the “sounds like” issue we can write the actual code to correct unknown actions
protected override void HandleUnknownAction(string actionName)
{
//Use reflection to get information about the current controller
ReflectedControllerDescriptor controllerDesc
= new ReflectedControllerDescriptor(this.GetType());
//Get all action names from the current controller
IEnumerable<string> allActions = controllerDesc.GetCanonicalActions().Select(a => a.ActionName);
//Declare some variables
Levenshtein levenshtein = new Levenshtein();
string bestMatch = actionName;
int closestSoundsLike = 0, crtSoundsLike;
//Get the action that "sounds more like" the action the user has typed
foreach (string action in allActions)
{
crtSoundsLike = levenshtein.Compute(action, actionName);
if (crtSoundsLike > closestSoundsLike)
{
closestSoundsLike = crtSoundsLike;
bestMatch = action;
}
}
//Render the corrected view
this.View(bestMatch).ExecuteResult(this.ControllerContext);
}
T3ZlciBBbmQgT3V0IQ==

Be First to Comment