Re-enable extensibility points for ExpressionHelper.GetExpressionText, ExpressionMetadataProvider.FromStringExpression (#4494)

* Re-enable extensibility points for ExpressionHelper.GetExpressionText, ExpressionMetadataProvider.FromStringExpression

Fixes https://github.com/aspnet/Mvc/issues/8724
This commit is contained in:
Pranav K 2018-12-08 13:54:05 +05:30 committed by GitHub
parent 027bf336da
commit 926034ac4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 258 additions and 236 deletions

View File

@ -166,8 +166,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddTransient<IHtmlHelper, HtmlHelper>();
services.TryAddTransient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>));
services.TryAddSingleton<IHtmlGenerator, DefaultHtmlGenerator>();
services.TryAddSingleton<ExpressionTextCache>();
services.TryAddSingleton<IModelExpressionProvider, ModelExpressionProvider>();
services.TryAddSingleton<ModelExpressionProvider>();
// ModelExpressionProvider caches results. Ensure that it's re-used when the requested type is IModelExpressionProvider.
services.TryAddSingleton<IModelExpressionProvider>(s => s.GetRequiredService<ModelExpressionProvider>());
services.TryAddSingleton<ValidationHtmlAttributeProvider, DefaultValidationHtmlAttributeProvider>();
//

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@ -13,18 +14,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
internal static class ExpressionHelper
{
public static string GetExpressionText(string expression)
{
// If it's exactly "model", then give them an empty string, to replicate the lambda behavior.
return string.Equals(expression, "model", StringComparison.OrdinalIgnoreCase) ? string.Empty : expression;
}
public static string GetUncachedExpressionText(LambdaExpression expression)
=> GetExpressionText(expression, expressionTextCache: null);
public static string GetExpressionText(LambdaExpression expression)
{
return GetExpressionText(expression, expressionTextCache: null);
}
public static string GetExpressionText(LambdaExpression expression, ExpressionTextCache expressionTextCache)
public static string GetExpressionText(LambdaExpression expression, ConcurrentDictionary<LambdaExpression, string> expressionTextCache)
{
if (expression == null)
{
@ -32,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
if (expressionTextCache != null &&
expressionTextCache.Entries.TryGetValue(expression, out var expressionText))
expressionTextCache.TryGetValue(expression, out var expressionText))
{
return expressionText;
}
@ -145,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
Debug.Assert(!doNotCache);
if (expressionTextCache != null)
{
expressionTextCache.Entries.TryAdd(expression, string.Empty);
expressionTextCache.TryAdd(expression, string.Empty);
}
return string.Empty;
@ -202,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
expressionText = builder.ToString();
if (expressionTextCache != null && !doNotCache)
{
expressionTextCache.Entries.TryAdd(expression, expressionText);
expressionTextCache.TryAdd(expression, expressionText);
}
return expressionText;

View File

@ -1,124 +0,0 @@
// Copyright (c) .NET Foundation. 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
/// <summary>
/// This class holds the cache for the expression text that is computed by ExpressionHelper.
/// </summary>
public class ExpressionTextCache
{
/// <inheritdoc />
public ConcurrentDictionary<LambdaExpression, string> Entries { get; } =
new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
// This comparer is tightly coupled with the logic of ExpressionHelper.GetExpressionText.
// It is not designed to accurately compare any two arbitrary LambdaExpressions.
private class LambdaExpressionComparer : IEqualityComparer<LambdaExpression>
{
public static readonly LambdaExpressionComparer Instance = new LambdaExpressionComparer();
public bool Equals(LambdaExpression lambdaExpression1, LambdaExpression lambdaExpression2)
{
if (ReferenceEquals(lambdaExpression1,lambdaExpression2))
{
return true;
}
// We will cache only pure member access expressions. Hence we compare two expressions
// to be equal only if they are identical member access expressions.
var expression1 = lambdaExpression1.Body;
var expression2 = lambdaExpression2.Body;
while (true)
{
if (expression1 == null && expression2 == null)
{
return true;
}
if (expression1 == null || expression2 == null)
{
return false;
}
if (expression1.NodeType != expression2.NodeType)
{
return false;
}
switch (expression1.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression1 = (MemberExpression)expression1;
var memberName1 = memberExpression1.Member.Name;
expression1 = memberExpression1.Expression;
var memberExpression2 = (MemberExpression)expression2;
var memberName2 = memberExpression2.Member.Name;
expression2 = memberExpression2.Expression;
// If identifier contains "__", it is "reserved for use by the implementation" and likely
// compiler- or Razor-generated e.g. the name of a field in a delegate's generated class.
if (memberName1.Contains("__") && memberName2.Contains("__"))
{
return true;
}
if (!string.Equals(memberName1, memberName2, StringComparison.Ordinal))
{
return false;
}
break;
case ExpressionType.ArrayIndex:
// Shouldn't be cached. Just in case, ensure indexers are all different.
return false;
case ExpressionType.Call:
// Shouldn't be cached. Just in case, ensure indexers and other calls are all different.
return false;
default:
// Everything else terminates name generation. Haven't found a difference so far...
return true;
}
}
}
public int GetHashCode(LambdaExpression lambdaExpression)
{
var expression = lambdaExpression.Body;
var hashCodeCombiner = HashCodeCombiner.Start();
while (true)
{
if (expression != null && expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression)expression;
var memberName = memberExpression.Member.Name;
if (memberName.Contains("__"))
{
break;
}
hashCodeCombiner.Add(memberName, StringComparer.Ordinal);
expression = memberExpression.Expression;
}
else
{
break;
}
}
return hashCodeCombiner.CombinedHash;
}
}
}
}

View File

@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return GenerateDisplay(
metadata,
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
htmlFieldName ?? GetExpressionText(expression),
templateName,
additionalViewData);
}
@ -366,7 +366,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return GenerateEditor(
modelExplorer,
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
htmlFieldName ?? GetExpressionText(expression),
templateName,
additionalViewData);
}
@ -1246,5 +1246,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return selectList;
}
private static string GetExpressionText(string expression)
{
// If it's exactly "model", then give them an empty string, to replicate the lambda behavior.
return string.Equals(expression, "model", StringComparison.OrdinalIgnoreCase) ? string.Empty : expression;
}
}
}

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
public class HtmlHelper<TModel> : HtmlHelper, IHtmlHelper<TModel>
{
private readonly ExpressionTextCache _expressionTextCache;
private readonly ModelExpressionProvider _modelExpressionProvider;
/// <summary>
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
IViewBufferScope bufferScope,
HtmlEncoder htmlEncoder,
UrlEncoder urlEncoder,
ExpressionTextCache expressionTextCache)
ModelExpressionProvider modelExpressionProvider)
: base(
htmlGenerator,
viewEngine,
@ -36,12 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
htmlEncoder,
urlEncoder)
{
if (expressionTextCache == null)
{
throw new ArgumentNullException(nameof(expressionTextCache));
}
_expressionTextCache = expressionTextCache;
_modelExpressionProvider = modelExpressionProvider ?? throw new ArgumentNullException(nameof(modelExpressionProvider));
}
/// <inheritdoc />
@ -102,10 +97,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateCheckBox(
modelExplorer,
GetExpressionName(expression),
modelExpression.ModelExplorer,
modelExpression.Name,
isChecked: null,
htmlAttributes: htmlAttributes);
}
@ -122,10 +117,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateDropDown(
modelExplorer,
GetExpressionName(expression),
modelExpression.ModelExplorer,
modelExpression.Name,
selectList,
optionLabel,
htmlAttributes);
@ -143,10 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateDisplay(
modelExplorer,
htmlFieldName ?? GetExpressionName(expression),
modelExpression.ModelExplorer,
htmlFieldName ?? modelExpression.Name,
templateName,
additionalViewData);
}
@ -159,8 +154,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
return GenerateDisplayName(modelExplorer, GetExpressionName(expression));
var modelExpression = GetModelExpression(expression);
return GenerateDisplayName(modelExpression.ModelExplorer, modelExpression.Name);
}
/// <inheritdoc />
@ -172,18 +167,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression<TModelItem, TResult>(
expression,
var modelExpression = _modelExpressionProvider.CreateModelExpression(
new ViewDataDictionary<TModelItem>(ViewData, model: null),
MetadataProvider);
expression);
var expressionText = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
if (modelExplorer == null)
{
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionText));
}
return GenerateDisplayName(modelExplorer, expressionText);
return GenerateDisplayName(modelExpression.ModelExplorer, modelExpression.Name);
}
/// <inheritdoc />
@ -209,10 +197,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateEditor(
modelExplorer,
htmlFieldName ?? GetExpressionName(expression),
modelExpression.ModelExplorer,
htmlFieldName ?? modelExpression.Name,
templateName,
additionalViewData);
}
@ -227,11 +215,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateHidden(
modelExplorer,
GetExpressionName(expression),
modelExplorer.Model,
modelExpression.ModelExplorer,
modelExpression.Name,
modelExpression.Model,
useViewData: false,
htmlAttributes: htmlAttributes);
}
@ -258,8 +246,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
return GenerateLabel(modelExplorer, GetExpressionName(expression), labelText, htmlAttributes);
var modelExpression = GetModelExpression(expression);
return GenerateLabel(modelExpression.ModelExplorer, modelExpression.Name, labelText, htmlAttributes);
}
/// <inheritdoc />
@ -273,10 +261,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var name = GetExpressionName(expression);
var modelExpression = GetModelExpression(expression);
var name = modelExpression.Name;
return GenerateListBox(modelExplorer, name, selectList, htmlAttributes);
return GenerateListBox(modelExpression.ModelExplorer, name, selectList, htmlAttributes);
}
/// <inheritdoc />
@ -301,10 +289,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GeneratePassword(
modelExplorer,
GetExpressionName(expression),
modelExpression.ModelExplorer,
modelExpression.Name,
value: null,
htmlAttributes: htmlAttributes);
}
@ -325,10 +313,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(value));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateRadioButton(
modelExplorer,
GetExpressionName(expression),
modelExpression.ModelExplorer,
modelExpression.Name,
value,
isChecked: null,
htmlAttributes: htmlAttributes);
@ -346,8 +334,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
return GenerateTextArea(modelExplorer, GetExpressionName(expression), rows, columns, htmlAttributes);
var modelExpression = GetModelExpression(expression);
return GenerateTextArea(modelExpression.ModelExplorer, modelExpression.Name, rows, columns, htmlAttributes);
}
/// <inheritdoc />
@ -361,15 +349,25 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateTextBox(
modelExplorer,
GetExpressionName(expression),
modelExplorer.Model,
modelExpression.ModelExplorer,
modelExpression.Name,
modelExpression.Model,
format,
htmlAttributes);
}
private ModelExpression GetModelExpression<TResult>(Expression<Func<TModel, TResult>> expression)
{
return _modelExpressionProvider.CreateModelExpression(ViewData, expression);
}
/// <summary>
/// Gets the name for <paramref name="expression"/>.
/// </summary>
/// <param name="expression">The expression.</param>
/// <returns>The expression name.</returns>
protected string GetExpressionName<TResult>(Expression<Func<TModel, TResult>> expression)
{
if (expression == null)
@ -377,9 +375,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
return ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
return _modelExpressionProvider.GetExpressionText(expression);
}
/// <summary>
/// Gets the <see cref="ModelExplorer"/> for <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="expression">The expression.</param>
/// <returns>The <see cref="ModelExplorer"/>.</returns>
protected ModelExplorer GetModelExplorer<TResult>(Expression<Func<TModel, TResult>> expression)
{
if (expression == null)
@ -387,15 +391,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer =
ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
if (modelExplorer == null)
{
var expressionName = GetExpressionName(expression);
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionName));
}
return modelExplorer;
var modelExpression = GetModelExpression(expression);
return modelExpression.ModelExplorer;
}
/// <inheritdoc />
@ -410,10 +407,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
var modelExpression = GetModelExpression(expression);
return GenerateValidationMessage(
modelExplorer,
GetExpressionName(expression),
modelExpression.ModelExplorer,
modelExpression.Name,
message,
tag,
htmlAttributes);
@ -427,8 +424,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = GetModelExplorer(expression);
return GenerateValue(GetExpressionName(expression), modelExplorer.Model, format, useViewData: false);
var modelExpression = GetModelExpression(expression);
return GenerateValue(modelExpression.Name, modelExpression.Model, format, useViewData: false);
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
// This comparer is tightly coupled with the logic of ExpressionHelper.GetExpressionText.
// It is not designed to accurately compare any two arbitrary LambdaExpressions.
internal class LambdaExpressionComparer : IEqualityComparer<LambdaExpression>
{
public static readonly LambdaExpressionComparer Instance = new LambdaExpressionComparer();
public bool Equals(LambdaExpression lambdaExpression1, LambdaExpression lambdaExpression2)
{
if (ReferenceEquals(lambdaExpression1, lambdaExpression2))
{
return true;
}
// We will cache only pure member access expressions. Hence we compare two expressions
// to be equal only if they are identical member access expressions.
var expression1 = lambdaExpression1.Body;
var expression2 = lambdaExpression2.Body;
while (true)
{
if (expression1 == null && expression2 == null)
{
return true;
}
if (expression1 == null || expression2 == null)
{
return false;
}
if (expression1.NodeType != expression2.NodeType)
{
return false;
}
switch (expression1.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression1 = (MemberExpression)expression1;
var memberName1 = memberExpression1.Member.Name;
expression1 = memberExpression1.Expression;
var memberExpression2 = (MemberExpression)expression2;
var memberName2 = memberExpression2.Member.Name;
expression2 = memberExpression2.Expression;
// If identifier contains "__", it is "reserved for use by the implementation" and likely
// compiler- or Razor-generated e.g. the name of a field in a delegate's generated class.
if (memberName1.Contains("__") && memberName2.Contains("__"))
{
return true;
}
if (!string.Equals(memberName1, memberName2, StringComparison.Ordinal))
{
return false;
}
break;
case ExpressionType.ArrayIndex:
// Shouldn't be cached. Just in case, ensure indexers are all different.
return false;
case ExpressionType.Call:
// Shouldn't be cached. Just in case, ensure indexers and other calls are all different.
return false;
default:
// Everything else terminates name generation. Haven't found a difference so far...
return true;
}
}
}
public int GetHashCode(LambdaExpression lambdaExpression)
{
var expression = lambdaExpression.Body;
var hashCodeCombiner = HashCodeCombiner.Start();
while (true)
{
if (expression != null && expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression)expression;
var memberName = memberExpression.Member.Name;
if (memberName.Contains("__"))
{
break;
}
hashCodeCombiner.Add(memberName, StringComparer.Ordinal);
expression = memberExpression.Expression;
}
else
{
break;
}
}
return hashCodeCombiner.CombinedHash;
}
}
}

View File

@ -2,40 +2,50 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
/// <summary>
/// A default implementation of <see cref="IModelMetadataProvider"/>.
/// Provides <see cref="ModelExpression"/> for expressions.
/// </summary>
public class ModelExpressionProvider : IModelExpressionProvider
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly ExpressionTextCache _expressionTextCache;
private readonly ConcurrentDictionary<LambdaExpression, string> _expressionTextCache;
/// <summary>
/// Creates a new <see cref="ModelExpressionProvider"/>.
/// Creates a new <see cref="ModelExpressionProvider"/>.
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="expressionTextCache">The <see cref="ExpressionTextCache"/>.</param>
public ModelExpressionProvider(
IModelMetadataProvider modelMetadataProvider,
ExpressionTextCache expressionTextCache)
public ModelExpressionProvider(IModelMetadataProvider modelMetadataProvider)
{
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
}
if (expressionTextCache == null)
_modelMetadataProvider = modelMetadataProvider;
_expressionTextCache = new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
}
/// <summary>
/// Gets the name for <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TValue">The type of the <paramref name="expression"/> result.</typeparam>
/// <param name="expression">The expression.</param>
/// <returns>The expression name.</returns>
public string GetExpressionText<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expressionTextCache));
throw new ArgumentNullException(nameof(expression));
}
_modelMetadataProvider = modelMetadataProvider;
_expressionTextCache = expressionTextCache;
return ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
}
/// <inheritdoc />
@ -53,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
var name = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
var name = GetExpressionText(expression);
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, viewData, _modelMetadataProvider);
if (modelExplorer == null)
{
@ -63,5 +73,37 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return new ModelExpression(name, modelExplorer);
}
/// <summary>
/// Returns a <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TModel">The type of the <paramref name="viewData"/>'s <see cref="ViewDataDictionary{T}.Model"/>.</typeparam>
/// <param name="viewData">The <see cref="ViewDataDictionary{TModel}"/> containing the <see cref="ViewDataDictionary{T}.Model"/>
/// against which <paramref name="expression"/> is evaluated. </param>
/// <param name="expression">Expression name, relative to <c>viewData.Model</c>.</param>
/// <returns>A new <see cref="ModelExpression"/> instance describing the given <paramref name="expression"/>.</returns>
public ModelExpression CreateModelExpression<TModel>(
ViewDataDictionary<TModel> viewData,
string expression)
{
if (viewData == null)
{
throw new ArgumentNullException(nameof(viewData));
}
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var modelExplorer = ExpressionMetadataProvider.FromStringExpression(expression, viewData, _modelMetadataProvider);
if (modelExplorer == null)
{
throw new InvalidOperationException(
Resources.FormatCreateModelExpression_NullModelMetadata(nameof(IModelMetadataProvider), expression));
}
return new ModelExpression(expression, modelExplorer);
}
}
}

View File

@ -197,12 +197,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
if (IsConversionToObject(unaryExpression))
{
return ExpressionHelper.GetExpressionText(Expression.Lambda(
return ExpressionHelper.GetUncachedExpressionText(Expression.Lambda(
unaryExpression.Operand,
expression.Parameters[0]));
}
return ExpressionHelper.GetExpressionText(expression);
return ExpressionHelper.GetUncachedExpressionText(expression);
}
private static bool IsConversionToObject(UnaryExpression expression)

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
HtmlEncoder = new HtmlTestEncoder();
JsonHelper = Mock.Of<IJsonHelper>();
MetadataProvider = new EmptyModelMetadataProvider();
ModelExpressionProvider = new ModelExpressionProvider(MetadataProvider, new ExpressionTextCache());
ModelExpressionProvider = new ModelExpressionProvider(MetadataProvider);
UrlHelperFactory = new UrlHelperFactory();
}
@ -245,7 +245,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(htmlHelper)
.AddSingleton(new ExpressionTextCache())
.BuildServiceProvider();
var httpContext = new DefaultHttpContext

View File

@ -209,9 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private static IModelExpressionProvider CreateModelExpressionProvider()
{
var provider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(
provider,
new ExpressionTextCache());
var modelExpressionProvider = new ModelExpressionProvider(provider);
return modelExpressionProvider;
}
@ -222,7 +220,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var viewData = new ViewDataDictionary<RazorPageCreateModelExpressionModel>(provider, new ModelStateDictionary());
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IModelMetadataProvider>(provider);
serviceCollection.AddSingleton<ExpressionTextCache, ExpressionTextCache>();
var httpContext = new DefaultHttpContext
{

View File

@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private static TestRazorPage CreateTestRazorPage()
{
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider);
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@ -11,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
public class ExpressionHelperTest
{
private readonly ExpressionTextCache _expressionTextCache = new ExpressionTextCache();
private readonly ConcurrentDictionary<LambdaExpression, string> _expressionTextCache = new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
public static TheoryData<Expression, string> ExpressionAndTexts
{

View File

@ -252,8 +252,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
.Returns(urlHelper);
var expressionTextCache = new ExpressionTextCache();
if (htmlGenerator == null)
{
htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(provider, urlHelperFactory.Object, options);
@ -290,7 +288,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
new TestViewBufferScope(),
new HtmlTestEncoder(),
UrlEncoder.Default,
expressionTextCache);
new ModelExpressionProvider(provider));
var viewContext = new ViewContext(
actionContext,

View File

@ -562,7 +562,6 @@ namespace Microsoft.AspNetCore.Mvc
var services = new ServiceCollection();
services.AddSingleton<DiagnosticListener>(diagnosticSource);
services.AddSingleton<ViewComponentInvokerCache>();
services.AddSingleton<ExpressionTextCache>();
services.AddSingleton(Options.Create(new MvcViewOptions()));
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();