Reduce allocations during HTML generation
- #3918 - precompute size of `StringBuilder` in `ExpressionHelper` - reduce `string` allocations in `ViewDataEvaluator` - also get rid of `Enumeration` state machines - reduce the size of a few objects; use more expression-valued properties - e.g. don't store `_modelType` in `ModelExplorer` - add `EmptyArray<TElement>`; make empty arrays consistently `static` - avoid `string.Split()` in HTML and tag helpers nits: - make `ExpressionHelperTest` tests more stringent - correct `Message` for an `ArgumentNullException` - remove excess `using`s in classes I touched (but often ended up leaving otherwise unchanged) - improve doc comments - remove `ToString()` call on a `string` - avoid encoding `string.Empty` - fix test file name - remove useless variables - correct spelling - improve whitespace
This commit is contained in:
parent
92682b71f3
commit
4cca6b09f0
|
|
@ -169,7 +169,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
// Flag that indiciates if TooManyModelErrorException has already been added to this dictionary.
|
||||
// Flag that indicates if TooManyModelErrorException has already been added to this dictionary.
|
||||
private bool HasRecordedMaxModelError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -625,7 +625,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keys and values from ths instance of <see cref="ModelStateDictionary"/>.
|
||||
/// Removes all keys and values from this instance of <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public abstract class ModelStateEntry
|
||||
{
|
||||
private ModelErrorCollection _errors;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw value from the request associated with this entry.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
#if NETSTANDARD1_6
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
|
@ -83,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
if (string.IsNullOrEmpty(original))
|
||||
{
|
||||
return new string[0];
|
||||
return EmptyArray<string>.Instance;
|
||||
}
|
||||
|
||||
var split = original.Split(',').Select(piece => piece.Trim()).Where(piece => !string.IsNullOrEmpty(piece));
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Initializes a new instance of <see cref="ChallengeResult"/>.
|
||||
/// </summary>
|
||||
public ChallengeResult()
|
||||
: this(new string[] { })
|
||||
: this(EmptyArray<string>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
|
||||
/// challenge.</param>
|
||||
public ChallengeResult(AuthenticationProperties properties)
|
||||
: this(new string[] { }, properties)
|
||||
: this(EmptyArray<string>.Instance, properties)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
// The value used is a non default value so that it avoids getting mixed with other action constraints
|
||||
// with default order.
|
||||
/// <inheritdoc />
|
||||
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
|
||||
int IActionConstraint.Order => ConsumesActionConstraintOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the supported request content types. Used to select an action when there would otherwise be
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public int Order { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReusable { get; } = true;
|
||||
public bool IsReusable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Initializes a new instance of <see cref="ForbidResult"/>.
|
||||
/// </summary>
|
||||
public ForbidResult()
|
||||
: this(new string[] { })
|
||||
: this(EmptyArray<string>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
|
||||
/// challenge.</param>
|
||||
public ForbidResult(AuthenticationProperties properties)
|
||||
: this(new string[] { }, properties)
|
||||
: this(EmptyArray<string>.Instance, properties)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
|
|
@ -18,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Sets mapping for the format to specified media type.
|
||||
/// Sets mapping for the format to specified media type.
|
||||
/// If the format already exists, the media type will be overwritten with the new value.
|
||||
/// </summary>
|
||||
/// <param name="format">The format value.</param>
|
||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets mapping for the format to specified media type.
|
||||
/// Sets mapping for the format to specified media type.
|
||||
/// If the format already exists, the media type will be overwritten with the new value.
|
||||
/// </summary>
|
||||
/// <param name="format">The format value.</param>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public class ClientValidatorCache
|
||||
{
|
||||
private readonly IReadOnlyList<IClientModelValidator> EmptyArray = new IClientModelValidator[0];
|
||||
|
||||
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
||||
|
||||
public IReadOnlyList<IClientModelValidator> GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider)
|
||||
|
|
@ -106,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (count == 0)
|
||||
{
|
||||
return EmptyArray;
|
||||
return EmptyArray<IClientModelValidator>.Instance;
|
||||
}
|
||||
|
||||
var validators = new IClientModelValidator[count];
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
|
@ -13,8 +12,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public class ControllerActionInvokerCache
|
||||
{
|
||||
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
|
||||
|
||||
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||
private readonly IFilterProvider[] _filterProviders;
|
||||
|
||||
|
|
@ -134,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (count == 0)
|
||||
{
|
||||
return EmptyFilterArray;
|
||||
return EmptyArray<IFilterMetadata>.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// 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.
|
||||
|
||||
#if !NET451
|
||||
using System;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public static class EmptyArray<TElement>
|
||||
{
|
||||
public static TElement[] Instance { get; } =
|
||||
#if NET451
|
||||
new TElement[0];
|
||||
#else
|
||||
Array.Empty<TElement>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private void SetContentType(ActionContext context, FileResult result)
|
||||
{
|
||||
var response = context.HttpContext.Response;
|
||||
response.ContentType = result.ContentType.ToString();
|
||||
response.ContentType = result.ContentType;
|
||||
}
|
||||
|
||||
protected static ILogger CreateLogger<T>(ILoggerFactory factory)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
/// </summary>
|
||||
public class PrefixContainer
|
||||
{
|
||||
private static readonly string[] EmptyArray = new string[0];
|
||||
private static readonly char[] Delimiters = new char[] { '[', '.' };
|
||||
|
||||
private readonly ICollection<string> _originalValues;
|
||||
|
|
@ -31,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (_originalValues.Count == 0)
|
||||
{
|
||||
_sortedValues = EmptyArray;
|
||||
_sortedValues = EmptyArray<string>.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public class ValidatorCache
|
||||
{
|
||||
private readonly IReadOnlyList<IModelValidator> EmptyArray = new IModelValidator[0];
|
||||
|
||||
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
||||
|
||||
public IReadOnlyList<IModelValidator> GetValidators(ModelMetadata metadata, IModelValidatorProvider validatorProvider)
|
||||
|
|
@ -105,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (count == 0)
|
||||
{
|
||||
return EmptyArray;
|
||||
return EmptyArray<IModelValidator>.Instance;
|
||||
}
|
||||
|
||||
var validators = new IModelValidator[count];
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
{
|
||||
Debug.Assert(targetType == typeof(TElement[]), "GenericModelBinder only creates this binder for arrays.");
|
||||
|
||||
return new TElement[0];
|
||||
return EmptyArray<TElement>.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
|
|
@ -36,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
{
|
||||
model = new EmptyFormCollection();
|
||||
}
|
||||
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(model);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Localization
|
||||
{
|
||||
|
|
@ -14,11 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.Localization
|
|||
/// </summary>
|
||||
public class LocalizedHtmlString : IHtmlContent
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
private static readonly object[] EmptyArguments = Array.Empty<object>();
|
||||
#else
|
||||
private static readonly object[] EmptyArguments = new object[0];
|
||||
#endif
|
||||
private readonly object[] _arguments;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -27,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization
|
|||
/// <param name="name">The name of the string resource.</param>
|
||||
/// <param name="value">The string resource.</param>
|
||||
public LocalizedHtmlString(string name, string value)
|
||||
: this(name, value, isResourceNotFound: false, arguments: EmptyArguments)
|
||||
: this(name, value, isResourceNotFound: false, arguments: EmptyArray<string>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization
|
|||
/// <param name="value">The string resource.</param>
|
||||
/// <param name="isResourceNotFound">A flag that indicates if the resource is not found.</param>
|
||||
public LocalizedHtmlString(string name, string value, bool isResourceNotFound)
|
||||
: this(name, value, isResourceNotFound, arguments: EmptyArguments)
|
||||
: this(name, value, isResourceNotFound, arguments: EmptyArray<string>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
||||
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult)
|
||||
: this(relativePath, compilationResult, new IChangeToken[0])
|
||||
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +87,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// Gets a delegate that creates an instance of the <see cref="IRazorPage"/>.
|
||||
/// </summary>
|
||||
public Func<IRazorPage> PageFactory { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
|
@ -31,8 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
|
||||
private const string ControllerKey = "controller";
|
||||
private const string AreaKey = "area";
|
||||
private static readonly ViewLocationCacheItem[] EmptyViewStartLocationCacheItems =
|
||||
new ViewLocationCacheItem[0];
|
||||
private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20);
|
||||
|
||||
private readonly IRazorPageFactoryProvider _pageFactory;
|
||||
|
|
@ -415,7 +414,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
// Only need to lookup _ViewStarts for the main page.
|
||||
var viewStartPages = isMainPage ?
|
||||
GetViewStartPages(relativePath, expirationTokens) :
|
||||
EmptyViewStartLocationCacheItems;
|
||||
EmptyArray<ViewLocationCacheItem>.Instance;
|
||||
|
||||
return new ViewLocationCacheResult(
|
||||
new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
|
|
@ -18,13 +19,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
|||
/// </summary>
|
||||
public class GlobbingUrlBuilder
|
||||
{
|
||||
private static readonly IReadOnlyList<string> EmptyList =
|
||||
#if NET451
|
||||
new string[0];
|
||||
#else
|
||||
Array.Empty<string>();
|
||||
#endif
|
||||
|
||||
// Valid whitespace characters defined by the HTML5 spec.
|
||||
private static readonly char[] ValidAttributeWhitespaceChars =
|
||||
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
||||
|
|
@ -116,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
|||
{
|
||||
if (string.IsNullOrEmpty(include))
|
||||
{
|
||||
return EmptyList;
|
||||
return EmptyArray<string>.Instance;
|
||||
}
|
||||
|
||||
var cacheKey = new GlobbingUrlKey(include, exclude);
|
||||
|
|
@ -130,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
|||
var includeEnumerator = includeTokenizer.GetEnumerator();
|
||||
if (!includeEnumerator.MoveNext())
|
||||
{
|
||||
return EmptyList;
|
||||
return EmptyArray<string>.Instance;
|
||||
}
|
||||
|
||||
var options = new MemoryCacheEntryOptions();
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||
|
||||
var fieldNameBase = oldPrefix;
|
||||
var result = new HtmlContentBuilder();
|
||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
||||
|
|
@ -138,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
container: htmlHelper.ViewData.ModelExplorer,
|
||||
metadata: itemMetadata,
|
||||
model: item);
|
||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", oldPrefix, index++);
|
||||
|
||||
var templateBuilder = new TemplateBuilder(
|
||||
viewEngine,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||
|
||||
var fieldNameBase = oldPrefix;
|
||||
var result = new HtmlContentBuilder();
|
||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
||||
|
|
@ -105,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
container: htmlHelper.ViewData.ModelExplorer,
|
||||
metadata: itemMetadata,
|
||||
model: item);
|
||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", oldPrefix, index++);
|
||||
|
||||
var templateBuilder = new TemplateBuilder(
|
||||
viewEngine,
|
||||
|
|
|
|||
|
|
@ -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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
|
@ -37,86 +38,166 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
return expressionText;
|
||||
}
|
||||
|
||||
// Determine size of string needed (length) and number of segments it contains (segmentCount). Put another
|
||||
// way, segmentCount tracks the number of times the loop below should iterate. This avoids adding ".model"
|
||||
// and / or an extra leading "." and then removing them after the loop. Other information collected in this
|
||||
// first loop helps with length and segmentCount adjustments. containsIndexers is somewhat separate: If
|
||||
// true, expression strings are not cached for the expression.
|
||||
//
|
||||
// After the corrections below the first loop, length is usually exactly the size of the returned string.
|
||||
// However when containsIndexers is true, the calculation is approximate because either evaluating indexer
|
||||
// expressions multiple times or saving indexer strings can get expensive. Optimizing for the common case
|
||||
// of a collection (not a dictionary) with less than 100 elements. If that assumption proves to be
|
||||
// incorrect, the StringBuilder will be enlarged but hopefully just once.
|
||||
var containsIndexers = false;
|
||||
var lastIsModel = false;
|
||||
var length = 0;
|
||||
var segmentCount = 0;
|
||||
var trailingMemberExpressions = 0;
|
||||
|
||||
var part = expression.Body;
|
||||
|
||||
// Builder to concatenate the names for property/field accessors within an expression to create a string.
|
||||
var builder = new StringBuilder();
|
||||
|
||||
while (part != null)
|
||||
{
|
||||
if (part.NodeType == ExpressionType.Call)
|
||||
switch (part.NodeType)
|
||||
{
|
||||
containsIndexers = true;
|
||||
var methodExpression = (MethodCallExpression)part;
|
||||
if (!IsSingleArgumentIndexer(methodExpression))
|
||||
{
|
||||
case ExpressionType.Call:
|
||||
var methodExpression = (MethodCallExpression)part;
|
||||
if (IsSingleArgumentIndexer(methodExpression))
|
||||
{
|
||||
containsIndexers = true;
|
||||
lastIsModel = false;
|
||||
length += "[99]".Length;
|
||||
part = methodExpression.Object;
|
||||
segmentCount++;
|
||||
trailingMemberExpressions = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported.
|
||||
part = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case ExpressionType.ArrayIndex:
|
||||
var binaryExpression = (BinaryExpression)part;
|
||||
|
||||
containsIndexers = true;
|
||||
lastIsModel = false;
|
||||
length += "[99]".Length;
|
||||
part = binaryExpression.Left;
|
||||
segmentCount++;
|
||||
trailingMemberExpressions = 0;
|
||||
break;
|
||||
|
||||
case ExpressionType.MemberAccess:
|
||||
var memberExpressionPart = (MemberExpression)part;
|
||||
var name = memberExpressionPart.Member.Name;
|
||||
|
||||
// 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 (name.Contains("__"))
|
||||
{
|
||||
// Exit loop.
|
||||
part = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastIsModel = string.Equals("model", name, StringComparison.OrdinalIgnoreCase);
|
||||
length += name.Length + 1;
|
||||
part = memberExpressionPart.Expression;
|
||||
segmentCount++;
|
||||
trailingMemberExpressions++;
|
||||
}
|
||||
break;
|
||||
|
||||
case ExpressionType.Parameter:
|
||||
// Unsupported but indicates previous member access was not the view's Model.
|
||||
lastIsModel = false;
|
||||
part = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported.
|
||||
part = null;
|
||||
break;
|
||||
}
|
||||
|
||||
InsertIndexerInvocationText(
|
||||
builder,
|
||||
methodExpression.Arguments.Single(),
|
||||
expression);
|
||||
|
||||
part = methodExpression.Object;
|
||||
}
|
||||
else if (part.NodeType == ExpressionType.ArrayIndex)
|
||||
}
|
||||
|
||||
// If name would start with ".model", then strip that part away.
|
||||
if (lastIsModel)
|
||||
{
|
||||
length -= ".model".Length;
|
||||
segmentCount--;
|
||||
trailingMemberExpressions--;
|
||||
}
|
||||
|
||||
// Trim the leading "." if present. The loop below special-cases the last property to avoid this addition.
|
||||
if (trailingMemberExpressions > 0)
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
Debug.Assert(segmentCount >= 0);
|
||||
if (segmentCount == 0)
|
||||
{
|
||||
Debug.Assert(!containsIndexers);
|
||||
if (expressionTextCache != null)
|
||||
{
|
||||
containsIndexers = true;
|
||||
var binaryExpression = (BinaryExpression)part;
|
||||
|
||||
InsertIndexerInvocationText(
|
||||
builder,
|
||||
binaryExpression.Right,
|
||||
expression);
|
||||
|
||||
part = binaryExpression.Left;
|
||||
expressionTextCache.Entries.TryAdd(expression, string.Empty);
|
||||
}
|
||||
else if (part.NodeType == ExpressionType.MemberAccess)
|
||||
{
|
||||
var memberExpressionPart = (MemberExpression)part;
|
||||
var name = memberExpressionPart.Member.Name;
|
||||
|
||||
// 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 (name.Contains("__"))
|
||||
{
|
||||
// Exit loop. Should have the entire name because previous MemberAccess has same name as the
|
||||
// leftmost expression node (a variable).
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(length);
|
||||
part = expression.Body;
|
||||
while (part != null && segmentCount > 0)
|
||||
{
|
||||
segmentCount--;
|
||||
switch (part.NodeType)
|
||||
{
|
||||
case ExpressionType.Call:
|
||||
Debug.Assert(containsIndexers);
|
||||
var methodExpression = (MethodCallExpression)part;
|
||||
|
||||
InsertIndexerInvocationText(builder, methodExpression.Arguments.Single(), expression);
|
||||
|
||||
part = methodExpression.Object;
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Insert(0, name);
|
||||
builder.Insert(0, '.');
|
||||
part = memberExpressionPart.Expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case ExpressionType.ArrayIndex:
|
||||
Debug.Assert(containsIndexers);
|
||||
var binaryExpression = (BinaryExpression)part;
|
||||
|
||||
InsertIndexerInvocationText(builder, binaryExpression.Right, expression);
|
||||
|
||||
part = binaryExpression.Left;
|
||||
break;
|
||||
|
||||
case ExpressionType.MemberAccess:
|
||||
var memberExpression = (MemberExpression)part;
|
||||
var name = memberExpression.Member.Name;
|
||||
Debug.Assert(!name.Contains("__"));
|
||||
|
||||
builder.Insert(0, name);
|
||||
if (segmentCount > 0)
|
||||
{
|
||||
// One or more parts to the left of this part are coming.
|
||||
builder.Insert(0, '.');
|
||||
}
|
||||
|
||||
part = memberExpression.Expression;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Should be unreachable due to handling in above loop.
|
||||
Debug.Assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If parts start with "model", then strip that part away.
|
||||
if (part == null || part.NodeType != ExpressionType.Parameter)
|
||||
{
|
||||
var text = builder.ToString();
|
||||
if (text.StartsWith(".model", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 6 is the length of the string ".model".
|
||||
builder.Remove(0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
// Trim the leading "." if present.
|
||||
builder.Replace(".", string.Empty, 0, 1);
|
||||
}
|
||||
|
||||
Debug.Assert(segmentCount == 0);
|
||||
expressionText = builder.ToString();
|
||||
|
||||
if (expressionTextCache != null && !containsIndexers)
|
||||
{
|
||||
expressionTextCache.Entries.TryAdd(expression, expressionText);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
public class ExpressionTextCache
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ConcurrentDictionary<LambdaExpression, string> Entries { get; } =
|
||||
public ConcurrentDictionary<LambdaExpression, string> Entries { get; } =
|
||||
new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
|
||||
|
||||
// This comparer is tightly coupled with the logic of ExpressionHelper.GetExpressionText.
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
return true;
|
||||
}
|
||||
// We will cache only pure member access expressions. Hence we compare two expressions
|
||||
// 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;
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -14,7 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
internal static class MvcViewFeaturesLoggerExtensions
|
||||
{
|
||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
private static readonly string[] EmptyArguments = new string[0];
|
||||
|
||||
private static readonly Action<ILogger, string, string[], Exception> _viewComponentExecuting;
|
||||
private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted;
|
||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
if (arguments == null || arguments.Length == 0)
|
||||
{
|
||||
return EmptyArguments;
|
||||
return EmptyArray<string>.Instance;
|
||||
}
|
||||
|
||||
var formattedArguments = new string[arguments.Length];
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||
{
|
||||
public static class ValidationHelpers
|
||||
{
|
||||
private static readonly ModelStateEntry[] EmptyModelStateEntries = new ModelStateEntry[0];
|
||||
|
||||
public static string GetModelErrorMessageOrDefault(ModelError modelError)
|
||||
{
|
||||
Debug.Assert(modelError != null);
|
||||
|
|
@ -80,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
return entries;
|
||||
}
|
||||
|
||||
return EmptyModelStateEntries;
|
||||
return EmptyArray<ModelStateEntry>.Instance;
|
||||
}
|
||||
|
||||
private static void Visit(
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// We check if expression is wrapped with conversion to object expression
|
||||
// and unwrap it if necessary, because Expression<Func<TModel, object>>
|
||||
// automatically creates a convert to object expression for expresions
|
||||
// automatically creates a convert to object expression for expressions
|
||||
// returning value types
|
||||
var unaryExpression = expression.Body as UnaryExpression;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
|
@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public class RemoteAttribute : ValidationAttribute, IClientModelValidator
|
||||
{
|
||||
private string _additionalFields = string.Empty;
|
||||
private string[] _additionalFieldsSplit = new string[0];
|
||||
private string[] _additionalFieldsSplit = EmptyArray<string>.Instance;
|
||||
private bool _checkedForLocalizer;
|
||||
private IStringLocalizer _stringLocalizer;
|
||||
|
||||
|
|
@ -275,7 +276,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
if (string.IsNullOrEmpty(original))
|
||||
{
|
||||
return new string[0];
|
||||
return EmptyArray<string>.Instance;
|
||||
}
|
||||
|
||||
var split = original
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return valid HTML 4.01 "id" attribute for an element with the given <paramref name="name"/>.
|
||||
/// Returns a valid HTML 4.01 "id" attribute value for an element with the given <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The original element name.</param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
|
|
@ -99,9 +99,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
/// <paramref name="name"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Valid HTML 4.01 "id" attribute for an element with the given <paramref name="name"/>.
|
||||
/// Valid HTML 4.01 "id" attribute value for an element with the given <paramref name="name"/>.
|
||||
/// </returns>
|
||||
/// <remarks>Valid "id" attributes are defined in http://www.w3.org/TR/html401/types.html#type-id</remarks>
|
||||
/// <remarks>
|
||||
/// Valid "id" attributes are defined in http://www.w3.org/TR/html401/types.html#type-id
|
||||
/// </remarks>
|
||||
public static string CreateSanitizedId(string name, string invalidCharReplacement)
|
||||
{
|
||||
if (invalidCharReplacement == null)
|
||||
|
|
@ -205,7 +207,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
writer.Write(" ");
|
||||
writer.Write(key);
|
||||
writer.Write("=\"");
|
||||
encoder.Encode(writer, attribute.Value ?? string.Empty);
|
||||
if (attribute.Value != null)
|
||||
{
|
||||
encoder.Encode(writer, attribute.Value);
|
||||
}
|
||||
|
||||
writer.Write("\"");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
Writer = writer;
|
||||
|
||||
FormContext = new FormContext();
|
||||
|
||||
|
||||
ClientValidationEnabled = htmlHelperOptions.ClientValidationEnabled;
|
||||
Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode;
|
||||
ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement;
|
||||
|
|
@ -228,7 +228,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
public FormContext GetFormContextForClientValidation()
|
||||
{
|
||||
return (ClientValidationEnabled) ? FormContext : null;
|
||||
return ClientValidationEnabled ? FormContext : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -558,6 +558,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
{
|
||||
_attributes = attributes;
|
||||
}
|
||||
|
||||
public int Count => _attributes.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
|
|
|||
|
|
@ -404,10 +404,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
var resolvedLabelText = labelText ??
|
||||
modelExplorer.Metadata.DisplayName ??
|
||||
modelExplorer.Metadata.PropertyName;
|
||||
if (resolvedLabelText == null)
|
||||
if (resolvedLabelText == null && expression != null)
|
||||
{
|
||||
resolvedLabelText =
|
||||
string.IsNullOrEmpty(expression) ? string.Empty : expression.Split('.').Last();
|
||||
var index = expression.LastIndexOf('.');
|
||||
if (index == -1)
|
||||
{
|
||||
// Expression does not contain a dot separator.
|
||||
resolvedLabelText = expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedLabelText = expression.Substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(resolvedLabelText))
|
||||
|
|
@ -1289,8 +1297,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
object typeAttributeValue;
|
||||
if (htmlAttributes != null && htmlAttributes.TryGetValue("type", out typeAttributeValue))
|
||||
{
|
||||
if (string.Equals(typeAttributeValue.ToString(), "file", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(typeAttributeValue.ToString(), "image", StringComparison.OrdinalIgnoreCase))
|
||||
var typeAttributeString = typeAttributeValue.ToString();
|
||||
if (string.Equals(typeAttributeString, "file", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(typeAttributeString, "image", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 'value' attribute is not needed for 'file' and 'image' input types.
|
||||
addValue = false;
|
||||
|
|
|
|||
|
|
@ -181,16 +181,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <summary>
|
||||
/// Creates a dictionary of HTML attributes from the input object,
|
||||
/// translating underscores to dashes in each public instance property.
|
||||
///
|
||||
/// If the object is already an <see cref="IDictionary{String, Object}"/> instance, then it is
|
||||
/// returned as-is.
|
||||
/// </summary>
|
||||
/// <param name="htmlAttributes">Anonymous object describing HTML attributes.</param>
|
||||
/// <returns>A dictionary that represents HTML attributes.</returns>
|
||||
/// <remarks>
|
||||
/// If the object is already an <see cref="IDictionary{String, Object}"/> instance, then a shallow copy is
|
||||
/// returned.
|
||||
/// <example>
|
||||
/// <c>new { data_name="value" }</c> will translate to the entry <c>{ "data-name", "value" }</c>
|
||||
/// in the resulting dictionary.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <param name="htmlAttributes">Anonymous object describing HTML attributes.</param>
|
||||
/// <returns>A dictionary that represents HTML attributes.</returns>
|
||||
/// </remarks>
|
||||
public static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
|
||||
{
|
||||
var dictionary = htmlAttributes as IDictionary<string, object>;
|
||||
|
|
@ -799,13 +800,21 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
// we want to fall back to the field name rather than the ModelType.
|
||||
// This is similar to how the GenerateLabel get the text of a label.
|
||||
var resolvedDisplayName = modelExplorer.Metadata.DisplayName ?? modelExplorer.Metadata.PropertyName;
|
||||
if (resolvedDisplayName == null)
|
||||
if (resolvedDisplayName == null && expression != null)
|
||||
{
|
||||
resolvedDisplayName =
|
||||
string.IsNullOrEmpty(expression) ? string.Empty : expression.Split('.').Last();
|
||||
var index = expression.LastIndexOf('.');
|
||||
if (index == -1)
|
||||
{
|
||||
// Expression does not contain a dot separator.
|
||||
resolvedDisplayName = expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedDisplayName = expression.Substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedDisplayName;
|
||||
return resolvedDisplayName ?? string.Empty;
|
||||
}
|
||||
|
||||
protected virtual string GenerateDisplayText(ModelExplorer modelExplorer)
|
||||
|
|
@ -978,14 +987,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
bool useViewData,
|
||||
object htmlAttributes)
|
||||
{
|
||||
var tagBuilder =
|
||||
_htmlGenerator.GenerateHidden(
|
||||
ViewContext,
|
||||
modelExplorer,
|
||||
expression,
|
||||
value,
|
||||
useViewData,
|
||||
htmlAttributes);
|
||||
var tagBuilder = _htmlGenerator.GenerateHidden(
|
||||
ViewContext,
|
||||
modelExplorer,
|
||||
expression,
|
||||
value,
|
||||
useViewData,
|
||||
htmlAttributes);
|
||||
if (tagBuilder == null)
|
||||
{
|
||||
return HtmlString.Empty;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
private object _model;
|
||||
private Func<object, object> _modelAccessor;
|
||||
private Type _modelType;
|
||||
private List<ModelExplorer> _properties;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,8 +29,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
|
||||
/// <param name="model">The model object. May be <c>null</c>.</param>
|
||||
public ModelExplorer(
|
||||
IModelMetadataProvider metadataProvider,
|
||||
ModelMetadata metadata,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
ModelMetadata metadata,
|
||||
object model)
|
||||
{
|
||||
if (metadataProvider == null)
|
||||
|
|
@ -172,29 +171,23 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
{
|
||||
get
|
||||
{
|
||||
if (_modelType == null)
|
||||
if (Metadata.IsNullableValueType)
|
||||
{
|
||||
if (Model == null)
|
||||
{
|
||||
// If the model is null, then use the declared model type;
|
||||
_modelType = Metadata.ModelType;
|
||||
}
|
||||
else if (Metadata.IsNullableValueType)
|
||||
{
|
||||
// We have a model, but if it's a nullable value type, then Model.GetType() will return
|
||||
// the non-nullable type (int? -> int). Since it's a value type, there's no subclassing,
|
||||
// just go with the declared type.
|
||||
_modelType = Metadata.ModelType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have a model, and it's not a nullable so use the runtime type to handle
|
||||
// cases where the model is a subclass of the declared type and has extra data.
|
||||
_modelType = Model.GetType();
|
||||
}
|
||||
// We have a model, but if it's a nullable value type, then Model.GetType() will return
|
||||
// the non-nullable type (int? -> int). Since it's a value type, there's no subclassing,
|
||||
// just go with the declared type.
|
||||
return Metadata.ModelType;
|
||||
}
|
||||
|
||||
return _modelType;
|
||||
if (Model == null)
|
||||
{
|
||||
// If the model is null, then use the declared model type;
|
||||
return Metadata.ModelType;
|
||||
}
|
||||
|
||||
// We have a model, and it's not a nullable, so use the runtime type to handle
|
||||
// cases where the model is a subclass of the declared type and has extra data.
|
||||
return Model.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Includes a <see cref="ModelExplorer"/> for each property of the <see cref="ModelMetadata"/>
|
||||
/// for <see cref="ModelType"/>.
|
||||
/// for <see cref="ModelType"/>.
|
||||
/// </remarks>
|
||||
public IEnumerable<ModelExplorer> Properties
|
||||
{
|
||||
|
|
@ -260,8 +253,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
return Properties.FirstOrDefault(p => string.Equals(
|
||||
p.Metadata.PropertyName,
|
||||
name,
|
||||
p.Metadata.PropertyName,
|
||||
name,
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
|
|
@ -383,7 +376,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A <see cref="ModelExplorer"/> created by
|
||||
/// A <see cref="ModelExplorer"/> created by
|
||||
/// <see cref="GetExplorerForExpression(Type, Func{object, object})"/>
|
||||
/// represents the result of executing an arbitrary expression against the model contained
|
||||
/// in the current <see cref="ModelExplorer"/> instance.
|
||||
|
|
@ -411,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A <see cref="ModelExplorer"/> created by
|
||||
/// A <see cref="ModelExplorer"/> created by
|
||||
/// <see cref="GetExplorerForExpression(ModelMetadata, Func{object, object})"/>
|
||||
/// represents the result of executing an arbitrary expression against the model contained
|
||||
/// in the current <see cref="ModelExplorer"/> instance.
|
||||
|
|
@ -444,7 +437,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
private ModelExplorer CreateExplorerForProperty(
|
||||
ModelMetadata propertyMetadata,
|
||||
ModelMetadata propertyMetadata,
|
||||
PropertyHelper propertyHelper)
|
||||
{
|
||||
if (propertyHelper == null)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Bson;
|
||||
|
|
@ -156,7 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
{
|
||||
// Since we call Save() after the response has been sent, we need to initialize an empty session
|
||||
// so that it is established before the headers are sent.
|
||||
session.Set(TempDataSessionStateKey, new byte[] { });
|
||||
session.Set(TempDataSessionStateKey, EmptyArray<byte>.Instance);
|
||||
}
|
||||
|
||||
return tempDataDictionary ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
|
||||
if (bufferScope == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(_bufferScope));
|
||||
throw new ArgumentNullException(nameof(bufferScope));
|
||||
}
|
||||
|
||||
if (viewContext == null)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
|
|
@ -98,57 +99,58 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
private static ViewDataInfo InnerEvalComplexExpression(object indexableObject, string expression)
|
||||
{
|
||||
foreach (var expressionPair in GetRightToLeftExpressions(expression))
|
||||
Debug.Assert(expression != null);
|
||||
var leftExpression = expression;
|
||||
do
|
||||
{
|
||||
var subExpression = expressionPair.Left;
|
||||
var postExpression = expressionPair.Right;
|
||||
|
||||
var subTargetInfo = GetPropertyValue(indexableObject, subExpression);
|
||||
if (subTargetInfo != null)
|
||||
var targetInfo = GetPropertyValue(indexableObject, leftExpression);
|
||||
if (targetInfo != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(postExpression))
|
||||
if (leftExpression.Length == expression.Length)
|
||||
{
|
||||
return subTargetInfo;
|
||||
// Nothing remaining in expression after leftExpression.
|
||||
return targetInfo;
|
||||
}
|
||||
|
||||
if (subTargetInfo.Value != null)
|
||||
if (targetInfo.Value != null)
|
||||
{
|
||||
var potential = InnerEvalComplexExpression(subTargetInfo.Value, postExpression);
|
||||
if (potential != null)
|
||||
var rightExpression = expression.Substring(leftExpression.Length + 1);
|
||||
targetInfo = InnerEvalComplexExpression(targetInfo.Value, rightExpression);
|
||||
if (targetInfo != null)
|
||||
{
|
||||
return potential;
|
||||
return targetInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leftExpression = GetNextShorterExpression(leftExpression);
|
||||
}
|
||||
while (!string.IsNullOrEmpty(leftExpression));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Produces an enumeration of combinations of property names given a complex expression in the following order:
|
||||
// this["one.two.three.four"]
|
||||
// this["one.two.three][four"]
|
||||
// this["one.two][three.four"]
|
||||
// this["one][two.three.four"]
|
||||
// Given "one.two.three.four" initially, calls return
|
||||
// "one.two.three"
|
||||
// "one.two"
|
||||
// "one"
|
||||
// ""
|
||||
// Recursion of InnerEvalComplexExpression() further sub-divides these cases to cover the full set of
|
||||
// combinations shown in Eval(ViewDataDictionary, string) comments.
|
||||
private static IEnumerable<ExpressionPair> GetRightToLeftExpressions(string expression)
|
||||
private static string GetNextShorterExpression(string expression)
|
||||
{
|
||||
yield return new ExpressionPair(expression, string.Empty);
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var lastDot = expression.LastIndexOf('.');
|
||||
|
||||
var subExpression = expression;
|
||||
var postExpression = string.Empty;
|
||||
|
||||
while (lastDot > -1)
|
||||
if (lastDot == -1)
|
||||
{
|
||||
subExpression = expression.Substring(0, lastDot);
|
||||
postExpression = expression.Substring(lastDot + 1);
|
||||
yield return new ExpressionPair(subExpression, postExpression);
|
||||
|
||||
lastDot = subExpression.LastIndexOf('.');
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return expression.Substring(startIndex: 0, length: lastDot);
|
||||
}
|
||||
|
||||
private static ViewDataInfo GetIndexedPropertyValue(object indexableObject, string key)
|
||||
|
|
@ -205,17 +207,5 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
return new ViewDataInfo(container, propertyInfo);
|
||||
}
|
||||
|
||||
private struct ExpressionPair
|
||||
{
|
||||
public readonly string Left;
|
||||
public readonly string Right;
|
||||
|
||||
public ExpressionPair(string left, string right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
public class OverloadActionConstraint : IActionConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Order { get; } = int.MaxValue;
|
||||
public int Order => int.MaxValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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 Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
generator.Verify();
|
||||
Assert.Equal("a", output.TagName);
|
||||
Assert.Empty(output.Attributes);
|
||||
Assert.True(output.Content.GetContent().Length == 0);
|
||||
Assert.Empty(output.Content.GetContent());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
generator.Verify();
|
||||
Assert.Equal("a", output.TagName);
|
||||
Assert.Empty(output.Attributes);
|
||||
Assert.True(output.Content.GetContent().Length == 0);
|
||||
Assert.Empty(output.Content.GetContent());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
|
||||
Assert.Equal("a", output.TagName);
|
||||
Assert.Empty(output.Attributes);
|
||||
Assert.True(output.Content.GetContent().Length == 0);
|
||||
Assert.Empty(output.Content.GetContent());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
|
||||
Assert.Equal("a", output.TagName);
|
||||
Assert.Empty(output.Attributes);
|
||||
Assert.True(output.Content.GetContent().Length == 0);
|
||||
Assert.Empty(output.Content.GetContent());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
|
||||
Assert.Equal("a", output.TagName);
|
||||
Assert.Empty(output.Attributes);
|
||||
Assert.True(output.Content.GetContent().Length == 0);
|
||||
Assert.Empty(output.Content.GetContent());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -149,7 +149,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[InlineData("\r\n \r\n", "\r\n Something Else \r\n", "\r\n Something Else \r\n")]
|
||||
[InlineData("\r\n \r\n", "Some Content", "Some Content")]
|
||||
public async Task ProcessAsync_DoesNotOverrideOutputContent(
|
||||
string childContent, string outputContent, string expectedOutputContent)
|
||||
string childContent,
|
||||
string outputContent,
|
||||
string expectedOutputContent)
|
||||
{
|
||||
// Arrange
|
||||
var tagBuilder = new TagBuilder("span2");
|
||||
|
|
@ -209,7 +211,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[InlineData("Content of validation message", "Content of validation message")]
|
||||
[InlineData("\r\n \r\n", "New HTML")]
|
||||
public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage(
|
||||
string childContent, string expectedOutputContent)
|
||||
string childContent,
|
||||
string expectedOutputContent)
|
||||
{
|
||||
// Arrange
|
||||
var tagBuilder = new TagBuilder("span2");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
// 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 Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit.Sdk;
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
(Expression<Func<TestModel, string>>)(m => value)
|
||||
},
|
||||
{
|
||||
// These two expressions are not actually equivalent. However ExpressionHelper returns
|
||||
// string.Empty for these two expressions and hence they are considered as equivalent by the
|
||||
// These two expressions are not actually equivalent. However ExpressionHelper returns
|
||||
// string.Empty for these two expressions and hence they are considered as equivalent by the
|
||||
// cache.
|
||||
(Expression<Func<TestModel, string>>)(m => Model),
|
||||
(Expression<Func<TestModel, TestModel>>)(m => m)
|
||||
|
|
@ -206,10 +206,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
(Expression<Func<IDictionary<string, TestModel>, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory),
|
||||
(Expression<Func<TestModel, Category>>)(model => model.SelectedCategory)
|
||||
},
|
||||
{
|
||||
(Expression<Func<IList<TestModel>, Category>>)(model => model[2].SelectedCategory),
|
||||
(Expression<Func<IList<TestModel>, Category>>)(model => model[2].SelectedCategory)
|
||||
},
|
||||
{
|
||||
(Expression<Func<TestModel, string>>)(m => Model),
|
||||
(Expression<Func<TestModel, string>>)(m => m.Model)
|
||||
|
|
@ -226,10 +222,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
(Expression<Func<TestModel, string>>)(m => key),
|
||||
(Expression<Func<TestModel, string>>)(m => value)
|
||||
},
|
||||
{
|
||||
(Expression<Func<IDictionary<string, TestModel>, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory),
|
||||
(Expression<Func<IDictionary<string, TestModel>, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -256,7 +248,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
||||
|
||||
// Assert
|
||||
Assert.Same(text1, text2); // Cached
|
||||
Assert.Same(text1, text2); // cached
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -270,6 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(text1, text2, StringComparer.Ordinal);
|
||||
Assert.NotSame(text1, text2); // not cached
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +277,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
||||
|
||||
// Assert
|
||||
Assert.Same(text1, text2);
|
||||
Assert.Same(text1, text2); // cached
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -298,6 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(text1, text2, StringComparer.Ordinal);
|
||||
Assert.NotSame(text1, text2);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
get
|
||||
{
|
||||
var expected1 = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
yield return new object[] { new Dictionary<string, object> { { "baz", "BazValue" } }, expected1 };
|
||||
yield return new object[] { new { baz = "BazValue" }, expected1 };
|
||||
|
||||
var expected2 = @"<input foo-baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
yield return new object[] { new Dictionary<string, object> { { "foo-baz", "BazValue" } }, expected2 };
|
||||
yield return new object[] { new { foo_baz = "BazValue" }, expected2 };
|
||||
}
|
||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
// Arrange
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" key=""HtmlEncode[[value]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[test]]"" />";
|
||||
@"value=""HtmlEncode[[test]]"" />";
|
||||
var attributes = new { key = "value" };
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
// Arrange
|
||||
var expected = @"<input data-key=""HtmlEncode[[value]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[test]]"" />";
|
||||
@"value=""HtmlEncode[[test]]"" />";
|
||||
var attributes = new Dictionary<string, object> { { "data-key", "value" } };
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
// Arrange
|
||||
var expected = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[keyNotFound]]"" name=""HtmlEncode[[keyNotFound]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value="""" />";
|
||||
@"value="""" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
||||
var attributes = new Dictionary<string, object> { { "baz", "BazValue" } };
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
public void LabelHelpers_DisplayPropertyName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedLabel = "<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
|
||||
// Act
|
||||
|
|
@ -45,14 +46,15 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
var labelForResult = helper.LabelFor(m => m.Property1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>", HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||
Assert.Equal("<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>", HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelHelpers_DisplayPropertyName_ForNestedProperty()
|
||||
{
|
||||
// Arrange
|
||||
var expectedLabel = "<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper<OuterClass>(model: null);
|
||||
|
||||
// Act
|
||||
|
|
@ -60,8 +62,8 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
var labelForResult = helper.LabelFor(m => m.Inner.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>", HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||
Assert.Equal("<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>", HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
Expression<Func<PasswordModel, string>> expression,
|
||||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var model = new PasswordModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
helper.ViewData.TemplateInfo.HtmlFieldPrefix = "pre";
|
||||
|
|
@ -489,7 +489,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
// Act
|
||||
var result = helper.PasswordFor(expression);
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[Fact]
|
||||
public void TextAreaFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var model = new TextAreaModelWithAPlaceholder();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var textArea = helper.TextAreaFor(m => m.Property1);
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
||||
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
|
@ -30,14 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[Fact]
|
||||
public void TextAreaFor_DoesNotGeneratePlaceholderAttribute_WhenNoPlaceholderPresentInModel()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var model = new TextAreaModelWithoutAPlaceholder();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var textArea = helper.TextAreaFor(m => m.Property1);
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
||||
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[InlineData("number")]
|
||||
public void TextBoxFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid(string type)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var model = new TextBoxModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
||||
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[InlineData("file")]
|
||||
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var model = new TextBoxModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
||||
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue