Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit a045324

Browse files
committed
Do not include compiler-generated names in expression names
- #2890 - add lots of `ExpressionHelper` tests using `IdFor()` and `NameFor()` (which are thin veneers)
1 parent 8babf2b commit a045324

File tree

2 files changed

+132
-15
lines changed

2 files changed

+132
-15
lines changed

src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Expressions/ExpressionHelper.cs

+19-4
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ public static string GetExpressionText([NotNull] LambdaExpression expression)
3131
if (part.NodeType == ExpressionType.Call)
3232
{
3333
var methodExpression = (MethodCallExpression)part;
34-
3534
if (!IsSingleArgumentIndexer(methodExpression))
3635
{
36+
// Unsupported.
3737
break;
3838
}
3939

@@ -58,7 +58,18 @@ public static string GetExpressionText([NotNull] LambdaExpression expression)
5858
else if (part.NodeType == ExpressionType.MemberAccess)
5959
{
6060
var memberExpressionPart = (MemberExpression)part;
61-
nameParts.Push("." + memberExpressionPart.Member.Name);
61+
var name = memberExpressionPart.Member.Name;
62+
63+
// If identifier contains "__", it is "reserved for use by the implementation" and likely compiler-
64+
// or Razor-generated e.g. the name of a field in a delegate's generated class.
65+
if (name.Contains("__"))
66+
{
67+
// Exit loop. Should have the entire name because previous MemberAccess has same name as the
68+
// leftmost expression node (a variable).
69+
break;
70+
}
71+
72+
nameParts.Push("." + name);
6273
part = memberExpressionPart.Expression;
6374
}
6475
else if (part.NodeType == ExpressionType.Parameter)
@@ -67,15 +78,19 @@ public static string GetExpressionText([NotNull] LambdaExpression expression)
6778
// string onto the stack and stop evaluating. The extra empty string makes sure that
6879
// we don't accidentally cut off too much of m => m.Model.
6980
nameParts.Push(string.Empty);
70-
part = null;
81+
82+
// Exit loop. Have the entire name because the parameter cannot be used as an indexer; always the
83+
// leftmost expression node.
84+
break;
7185
}
7286
else
7387
{
88+
// Unsupported.
7489
break;
7590
}
7691
}
7792

78-
// If it starts with "model", then strip that away
93+
// If parts start with "model", then strip that part away.
7994
if (nameParts.Count > 0 && string.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase))
8095
{
8196
nameParts.Pop();

test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperNameExtensionsTest.cs

+113-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
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;
5+
using System.Collections.Generic;
6+
using System.Linq.Expressions;
47
using Microsoft.AspNet.Mvc.ModelBinding;
58
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
69
using Microsoft.AspNet.Mvc.Rendering;
10+
using Microsoft.AspNet.Testing;
711
using Moq;
812
using Xunit;
913

@@ -14,6 +18,26 @@ namespace Microsoft.AspNet.Mvc.Core
1418
/// </summary>
1519
public class HtmlHelperNameExtensionsTest
1620
{
21+
private static List<string> _staticCollection = new List<string>();
22+
private static int _staticIndex = 6;
23+
24+
private List<string> _collection = new List<string>();
25+
private int _index = 7;
26+
private List<string[]> _nestedCollection = new List<string[]>();
27+
private string _string = string.Empty;
28+
29+
private static List<string> StaticCollection { get; }
30+
31+
private static int StaticIndex { get; } = 8;
32+
33+
private List<string> Collection { get; }
34+
35+
private int Index { get; } = 9;
36+
37+
private List<string[]> NestedCollection { get; }
38+
39+
private string StringProperty { get; }
40+
1741
[Fact]
1842
public void IdAndNameHelpers_ReturnEmptyForModel()
1943
{
@@ -140,7 +164,7 @@ public void IdAndNameHelpers_DoNotConsultMetadataOrMetadataProvider()
140164
// Arrange
141165
var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
142166
var metadata = new Mock<ModelMetadata>(
143-
MockBehavior.Loose,
167+
MockBehavior.Loose,
144168
ModelMetadataIdentity.ForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel)));
145169
provider
146170
.Setup(m => m.GetMetadataForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel)))
@@ -223,28 +247,106 @@ public void IdForAndNameFor_ReturnEmpty_IfExpressionUnsupported()
223247
Assert.Empty(nameResult);
224248
}
225249

226-
[Fact]
227-
public void IdForAndNameFor_ReturnVariableName()
250+
// expression, expected name, expected id
251+
public static TheoryData StaticExpressionNamesData
252+
{
253+
get
254+
{
255+
// Give expressions access to non-static fields and properties.
256+
var test = new HtmlHelperNameExtensionsTest();
257+
return test.ExpressionNamesData;
258+
}
259+
}
260+
261+
// expression, expected name, expected id
262+
private TheoryData ExpressionNamesData
263+
{
264+
get
265+
{
266+
var collection = new List<string>();
267+
var nestedCollection = new List<string[]>();
268+
var index1 = 5;
269+
var index2 = 23;
270+
var unknownKey = "this is a dummy parameter value";
271+
272+
return new TheoryData<Expression<Func<List<OuterClass>, string>>, string, string>
273+
{
274+
{ m => unknownKey, "unknownKey", "unknownKey" },
275+
{ m => collection[index1], "collection[5]", "collection_5_" },
276+
{ m => nestedCollection[index1][23], "nestedCollection[5][23]", "nestedCollection_5__23_" },
277+
{ m => nestedCollection[index1][index2], "nestedCollection[5][23]", "nestedCollection_5__23_" },
278+
{ m => nestedCollection[_index][Index], "nestedCollection[7][9]", "nestedCollection_7__9_" },
279+
{ m => nestedCollection[Index][StaticIndex], "nestedCollection[9][8]", "nestedCollection_9__8_" },
280+
{ m => _string, "_string", "zstring" },
281+
{ m => _collection[_index], "_collection[7]", "zcollection_7_" },
282+
{ m => _nestedCollection[_index][23], "_nestedCollection[7][23]", "znestedCollection_7__23_" },
283+
{ m => _nestedCollection[_index][index2], "_nestedCollection[7][23]", "znestedCollection_7__23_" },
284+
{ m => _nestedCollection[Index][_staticIndex], "_nestedCollection[9][6]", "znestedCollection_9__6_" },
285+
{ m => _nestedCollection[StaticIndex][_index], "_nestedCollection[8][7]", "znestedCollection_8__7_" },
286+
{ m => StringProperty, "StringProperty", "StringProperty" },
287+
{ m => Collection[Index], "Collection[9]", "Collection_9_" },
288+
{ m => NestedCollection[Index][23], "NestedCollection[9][23]", "NestedCollection_9__23_" },
289+
{ m => NestedCollection[Index][index2], "NestedCollection[9][23]", "NestedCollection_9__23_" },
290+
{ m => NestedCollection[_index][Index], "NestedCollection[7][9]", "NestedCollection_7__9_" },
291+
{ m => NestedCollection[Index][StaticIndex], "NestedCollection[9][8]", "NestedCollection_9__8_" },
292+
{ m => _staticCollection[_staticIndex], "_staticCollection[6]", "zstaticCollection_6_" },
293+
{ m => _staticCollection[Index], "_staticCollection[9]", "zstaticCollection_9_" },
294+
{ m => _staticCollection[_index], "_staticCollection[7]", "zstaticCollection_7_" },
295+
{ m => StaticCollection[StaticIndex], "StaticCollection[8]", "StaticCollection_8_" },
296+
{ m => StaticCollection[_staticIndex], "StaticCollection[6]", "StaticCollection_6_" },
297+
{ m => StaticCollection[index1], "StaticCollection[5]", "StaticCollection_5_" },
298+
{ m => m[index1].Inner.Name, "[5].Inner.Name", "z5__Inner_Name" },
299+
{ m => m[_staticIndex].Inner.Name, "[6].Inner.Name", "z6__Inner_Name" },
300+
{ m => m[_index].Inner.Name, "[7].Inner.Name", "z7__Inner_Name" },
301+
{ m => m[StaticIndex].Inner.Name, "[8].Inner.Name", "z8__Inner_Name" },
302+
{ m => m[Index].Inner.Name, "[9].Inner.Name", "z9__Inner_Name" },
303+
};
304+
}
305+
}
306+
307+
[Theory]
308+
[MemberData(nameof(StaticExpressionNamesData))]
309+
public void IdForAndNameFor_ReturnExpectedValues_WithVariablesInExpression(
310+
Expression<Func<List<OuterClass>, string>> expression,
311+
string expectedName,
312+
string expectedId)
228313
{
229314
// Arrange
230-
var unknownKey = "this is a dummy parameter value";
231-
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
315+
var model = new List<OuterClass>();
316+
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
232317

233318
// Act
234-
var idResult = helper.IdFor(model => unknownKey);
235-
var nameResult = helper.NameFor(model => unknownKey);
319+
var idResult = helper.IdFor(expression);
320+
var nameResult = helper.NameFor(expression);
236321

237322
// Assert
238-
Assert.Equal("unknownKey", idResult);
239-
Assert.Equal("unknownKey", nameResult);
323+
Assert.Equal(expectedId, idResult);
324+
Assert.Equal(expectedName, nameResult);
240325
}
241326

242-
private sealed class InnerClass
327+
[Fact]
328+
public void IdForAndNameFor_Throws_WhenParameterUsedAsIndexer()
329+
{
330+
// Arrange
331+
var collection = new List<string>();
332+
var index = 24;
333+
var helper = DefaultTemplatesUtilities.GetHtmlHelper(index);
334+
var message = "The expression compiler was unable to evaluate the indexer expression 'm' because it " +
335+
"references the model parameter 'm' which is unavailable.";
336+
337+
// Act & Assert
338+
ExceptionAssert.Throws<InvalidOperationException>(() => helper.IdFor(m => collection[m]), message);
339+
ExceptionAssert.Throws<InvalidOperationException>(() => helper.NameFor(m => collection[m]), message);
340+
}
341+
342+
public sealed class InnerClass
243343
{
244344
public int Id { get; set; }
345+
346+
public string Name { get; set; }
245347
}
246348

247-
private sealed class OuterClass
349+
public sealed class OuterClass
248350
{
249351
public InnerClass Inner { get; set; }
250352
}

0 commit comments

Comments
 (0)