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