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:
parent
027bf336da
commit
926034ac4e
|
|
@ -166,8 +166,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddTransient<IHtmlHelper, HtmlHelper>();
|
services.TryAddTransient<IHtmlHelper, HtmlHelper>();
|
||||||
services.TryAddTransient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>));
|
services.TryAddTransient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>));
|
||||||
services.TryAddSingleton<IHtmlGenerator, DefaultHtmlGenerator>();
|
services.TryAddSingleton<IHtmlGenerator, DefaultHtmlGenerator>();
|
||||||
services.TryAddSingleton<ExpressionTextCache>();
|
services.TryAddSingleton<ModelExpressionProvider>();
|
||||||
services.TryAddSingleton<IModelExpressionProvider, 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>();
|
services.TryAddSingleton<ValidationHtmlAttributeProvider, DefaultValidationHtmlAttributeProvider>();
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -13,18 +14,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
internal static class ExpressionHelper
|
internal static class ExpressionHelper
|
||||||
{
|
{
|
||||||
public static string GetExpressionText(string expression)
|
public static string GetUncachedExpressionText(LambdaExpression expression)
|
||||||
{
|
=> GetExpressionText(expression, expressionTextCache: null);
|
||||||
// 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 GetExpressionText(LambdaExpression expression)
|
public static string GetExpressionText(LambdaExpression expression, ConcurrentDictionary<LambdaExpression, string> expressionTextCache)
|
||||||
{
|
|
||||||
return GetExpressionText(expression, expressionTextCache: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetExpressionText(LambdaExpression expression, ExpressionTextCache expressionTextCache)
|
|
||||||
{
|
{
|
||||||
if (expression == null)
|
if (expression == null)
|
||||||
{
|
{
|
||||||
|
|
@ -32,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expressionTextCache != null &&
|
if (expressionTextCache != null &&
|
||||||
expressionTextCache.Entries.TryGetValue(expression, out var expressionText))
|
expressionTextCache.TryGetValue(expression, out var expressionText))
|
||||||
{
|
{
|
||||||
return expressionText;
|
return expressionText;
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
Debug.Assert(!doNotCache);
|
Debug.Assert(!doNotCache);
|
||||||
if (expressionTextCache != null)
|
if (expressionTextCache != null)
|
||||||
{
|
{
|
||||||
expressionTextCache.Entries.TryAdd(expression, string.Empty);
|
expressionTextCache.TryAdd(expression, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
@ -202,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
expressionText = builder.ToString();
|
expressionText = builder.ToString();
|
||||||
if (expressionTextCache != null && !doNotCache)
|
if (expressionTextCache != null && !doNotCache)
|
||||||
{
|
{
|
||||||
expressionTextCache.Entries.TryAdd(expression, expressionText);
|
expressionTextCache.TryAdd(expression, expressionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return expressionText;
|
return expressionText;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
return GenerateDisplay(
|
return GenerateDisplay(
|
||||||
metadata,
|
metadata,
|
||||||
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
|
htmlFieldName ?? GetExpressionText(expression),
|
||||||
templateName,
|
templateName,
|
||||||
additionalViewData);
|
additionalViewData);
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +366,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
return GenerateEditor(
|
return GenerateEditor(
|
||||||
modelExplorer,
|
modelExplorer,
|
||||||
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
|
htmlFieldName ?? GetExpressionText(expression),
|
||||||
templateName,
|
templateName,
|
||||||
additionalViewData);
|
additionalViewData);
|
||||||
}
|
}
|
||||||
|
|
@ -1246,5 +1246,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
return selectList;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
public class HtmlHelper<TModel> : HtmlHelper, IHtmlHelper<TModel>
|
public class HtmlHelper<TModel> : HtmlHelper, IHtmlHelper<TModel>
|
||||||
{
|
{
|
||||||
private readonly ExpressionTextCache _expressionTextCache;
|
private readonly ModelExpressionProvider _modelExpressionProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
|
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
IViewBufferScope bufferScope,
|
IViewBufferScope bufferScope,
|
||||||
HtmlEncoder htmlEncoder,
|
HtmlEncoder htmlEncoder,
|
||||||
UrlEncoder urlEncoder,
|
UrlEncoder urlEncoder,
|
||||||
ExpressionTextCache expressionTextCache)
|
ModelExpressionProvider modelExpressionProvider)
|
||||||
: base(
|
: base(
|
||||||
htmlGenerator,
|
htmlGenerator,
|
||||||
viewEngine,
|
viewEngine,
|
||||||
|
|
@ -36,12 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
htmlEncoder,
|
htmlEncoder,
|
||||||
urlEncoder)
|
urlEncoder)
|
||||||
{
|
{
|
||||||
if (expressionTextCache == null)
|
_modelExpressionProvider = modelExpressionProvider ?? throw new ArgumentNullException(nameof(modelExpressionProvider));
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(expressionTextCache));
|
|
||||||
}
|
|
||||||
|
|
||||||
_expressionTextCache = expressionTextCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -102,10 +97,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateCheckBox(
|
return GenerateCheckBox(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
isChecked: null,
|
isChecked: null,
|
||||||
htmlAttributes: htmlAttributes);
|
htmlAttributes: htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
@ -122,10 +117,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateDropDown(
|
return GenerateDropDown(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
selectList,
|
selectList,
|
||||||
optionLabel,
|
optionLabel,
|
||||||
htmlAttributes);
|
htmlAttributes);
|
||||||
|
|
@ -143,10 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateDisplay(
|
return GenerateDisplay(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
htmlFieldName ?? GetExpressionName(expression),
|
htmlFieldName ?? modelExpression.Name,
|
||||||
templateName,
|
templateName,
|
||||||
additionalViewData);
|
additionalViewData);
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +154,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateDisplayName(modelExplorer, GetExpressionName(expression));
|
return GenerateDisplayName(modelExpression.ModelExplorer, modelExpression.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -172,18 +167,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression<TModelItem, TResult>(
|
var modelExpression = _modelExpressionProvider.CreateModelExpression(
|
||||||
expression,
|
|
||||||
new ViewDataDictionary<TModelItem>(ViewData, model: null),
|
new ViewDataDictionary<TModelItem>(ViewData, model: null),
|
||||||
MetadataProvider);
|
expression);
|
||||||
|
|
||||||
var expressionText = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
return GenerateDisplayName(modelExpression.ModelExplorer, modelExpression.Name);
|
||||||
if (modelExplorer == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionText));
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenerateDisplayName(modelExplorer, expressionText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -209,10 +197,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateEditor(
|
return GenerateEditor(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
htmlFieldName ?? GetExpressionName(expression),
|
htmlFieldName ?? modelExpression.Name,
|
||||||
templateName,
|
templateName,
|
||||||
additionalViewData);
|
additionalViewData);
|
||||||
}
|
}
|
||||||
|
|
@ -227,11 +215,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateHidden(
|
return GenerateHidden(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
modelExplorer.Model,
|
modelExpression.Model,
|
||||||
useViewData: false,
|
useViewData: false,
|
||||||
htmlAttributes: htmlAttributes);
|
htmlAttributes: htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
@ -258,8 +246,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateLabel(modelExplorer, GetExpressionName(expression), labelText, htmlAttributes);
|
return GenerateLabel(modelExpression.ModelExplorer, modelExpression.Name, labelText, htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -273,10 +261,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
var name = GetExpressionName(expression);
|
var name = modelExpression.Name;
|
||||||
|
|
||||||
return GenerateListBox(modelExplorer, name, selectList, htmlAttributes);
|
return GenerateListBox(modelExpression.ModelExplorer, name, selectList, htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -301,10 +289,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GeneratePassword(
|
return GeneratePassword(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
value: null,
|
value: null,
|
||||||
htmlAttributes: htmlAttributes);
|
htmlAttributes: htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
@ -325,10 +313,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(value));
|
throw new ArgumentNullException(nameof(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateRadioButton(
|
return GenerateRadioButton(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
value,
|
value,
|
||||||
isChecked: null,
|
isChecked: null,
|
||||||
htmlAttributes: htmlAttributes);
|
htmlAttributes: htmlAttributes);
|
||||||
|
|
@ -346,8 +334,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateTextArea(modelExplorer, GetExpressionName(expression), rows, columns, htmlAttributes);
|
return GenerateTextArea(modelExpression.ModelExplorer, modelExpression.Name, rows, columns, htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -361,15 +349,25 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateTextBox(
|
return GenerateTextBox(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
modelExplorer.Model,
|
modelExpression.Model,
|
||||||
format,
|
format,
|
||||||
htmlAttributes);
|
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)
|
protected string GetExpressionName<TResult>(Expression<Func<TModel, TResult>> expression)
|
||||||
{
|
{
|
||||||
if (expression == null)
|
if (expression == null)
|
||||||
|
|
@ -377,9 +375,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
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)
|
protected ModelExplorer GetModelExplorer<TResult>(Expression<Func<TModel, TResult>> expression)
|
||||||
{
|
{
|
||||||
if (expression == null)
|
if (expression == null)
|
||||||
|
|
@ -387,15 +391,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer =
|
var modelExpression = GetModelExpression(expression);
|
||||||
ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
|
return modelExpression.ModelExplorer;
|
||||||
if (modelExplorer == null)
|
|
||||||
{
|
|
||||||
var expressionName = GetExpressionName(expression);
|
|
||||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return modelExplorer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -410,10 +407,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateValidationMessage(
|
return GenerateValidationMessage(
|
||||||
modelExplorer,
|
modelExpression.ModelExplorer,
|
||||||
GetExpressionName(expression),
|
modelExpression.Name,
|
||||||
message,
|
message,
|
||||||
tag,
|
tag,
|
||||||
htmlAttributes);
|
htmlAttributes);
|
||||||
|
|
@ -427,8 +424,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = GetModelExplorer(expression);
|
var modelExpression = GetModelExpression(expression);
|
||||||
return GenerateValue(GetExpressionName(expression), modelExplorer.Model, format, useViewData: false);
|
return GenerateValue(modelExpression.Name, modelExpression.Model, format, useViewData: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,40 +2,50 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A default implementation of <see cref="IModelMetadataProvider"/>.
|
/// Provides <see cref="ModelExpression"/> for expressions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModelExpressionProvider : IModelExpressionProvider
|
public class ModelExpressionProvider : IModelExpressionProvider
|
||||||
{
|
{
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly ExpressionTextCache _expressionTextCache;
|
private readonly ConcurrentDictionary<LambdaExpression, string> _expressionTextCache;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ModelExpressionProvider"/>.
|
/// Creates a new <see cref="ModelExpressionProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||||
/// <param name="expressionTextCache">The <see cref="ExpressionTextCache"/>.</param>
|
public ModelExpressionProvider(IModelMetadataProvider modelMetadataProvider)
|
||||||
public ModelExpressionProvider(
|
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
|
||||||
ExpressionTextCache expressionTextCache)
|
|
||||||
{
|
{
|
||||||
if (modelMetadataProvider == null)
|
if (modelMetadataProvider == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelMetadataProvider));
|
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;
|
return ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
||||||
_expressionTextCache = expressionTextCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -53,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(expression));
|
throw new ArgumentNullException(nameof(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
var name = GetExpressionText(expression);
|
||||||
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, viewData, _modelMetadataProvider);
|
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, viewData, _modelMetadataProvider);
|
||||||
if (modelExplorer == null)
|
if (modelExplorer == null)
|
||||||
{
|
{
|
||||||
|
|
@ -63,5 +73,37 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
return new ModelExpression(name, modelExplorer);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,12 +197,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
if (IsConversionToObject(unaryExpression))
|
if (IsConversionToObject(unaryExpression))
|
||||||
{
|
{
|
||||||
return ExpressionHelper.GetExpressionText(Expression.Lambda(
|
return ExpressionHelper.GetUncachedExpressionText(Expression.Lambda(
|
||||||
unaryExpression.Operand,
|
unaryExpression.Operand,
|
||||||
expression.Parameters[0]));
|
expression.Parameters[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExpressionHelper.GetExpressionText(expression);
|
return ExpressionHelper.GetUncachedExpressionText(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConversionToObject(UnaryExpression expression)
|
private static bool IsConversionToObject(UnaryExpression expression)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
HtmlEncoder = new HtmlTestEncoder();
|
HtmlEncoder = new HtmlTestEncoder();
|
||||||
JsonHelper = Mock.Of<IJsonHelper>();
|
JsonHelper = Mock.Of<IJsonHelper>();
|
||||||
MetadataProvider = new EmptyModelMetadataProvider();
|
MetadataProvider = new EmptyModelMetadataProvider();
|
||||||
ModelExpressionProvider = new ModelExpressionProvider(MetadataProvider, new ExpressionTextCache());
|
ModelExpressionProvider = new ModelExpressionProvider(MetadataProvider);
|
||||||
UrlHelperFactory = new UrlHelperFactory();
|
UrlHelperFactory = new UrlHelperFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +245,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
var serviceProvider = new ServiceCollection()
|
var serviceProvider = new ServiceCollection()
|
||||||
.AddSingleton(myService)
|
.AddSingleton(myService)
|
||||||
.AddSingleton(htmlHelper)
|
.AddSingleton(htmlHelper)
|
||||||
.AddSingleton(new ExpressionTextCache())
|
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext
|
var httpContext = new DefaultHttpContext
|
||||||
|
|
|
||||||
|
|
@ -209,9 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
private static IModelExpressionProvider CreateModelExpressionProvider()
|
private static IModelExpressionProvider CreateModelExpressionProvider()
|
||||||
{
|
{
|
||||||
var provider = new EmptyModelMetadataProvider();
|
var provider = new EmptyModelMetadataProvider();
|
||||||
var modelExpressionProvider = new ModelExpressionProvider(
|
var modelExpressionProvider = new ModelExpressionProvider(provider);
|
||||||
provider,
|
|
||||||
new ExpressionTextCache());
|
|
||||||
|
|
||||||
return modelExpressionProvider;
|
return modelExpressionProvider;
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +220,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
var viewData = new ViewDataDictionary<RazorPageCreateModelExpressionModel>(provider, new ModelStateDictionary());
|
var viewData = new ViewDataDictionary<RazorPageCreateModelExpressionModel>(provider, new ModelStateDictionary());
|
||||||
var serviceCollection = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
serviceCollection.AddSingleton<IModelMetadataProvider>(provider);
|
serviceCollection.AddSingleton<IModelMetadataProvider>(provider);
|
||||||
serviceCollection.AddSingleton<ExpressionTextCache, ExpressionTextCache>();
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext
|
var httpContext = new DefaultHttpContext
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
private static TestRazorPage CreateTestRazorPage()
|
private static TestRazorPage CreateTestRazorPage()
|
||||||
{
|
{
|
||||||
var modelMetadataProvider = new EmptyModelMetadataProvider();
|
var modelMetadataProvider = new EmptyModelMetadataProvider();
|
||||||
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
|
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider);
|
||||||
var activator = new RazorPageActivator(
|
var activator = new RazorPageActivator(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
new UrlHelperFactory(),
|
new UrlHelperFactory(),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
@ -11,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
public class ExpressionHelperTest
|
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
|
public static TheoryData<Expression, string> ExpressionAndTexts
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -252,8 +252,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
||||||
.Returns(urlHelper);
|
.Returns(urlHelper);
|
||||||
|
|
||||||
var expressionTextCache = new ExpressionTextCache();
|
|
||||||
|
|
||||||
if (htmlGenerator == null)
|
if (htmlGenerator == null)
|
||||||
{
|
{
|
||||||
htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(provider, urlHelperFactory.Object, options);
|
htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(provider, urlHelperFactory.Object, options);
|
||||||
|
|
@ -290,7 +288,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
new TestViewBufferScope(),
|
new TestViewBufferScope(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
UrlEncoder.Default,
|
UrlEncoder.Default,
|
||||||
expressionTextCache);
|
new ModelExpressionProvider(provider));
|
||||||
|
|
||||||
var viewContext = new ViewContext(
|
var viewContext = new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
|
||||||
|
|
@ -562,7 +562,6 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<DiagnosticListener>(diagnosticSource);
|
services.AddSingleton<DiagnosticListener>(diagnosticSource);
|
||||||
services.AddSingleton<ViewComponentInvokerCache>();
|
services.AddSingleton<ViewComponentInvokerCache>();
|
||||||
services.AddSingleton<ExpressionTextCache>();
|
|
||||||
services.AddSingleton(Options.Create(new MvcViewOptions()));
|
services.AddSingleton(Options.Create(new MvcViewOptions()));
|
||||||
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||||
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
|
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue