diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 8144638ae8..db667803ed 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1482,22 +1482,6 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("AttributeRoute_NullTemplateRepresentation");
}
- ///
- /// "The path to the file must be absolute: {0}"
- ///
- internal static string FileResult_InvalidPathType_RelativeOrVirtualPath
- {
- get { return GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"); }
- }
-
- ///
- /// "The path to the file must be absolute: {0}"
- ///
- internal static string FormatFileResult_InvalidPathType_RelativeOrVirtualPath(object p0)
- {
- return string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"), p0);
- }
-
///
/// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}
///
@@ -1514,6 +1498,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultActionSelector_AmbiguousActions"), p0, p1);
}
+ ///
+ /// "The path to the file must be absolute: {0}"
+ ///
+ internal static string FileResult_InvalidPathType_RelativeOrVirtualPath
+ {
+ get { return GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"); }
+ }
+
+ ///
+ /// "The path to the file must be absolute: {0}"
+ ///
+ internal static string FormatFileResult_InvalidPathType_RelativeOrVirtualPath(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs
new file mode 100644
index 0000000000..52d2faa068
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs
@@ -0,0 +1,47 @@
+
+using System;
+using Microsoft.AspNet.Mvc.Core;
+using Microsoft.AspNet.Mvc.ModelBinding;
+
+namespace Microsoft.AspNet.Mvc.Rendering
+{
+ ///
+ /// Describes an passed to a tag helper.
+ ///
+ public class ModelExpression
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// String representation of the of interest.
+ ///
+ ///
+ /// Metadata about the of interest.
+ ///
+ public ModelExpression(string name, [NotNull] ModelMetadata metadata)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
+ }
+
+ Name = name;
+ Metadata = metadata;
+ }
+
+ ///
+ /// String representation of the of interest.
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// Metadata about the of interest.
+ ///
+ ///
+ /// Getting will evaluate a compiled version of the original
+ /// .
+ ///
+ public ModelMetadata Metadata { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
index 848fe0a25a..b18e7753ce 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -186,6 +186,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return GetString("RazorPage_YouCannotFlushWhileInAWritingScope");
}
+ ///
+ /// The {0} was unable to provide metadata for expression '{1}'.
+ ///
+ internal static string RazorPage_NullModelMetadata
+ {
+ get { return GetString("RazorPage_NullModelMetadata"); }
+ }
+
+ ///
+ /// The {0} was unable to provide metadata for expression '{1}'.
+ ///
+ internal static string FormatRazorPage_NullModelMetadata(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_NullModelMetadata"), p0, p1);
+ }
+
///
/// {0} can only be called from a layout page.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPageOfT.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPageOfT.cs
index f895cb794c..f9c84a659d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPageOfT.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPageOfT.cs
@@ -1,6 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Linq.Expressions;
+using Microsoft.AspNet.Mvc.ModelBinding;
+using Microsoft.AspNet.Mvc.Rendering;
+using Microsoft.AspNet.Mvc.Rendering.Expressions;
+using Microsoft.Framework.DependencyInjection;
+
namespace Microsoft.AspNet.Mvc.Razor
{
///
@@ -9,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// The type of the view data model.
public abstract class RazorPage : RazorPage
{
+ IModelMetadataProvider _provider;
+
public TModel Model
{
get
@@ -19,5 +28,33 @@ namespace Microsoft.AspNet.Mvc.Razor
[Activate]
public ViewDataDictionary ViewData { get; set; }
+
+ ///
+ /// Returns a instance describing the given .
+ ///
+ /// The type of the result.
+ /// An expression to be evaluated against the current model.
+ /// A new instance describing the given .
+ ///
+ ///
+ /// Compiler normally infers from the given .
+ ///
+ public ModelExpression CreateModelExpression([NotNull] Expression> expression)
+ {
+ if (_provider == null)
+ {
+ _provider = Context.RequestServices.GetService();
+ }
+
+ var name = ExpressionHelper.GetExpressionText(expression);
+ var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, _provider);
+ if (metadata == null)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatRazorPage_NullModelMetadata(nameof(IModelMetadataProvider), name));
+ }
+
+ return new ModelExpression(name, metadata);
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index 68439f48c4..2492e79319 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -150,6 +150,9 @@
You cannot flush while inside a writing scope.
+
+ The {0} was unable to provide metadata for expression '{1}'.
+
{0} can only be called from a layout page.
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs
new file mode 100644
index 0000000000..ea9ab573f5
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs
@@ -0,0 +1,162 @@
+
+using System;
+using System.IO;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc.ModelBinding;
+using Microsoft.AspNet.Mvc.Rendering;
+using Microsoft.AspNet.Routing;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ public class RazorPageCreateModelExpressionTest
+ {
+ public static TheoryData>, string> IntExpressions
+ {
+ get
+ {
+ var somethingElse = 23;
+ return new TheoryData>, string>
+ {
+ { model => somethingElse, "somethingElse" },
+ { model => model.Id, "Id" },
+ { model => model.SubModel.Id, "SubModel.Id" },
+ { model => model.SubModel.SubSubModel.Id, "SubModel.SubSubModel.Id" },
+ };
+ }
+ }
+
+ public static TheoryData>, string> StringExpressions
+ {
+ get
+ {
+ var somethingElse = "This is something else";
+ return new TheoryData>, string>
+ {
+ { model => somethingElse, "somethingElse" },
+ { model => model.Name, "Name" },
+ { model => model.SubModel.Name, "SubModel.Name" },
+ { model => model.SubModel.SubSubModel.Name, "SubModel.SubSubModel.Name" },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(IntExpressions))]
+ public void CreateModelExpression_ReturnsExpectedMetadata_IntExpressions(
+ Expression> expression,
+ string expectedName)
+ {
+ // Arrange
+ var viewContext = CreateViewContext(model: null);
+ var page = CreatePage(viewContext);
+
+ // Act
+ var result = page.CreateModelExpression(expression);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Metadata);
+ Assert.Equal(typeof(int), result.Metadata.ModelType);
+ Assert.Equal(expectedName, result.Name);
+ }
+
+ [Theory]
+ [MemberData(nameof(StringExpressions))]
+ public void CreateModelExpression_ReturnsExpectedMetadata_StringExpressions(
+ Expression> expression,
+ string expectedName)
+ {
+ // Arrange
+ var viewContext = CreateViewContext(model: null);
+ var page = CreatePage(viewContext);
+
+ // Act
+ var result = page.CreateModelExpression(expression);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Metadata);
+ Assert.Equal(typeof(string), result.Metadata.ModelType);
+ Assert.Equal(expectedName, result.Name);
+ }
+
+ private static TestRazorPage CreatePage(ViewContext viewContext)
+ {
+ return new TestRazorPage
+ {
+ ViewContext = viewContext,
+ ViewData = (ViewDataDictionary)viewContext.ViewData,
+ };
+ }
+
+ private static ViewContext CreateViewContext(RazorPageCreateModelExpressionModel model)
+ {
+ return CreateViewContext(model, new DataAnnotationsModelMetadataProvider());
+ }
+
+ private static ViewContext CreateViewContext(
+ RazorPageCreateModelExpressionModel model,
+ IModelMetadataProvider provider)
+ {
+ var viewData = new ViewDataDictionary(provider)
+ {
+ Model = model,
+ };
+
+ var serviceProvider = new Mock();
+ serviceProvider
+ .Setup(real => real.GetService(typeof(IModelMetadataProvider)))
+ .Returns(provider);
+
+ var httpContext = new Mock();
+ httpContext
+ .SetupGet(real => real.RequestServices)
+ .Returns(serviceProvider.Object);
+
+ var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
+
+ return new ViewContext(
+ actionContext,
+ view: Mock.Of(),
+ viewData: viewData,
+ writer: new StringWriter());
+ }
+
+ private class TestRazorPage : RazorPage
+ {
+ public override Task ExecuteAsync()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class RazorPageCreateModelExpressionModel
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public RazorPageCreateModelExpressionSubModel SubModel { get; set; }
+ }
+
+ public class RazorPageCreateModelExpressionSubModel
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public RazorPageCreateModelExpressionSubSubModel SubSubModel { get; set; }
+ }
+
+ public class RazorPageCreateModelExpressionSubSubModel
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+ }
+ }
+}
\ No newline at end of file