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

Commit 639a788

Browse files
dougbuN. Taylor Mullen
authored and
N. Taylor Mullen
committed
Tag Helpers: add ModelExpression class to support Expression<Func<TModel, TValue>> attributes
- includes new `RazorPage<TModel>.CreateModelExpression<TValue>()` method - #1240 nit: - regenerating the resources reordered Microsoft.AspNet.Mvc.Core's Resources.designer.cs
1 parent 3290791 commit 639a788

File tree

6 files changed

+277
-12
lines changed

6 files changed

+277
-12
lines changed

src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+

2+
using System;
3+
using Microsoft.AspNet.Mvc.Core;
4+
using Microsoft.AspNet.Mvc.ModelBinding;
5+
6+
namespace Microsoft.AspNet.Mvc.Rendering
7+
{
8+
/// <summary>
9+
/// Describes an <see cref="System.Linq.Expressions.Expression"/> passed to a tag helper.
10+
/// </summary>
11+
public class ModelExpression
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="ModelExpression"/> class.
15+
/// </summary>
16+
/// <param name="name">
17+
/// String representation of the <see cref="System.Linq.Expressions.Expression"/> of interest.
18+
/// </param>
19+
/// <param name="metadata">
20+
/// Metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest.
21+
/// </param>
22+
public ModelExpression(string name, [NotNull] ModelMetadata metadata)
23+
{
24+
if (string.IsNullOrEmpty(name))
25+
{
26+
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
27+
}
28+
29+
Name = name;
30+
Metadata = metadata;
31+
}
32+
33+
/// <summary>
34+
/// String representation of the <see cref="System.Linq.Expressions.Expression"/> of interest.
35+
/// </summary>
36+
public string Name { get; private set; }
37+
38+
/// <summary>
39+
/// Metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest.
40+
/// </summary>
41+
/// <remarks>
42+
/// Getting <see cref="ModelMetadata.Model"/> will evaluate a compiled version of the original
43+
/// <see cref="System.Linq.Expressions.Expression"/>.
44+
/// </remarks>
45+
public ModelMetadata Metadata { get; private set; }
46+
}
47+
}

src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.Mvc.Razor/RazorPageOfT.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq.Expressions;
6+
using Microsoft.AspNet.Mvc.ModelBinding;
7+
using Microsoft.AspNet.Mvc.Rendering;
8+
using Microsoft.AspNet.Mvc.Rendering.Expressions;
9+
using Microsoft.Framework.DependencyInjection;
10+
411
namespace Microsoft.AspNet.Mvc.Razor
512
{
613
/// <summary>
@@ -9,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor
916
/// <typeparam name="TModel">The type of the view data model.</typeparam>
1017
public abstract class RazorPage<TModel> : RazorPage
1118
{
19+
IModelMetadataProvider _provider;
20+
1221
public TModel Model
1322
{
1423
get
@@ -19,5 +28,33 @@ public TModel Model
1928

2029
[Activate]
2130
public ViewDataDictionary<TModel> ViewData { get; set; }
31+
32+
/// <summary>
33+
/// Returns a <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.
34+
/// </summary>
35+
/// <typeparam name="TValue">The type of the <paramref name="expression"/> result.</typeparam>
36+
/// <param name="expression">An expression to be evaluated against the current model.</param>
37+
/// <returns>A new <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.
38+
/// </returns>
39+
/// <remarks>
40+
/// Compiler normally infers <typeparamref name="TValue"/> from the given <paramref name="expression"/>.
41+
/// </remarks>
42+
public ModelExpression CreateModelExpression<TValue>([NotNull] Expression<Func<TModel, TValue>> expression)
43+
{
44+
if (_provider == null)
45+
{
46+
_provider = Context.RequestServices.GetService<IModelMetadataProvider>();
47+
}
48+
49+
var name = ExpressionHelper.GetExpressionText(expression);
50+
var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, _provider);
51+
if (metadata == null)
52+
{
53+
throw new InvalidOperationException(
54+
Resources.FormatRazorPage_NullModelMetadata(nameof(IModelMetadataProvider), name));
55+
}
56+
57+
return new ModelExpression(name, metadata);
58+
}
2259
}
2360
}

src/Microsoft.AspNet.Mvc.Razor/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@
150150
<data name="RazorPage_YouCannotFlushWhileInAWritingScope" xml:space="preserve">
151151
<value>You cannot flush while inside a writing scope.</value>
152152
</data>
153+
<data name="RazorPage_NullModelMetadata" xml:space="preserve">
154+
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
155+
</data>
153156
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
154157
<value>{0} can only be called from a layout page.</value>
155158
</data>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+

2+
using System;
3+
using System.IO;
4+
using System.Linq.Expressions;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNet.Http;
7+
using Microsoft.AspNet.Mvc.ModelBinding;
8+
using Microsoft.AspNet.Mvc.Rendering;
9+
using Microsoft.AspNet.Routing;
10+
using Moq;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNet.Mvc.Razor
14+
{
15+
public class RazorPageCreateModelExpressionTest
16+
{
17+
public static TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, int>>, string> IntExpressions
18+
{
19+
get
20+
{
21+
var somethingElse = 23;
22+
return new TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, int>>, string>
23+
{
24+
{ model => somethingElse, "somethingElse" },
25+
{ model => model.Id, "Id" },
26+
{ model => model.SubModel.Id, "SubModel.Id" },
27+
{ model => model.SubModel.SubSubModel.Id, "SubModel.SubSubModel.Id" },
28+
};
29+
}
30+
}
31+
32+
public static TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, string>>, string> StringExpressions
33+
{
34+
get
35+
{
36+
var somethingElse = "This is something else";
37+
return new TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, string>>, string>
38+
{
39+
{ model => somethingElse, "somethingElse" },
40+
{ model => model.Name, "Name" },
41+
{ model => model.SubModel.Name, "SubModel.Name" },
42+
{ model => model.SubModel.SubSubModel.Name, "SubModel.SubSubModel.Name" },
43+
};
44+
}
45+
}
46+
47+
[Theory]
48+
[MemberData(nameof(IntExpressions))]
49+
public void CreateModelExpression_ReturnsExpectedMetadata_IntExpressions(
50+
Expression<Func<RazorPageCreateModelExpressionModel, int>> expression,
51+
string expectedName)
52+
{
53+
// Arrange
54+
var viewContext = CreateViewContext(model: null);
55+
var page = CreatePage(viewContext);
56+
57+
// Act
58+
var result = page.CreateModelExpression(expression);
59+
60+
// Assert
61+
Assert.NotNull(result);
62+
Assert.NotNull(result.Metadata);
63+
Assert.Equal(typeof(int), result.Metadata.ModelType);
64+
Assert.Equal(expectedName, result.Name);
65+
}
66+
67+
[Theory]
68+
[MemberData(nameof(StringExpressions))]
69+
public void CreateModelExpression_ReturnsExpectedMetadata_StringExpressions(
70+
Expression<Func<RazorPageCreateModelExpressionModel, string>> expression,
71+
string expectedName)
72+
{
73+
// Arrange
74+
var viewContext = CreateViewContext(model: null);
75+
var page = CreatePage(viewContext);
76+
77+
// Act
78+
var result = page.CreateModelExpression(expression);
79+
80+
// Assert
81+
Assert.NotNull(result);
82+
Assert.NotNull(result.Metadata);
83+
Assert.Equal(typeof(string), result.Metadata.ModelType);
84+
Assert.Equal(expectedName, result.Name);
85+
}
86+
87+
private static TestRazorPage CreatePage(ViewContext viewContext)
88+
{
89+
return new TestRazorPage
90+
{
91+
ViewContext = viewContext,
92+
ViewData = (ViewDataDictionary<RazorPageCreateModelExpressionModel>)viewContext.ViewData,
93+
};
94+
}
95+
96+
private static ViewContext CreateViewContext(RazorPageCreateModelExpressionModel model)
97+
{
98+
return CreateViewContext(model, new DataAnnotationsModelMetadataProvider());
99+
}
100+
101+
private static ViewContext CreateViewContext(
102+
RazorPageCreateModelExpressionModel model,
103+
IModelMetadataProvider provider)
104+
{
105+
var viewData = new ViewDataDictionary<RazorPageCreateModelExpressionModel>(provider)
106+
{
107+
Model = model,
108+
};
109+
110+
var serviceProvider = new Mock<IServiceProvider>();
111+
serviceProvider
112+
.Setup(real => real.GetService(typeof(IModelMetadataProvider)))
113+
.Returns(provider);
114+
115+
var httpContext = new Mock<HttpContext>();
116+
httpContext
117+
.SetupGet(real => real.RequestServices)
118+
.Returns(serviceProvider.Object);
119+
120+
var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
121+
122+
return new ViewContext(
123+
actionContext,
124+
view: Mock.Of<IView>(),
125+
viewData: viewData,
126+
writer: new StringWriter());
127+
}
128+
129+
private class TestRazorPage : RazorPage<RazorPageCreateModelExpressionModel>
130+
{
131+
public override Task ExecuteAsync()
132+
{
133+
throw new NotImplementedException();
134+
}
135+
}
136+
137+
public class RazorPageCreateModelExpressionModel
138+
{
139+
public int Id { get; set; }
140+
141+
public string Name { get; set; }
142+
143+
public RazorPageCreateModelExpressionSubModel SubModel { get; set; }
144+
}
145+
146+
public class RazorPageCreateModelExpressionSubModel
147+
{
148+
public int Id { get; set; }
149+
150+
public string Name { get; set; }
151+
152+
public RazorPageCreateModelExpressionSubSubModel SubSubModel { get; set; }
153+
}
154+
155+
public class RazorPageCreateModelExpressionSubSubModel
156+
{
157+
public int Id { get; set; }
158+
159+
public string Name { get; set; }
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)