diff --git a/src/System.Web.Mvc/ExpressionHelper.cs b/src/System.Web.Mvc/ExpressionHelper.cs index 99d0282e1..17c8cf35d 100644 --- a/src/System.Web.Mvc/ExpressionHelper.cs +++ b/src/System.Web.Mvc/ExpressionHelper.cs @@ -35,6 +35,7 @@ public static string GetExpressionText(LambdaExpression expression) if (!IsSingleArgumentIndexer(methodExpression)) { + // Unsupported break; } @@ -59,7 +60,18 @@ public static string GetExpressionText(LambdaExpression expression) else if (part.NodeType == ExpressionType.MemberAccess) { MemberExpression memberExpressionPart = (MemberExpression)part; - nameParts.Push("." + memberExpressionPart.Member.Name); + var name = memberExpressionPart.Member.Name; + + // If identifier contains "__", it is "reserved for use by the implementation" and likely compiler- + // or Razor-generated e.g. the name of a field in a delegate's generated class. + if (name.Contains("__")) + { + // Exit loop. Should have the entire name because previous MemberAccess has same name as the + // leftmost expression node (a variable). + break; + } + + nameParts.Push("." + name); part = memberExpressionPart.Expression; } else if (part.NodeType == ExpressionType.Parameter) @@ -69,15 +81,19 @@ public static string GetExpressionText(LambdaExpression expression) // string onto the stack and stop evaluating. The extra empty string makes sure that // we don't accidentally cut off too much of m => m.Model. nameParts.Push(String.Empty); - part = null; + + // Exit loop. Have the entire name because the parameter cannot be used as an indexer; always the + // leftmost expression node. + break; } else { + // Unsupported break; } } - // If it starts with "model", then strip that away + // If parts start with "model", then strip that part away. if (nameParts.Count > 0 && String.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase)) { nameParts.Pop(); diff --git a/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs b/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs index 19fb92492..41dd4c120 100644 --- a/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs +++ b/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.TestCommon; @@ -78,9 +79,83 @@ public void LambdaBasedExpressionTextTests() "The expression compiler was unable to evaluate the indexer expression '(s.Length - 4)' because it references the model parameter 's' which is unavailable."); } + public static TheoryDataSet ComplicatedLambdaExpressions + { + get + { + var collection = new List(); + var index = 20; + var data = new TheoryDataSet + { + + { + Lambda((List m) => collection[10].Model.FirstName), + "collection[10].Model.FirstName" + }, + { + Lambda((List m) => m[10].Model.FirstName), + "[10].Model.FirstName" + }, + { + Lambda((List m) => collection[index].Model.FirstName), + "collection[20].Model.FirstName" + }, + { + Lambda((List m) => m[index].Model.FirstName), + "[20].Model.FirstName" + }, + }; + + return data; + } + } + + [Theory] + [PropertyData("ComplicatedLambdaExpressions")] + public void GetExpressionText_WithComplicatedLambdaExpressions_ReturnsExpectedText( + LambdaExpression expression, + string expectedText) + { + // Arrange & Act + var result = ExpressionHelper.GetExpressionText(expression); + + // Assert + Assert.Equal(expectedText, result); + } + + [Fact] + public void GetExpressionText_WithinALoop_ReturnsExpectedText() + { + // Arrange 0 + var collection = new List(); + + for (var i = 0; i < 2; i++) + { + // Arrange 1 + var expectedText = string.Format("collection[{0}].Model.FirstName", i); + + // Act 1 + var result = ExpressionHelper.GetExpressionText(Lambda( + (List m) => collection[i].Model.FirstName)); + + // Assert 1 + Assert.Equal(expectedText, result); + + // Arrange 2 + expectedText = string.Format("[{0}].Model.FirstName", i); + + // Act 2 + result = ExpressionHelper.GetExpressionText(Lambda( + (List m) => m[i].Model.FirstName)); + + // Assert 2 + Assert.Equal(expectedText, result); + } + } + // Helpers - private LambdaExpression Lambda(Expression> expression) + private static LambdaExpression Lambda(Expression> expression) { return expression; }