зеркало из 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");
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}
|
||||
/// </summary>
|
||||
|
@ -1514,6 +1498,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
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)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </summary>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -9,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <typeparam name="TModel">The type of the view data model.</typeparam>
|
||||
public abstract class RazorPage<TModel> : RazorPage
|
||||
{
|
||||
IModelMetadataProvider _provider;
|
||||
|
||||
public TModel Model
|
||||
{
|
||||
get
|
||||
|
@ -19,5 +28,33 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
[Activate]
|
||||
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">
|
||||
<value>You cannot flush while inside a writing scope.</value>
|
||||
</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">
|
||||
<value>{0} can only be called from a layout page.</value>
|
||||
</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; }
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче