зеркало из https://github.com/aspnet/Mvc.git
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
This commit is contained in:
Родитель
3290791c5f
Коммит
639a788ed8
|
@ -1482,22 +1482,6 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return GetString("AttributeRoute_NullTemplateRepresentation");
|
return GetString("AttributeRoute_NullTemplateRepresentation");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// "The path to the file must be absolute: {0}"
|
|
||||||
/// </summary>
|
|
||||||
internal static string FileResult_InvalidPathType_RelativeOrVirtualPath
|
|
||||||
{
|
|
||||||
get { return GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// "The path to the file must be absolute: {0}"
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatFileResult_InvalidPathType_RelativeOrVirtualPath(object p0)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"), p0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}
|
/// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1514,6 +1498,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultActionSelector_AmbiguousActions"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultActionSelector_AmbiguousActions"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The path to the file must be absolute: {0}"
|
||||||
|
/// </summary>
|
||||||
|
internal static string FileResult_InvalidPathType_RelativeOrVirtualPath
|
||||||
|
{
|
||||||
|
get { return GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The path to the file must be absolute: {0}"
|
||||||
|
/// </summary>
|
||||||
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes an <see cref="System.Linq.Expressions.Expression"/> passed to a tag helper.
|
||||||
|
/// </summary>
|
||||||
|
public class ModelExpression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModelExpression"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// String representation of the <see cref="System.Linq.Expressions.Expression"/> of interest.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="metadata">
|
||||||
|
/// Metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest.
|
||||||
|
/// </param>
|
||||||
|
public ModelExpression(string name, [NotNull] ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
Metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String representation of the <see cref="System.Linq.Expressions.Expression"/> of interest.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Getting <see cref="ModelMetadata.Model"/> will evaluate a compiled version of the original
|
||||||
|
/// <see cref="System.Linq.Expressions.Expression"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public ModelMetadata Metadata { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -186,6 +186,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return GetString("RazorPage_YouCannotFlushWhileInAWritingScope");
|
return GetString("RazorPage_YouCannotFlushWhileInAWritingScope");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The {0} was unable to provide metadata for expression '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RazorPage_NullModelMetadata
|
||||||
|
{
|
||||||
|
get { return GetString("RazorPage_NullModelMetadata"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The {0} was unable to provide metadata for expression '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRazorPage_NullModelMetadata(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_NullModelMetadata"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// {0} can only be called from a layout page.
|
/// {0} can only be called from a layout page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -9,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// <typeparam name="TModel">The type of the view data model.</typeparam>
|
/// <typeparam name="TModel">The type of the view data model.</typeparam>
|
||||||
public abstract class RazorPage<TModel> : RazorPage
|
public abstract class RazorPage<TModel> : RazorPage
|
||||||
{
|
{
|
||||||
|
IModelMetadataProvider _provider;
|
||||||
|
|
||||||
public TModel Model
|
public TModel Model
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -19,5 +28,33 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
[Activate]
|
[Activate]
|
||||||
public ViewDataDictionary<TModel> ViewData { get; set; }
|
public ViewDataDictionary<TModel> ViewData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">The type of the <paramref name="expression"/> result.</typeparam>
|
||||||
|
/// <param name="expression">An expression to be evaluated against the current model.</param>
|
||||||
|
/// <returns>A new <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Compiler normally infers <typeparamref name="TValue"/> from the given <paramref name="expression"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public ModelExpression CreateModelExpression<TValue>([NotNull] Expression<Func<TModel, TValue>> expression)
|
||||||
|
{
|
||||||
|
if (_provider == null)
|
||||||
|
{
|
||||||
|
_provider = Context.RequestServices.GetService<IModelMetadataProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,9 @@
|
||||||
<data name="RazorPage_YouCannotFlushWhileInAWritingScope" xml:space="preserve">
|
<data name="RazorPage_YouCannotFlushWhileInAWritingScope" xml:space="preserve">
|
||||||
<value>You cannot flush while inside a writing scope.</value>
|
<value>You cannot flush while inside a writing scope.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RazorPage_NullModelMetadata" xml:space="preserve">
|
||||||
|
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
|
||||||
|
</data>
|
||||||
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
||||||
<value>{0} can only be called from a layout page.</value>
|
<value>{0} can only be called from a layout page.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -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<Expression<Func<RazorPageCreateModelExpressionModel, int>>, string> IntExpressions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var somethingElse = 23;
|
||||||
|
return new TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, int>>, 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<Expression<Func<RazorPageCreateModelExpressionModel, string>>, string> StringExpressions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var somethingElse = "This is something else";
|
||||||
|
return new TheoryData<Expression<Func<RazorPageCreateModelExpressionModel, string>>, 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<Func<RazorPageCreateModelExpressionModel, int>> 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<Func<RazorPageCreateModelExpressionModel, string>> 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<RazorPageCreateModelExpressionModel>)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<RazorPageCreateModelExpressionModel>(provider)
|
||||||
|
{
|
||||||
|
Model = model,
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceProvider = new Mock<IServiceProvider>();
|
||||||
|
serviceProvider
|
||||||
|
.Setup(real => real.GetService(typeof(IModelMetadataProvider)))
|
||||||
|
.Returns(provider);
|
||||||
|
|
||||||
|
var httpContext = new Mock<HttpContext>();
|
||||||
|
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<IView>(),
|
||||||
|
viewData: viewData,
|
||||||
|
writer: new StringWriter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRazorPage : RazorPage<RazorPageCreateModelExpressionModel>
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче