Skip to content

Commit b32ab42

Browse files
committed
Do not include compiler-generated names in expression names
- #117 - see also aspnet/Mvc@a045324d3a and PR aspnet/Mvc#3027, the fix for aspnet/Mvc#2890 nits: copy some improved comments from aspnet/Mvc@a045324d3a
1 parent 2b7ac74 commit b32ab42

File tree

2 files changed

+95
-4
lines changed

2 files changed

+95
-4
lines changed

src/System.Web.Mvc/ExpressionHelper.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public static string GetExpressionText(LambdaExpression expression)
3535

3636
if (!IsSingleArgumentIndexer(methodExpression))
3737
{
38+
// Unsupported
3839
break;
3940
}
4041

@@ -59,7 +60,18 @@ public static string GetExpressionText(LambdaExpression expression)
5960
else if (part.NodeType == ExpressionType.MemberAccess)
6061
{
6162
MemberExpression memberExpressionPart = (MemberExpression)part;
62-
nameParts.Push("." + memberExpressionPart.Member.Name);
63+
var name = memberExpressionPart.Member.Name;
64+
65+
// If identifier contains "__", it is "reserved for use by the implementation" and likely compiler-
66+
// or Razor-generated e.g. the name of a field in a delegate's generated class.
67+
if (name.Contains("__"))
68+
{
69+
// Exit loop. Should have the entire name because previous MemberAccess has same name as the
70+
// leftmost expression node (a variable).
71+
break;
72+
}
73+
74+
nameParts.Push("." + name);
6375
part = memberExpressionPart.Expression;
6476
}
6577
else if (part.NodeType == ExpressionType.Parameter)
@@ -69,15 +81,19 @@ public static string GetExpressionText(LambdaExpression expression)
6981
// string onto the stack and stop evaluating. The extra empty string makes sure that
7082
// we don't accidentally cut off too much of m => m.Model.
7183
nameParts.Push(String.Empty);
72-
part = null;
84+
85+
// Exit loop. Have the entire name because the parameter cannot be used as an indexer; always the
86+
// leftmost expression node.
87+
break;
7388
}
7489
else
7590
{
91+
// Unsupported
7692
break;
7793
}
7894
}
7995

80-
// If it starts with "model", then strip that away
96+
// If parts start with "model", then strip that part away.
8197
if (nameParts.Count > 0 && String.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase))
8298
{
8399
nameParts.Pop();

test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Collections.Generic;
45
using System.Linq.Expressions;
56
using Microsoft.TestCommon;
67

@@ -78,9 +79,83 @@ public void LambdaBasedExpressionTextTests()
7879
"The expression compiler was unable to evaluate the indexer expression '(s.Length - 4)' because it references the model parameter 's' which is unavailable.");
7980
}
8081

82+
public static TheoryDataSet<LambdaExpression, string> ComplicatedLambdaExpressions
83+
{
84+
get
85+
{
86+
var collection = new List<DummyModelContainer>();
87+
var index = 20;
88+
var data = new TheoryDataSet<LambdaExpression, string>
89+
{
90+
91+
{
92+
Lambda((List<DummyModelContainer> m) => collection[10].Model.FirstName),
93+
"collection[10].Model.FirstName"
94+
},
95+
{
96+
Lambda((List<DummyModelContainer> m) => m[10].Model.FirstName),
97+
"[10].Model.FirstName"
98+
},
99+
{
100+
Lambda((List<DummyModelContainer> m) => collection[index].Model.FirstName),
101+
"collection[20].Model.FirstName"
102+
},
103+
{
104+
Lambda((List<DummyModelContainer> m) => m[index].Model.FirstName),
105+
"[20].Model.FirstName"
106+
},
107+
};
108+
109+
return data;
110+
}
111+
}
112+
113+
[Theory]
114+
[PropertyData("ComplicatedLambdaExpressions")]
115+
public void GetExpressionText_WithComplicatedLambdaExpressions_ReturnsExpectedText(
116+
LambdaExpression expression,
117+
string expectedText)
118+
{
119+
// Arrange & Act
120+
var result = ExpressionHelper.GetExpressionText(expression);
121+
122+
// Assert
123+
Assert.Equal(expectedText, result);
124+
}
125+
126+
[Fact]
127+
public void GetExpressionText_WithinALoop_ReturnsExpectedText()
128+
{
129+
// Arrange 0
130+
var collection = new List<DummyModelContainer>();
131+
132+
for (var i = 0; i < 2; i++)
133+
{
134+
// Arrange 1
135+
var expectedText = string.Format("collection[{0}].Model.FirstName", i);
136+
137+
// Act 1
138+
var result = ExpressionHelper.GetExpressionText(Lambda(
139+
(List<DummyModelContainer> m) => collection[i].Model.FirstName));
140+
141+
// Assert 1
142+
Assert.Equal(expectedText, result);
143+
144+
// Arrange 2
145+
expectedText = string.Format("[{0}].Model.FirstName", i);
146+
147+
// Act 2
148+
result = ExpressionHelper.GetExpressionText(Lambda(
149+
(List<DummyModelContainer> m) => m[i].Model.FirstName));
150+
151+
// Assert 2
152+
Assert.Equal(expectedText, result);
153+
}
154+
}
155+
81156
// Helpers
82157

83-
private LambdaExpression Lambda<T1, T2>(Expression<Func<T1, T2>> expression)
158+
private static LambdaExpression Lambda<T1, T2>(Expression<Func<T1, T2>> expression)
84159
{
85160
return expression;
86161
}

0 commit comments

Comments
 (0)