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; }
|
private bool HasRecordedMaxModelError { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -625,7 +625,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public abstract class ModelStateEntry
|
public abstract class ModelStateEntry
|
||||||
{
|
{
|
||||||
private ModelErrorCollection _errors;
|
private ModelErrorCollection _errors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the raw value from the request associated with this entry.
|
/// Gets the raw value from the request associated with this entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
#if NETSTANDARD1_6
|
#if NETSTANDARD1_6
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -83,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(original))
|
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));
|
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"/>.
|
/// Initializes a new instance of <see cref="ChallengeResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ChallengeResult()
|
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
|
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
|
||||||
/// challenge.</param>
|
/// challenge.</param>
|
||||||
public ChallengeResult(AuthenticationProperties properties)
|
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
|
// The value used is a non default value so that it avoids getting mixed with other action constraints
|
||||||
// with default order.
|
// with default order.
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
|
int IActionConstraint.Order => ConsumesActionConstraintOrder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the supported request content types. Used to select an action when there would otherwise be
|
/// 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; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsReusable { get; } = true;
|
public bool IsReusable => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// Initializes a new instance of <see cref="ForbidResult"/>.
|
/// Initializes a new instance of <see cref="ForbidResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ForbidResult()
|
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
|
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
|
||||||
/// challenge.</param>
|
/// challenge.</param>
|
||||||
public ForbidResult(AuthenticationProperties properties)
|
public ForbidResult(AuthenticationProperties properties)
|
||||||
: this(new string[] { }, properties)
|
: this(EmptyArray<string>.Instance, properties)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
@ -18,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// If the format already exists, the media type will be overwritten with the new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="format">The format value.</param>
|
/// <param name="format">The format value.</param>
|
||||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// If the format already exists, the media type will be overwritten with the new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="format">The format value.</param>
|
/// <param name="format">The format value.</param>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ClientValidatorCache
|
public class ClientValidatorCache
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<IClientModelValidator> EmptyArray = new IClientModelValidator[0];
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
||||||
|
|
||||||
public IReadOnlyList<IClientModelValidator> GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider)
|
public IReadOnlyList<IClientModelValidator> GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider)
|
||||||
|
|
@ -106,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
return EmptyArray;
|
return EmptyArray<IClientModelValidator>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validators = new IClientModelValidator[count];
|
var validators = new IClientModelValidator[count];
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
@ -13,8 +12,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ControllerActionInvokerCache
|
public class ControllerActionInvokerCache
|
||||||
{
|
{
|
||||||
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
|
|
||||||
|
|
||||||
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||||
private readonly IFilterProvider[] _filterProviders;
|
private readonly IFilterProvider[] _filterProviders;
|
||||||
|
|
||||||
|
|
@ -134,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
return EmptyFilterArray;
|
return EmptyArray<IFilterMetadata>.Instance;
|
||||||
}
|
}
|
||||||
else
|
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)
|
private void SetContentType(ActionContext context, FileResult result)
|
||||||
{
|
{
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
response.ContentType = result.ContentType.ToString();
|
response.ContentType = result.ContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ILogger CreateLogger<T>(ILoggerFactory factory)
|
protected static ILogger CreateLogger<T>(ILoggerFactory factory)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PrefixContainer
|
public class PrefixContainer
|
||||||
{
|
{
|
||||||
private static readonly string[] EmptyArray = new string[0];
|
|
||||||
private static readonly char[] Delimiters = new char[] { '[', '.' };
|
private static readonly char[] Delimiters = new char[] { '[', '.' };
|
||||||
|
|
||||||
private readonly ICollection<string> _originalValues;
|
private readonly ICollection<string> _originalValues;
|
||||||
|
|
@ -31,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (_originalValues.Count == 0)
|
if (_originalValues.Count == 0)
|
||||||
{
|
{
|
||||||
_sortedValues = EmptyArray;
|
_sortedValues = EmptyArray<string>.Instance;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ValidatorCache
|
public class ValidatorCache
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<IModelValidator> EmptyArray = new IModelValidator[0];
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();
|
||||||
|
|
||||||
public IReadOnlyList<IModelValidator> GetValidators(ModelMetadata metadata, IModelValidatorProvider validatorProvider)
|
public IReadOnlyList<IModelValidator> GetValidators(ModelMetadata metadata, IModelValidatorProvider validatorProvider)
|
||||||
|
|
@ -105,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
return EmptyArray;
|
return EmptyArray<IModelValidator>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validators = new IModelValidator[count];
|
var validators = new IModelValidator[count];
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
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.");
|
Debug.Assert(targetType == typeof(TElement[]), "GenericModelBinder only creates this binder for arrays.");
|
||||||
|
|
||||||
return new TElement[0];
|
return EmptyArray<TElement>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
|
|
@ -36,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
model = new EmptyFormCollection();
|
model = new EmptyFormCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.Result = ModelBindingResult.Success(model);
|
bindingContext.Result = ModelBindingResult.Success(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Localization
|
namespace Microsoft.AspNetCore.Mvc.Localization
|
||||||
{
|
{
|
||||||
|
|
@ -14,11 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.Localization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LocalizedHtmlString : IHtmlContent
|
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;
|
private readonly object[] _arguments;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -27,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization
|
||||||
/// <param name="name">The name of the string resource.</param>
|
/// <param name="name">The name of the string resource.</param>
|
||||||
/// <param name="value">The string resource.</param>
|
/// <param name="value">The string resource.</param>
|
||||||
public LocalizedHtmlString(string name, string value)
|
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="value">The string resource.</param>
|
||||||
/// <param name="isResourceNotFound">A flag that indicates if the resource is not found.</param>
|
/// <param name="isResourceNotFound">A flag that indicates if the resource is not found.</param>
|
||||||
public LocalizedHtmlString(string name, string value, bool isResourceNotFound)
|
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.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.Extensions.Primitives;
|
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="relativePath">Path of the view file relative to the application base.</param>
|
||||||
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
||||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult)
|
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"/>.
|
/// Gets a delegate that creates an instance of the <see cref="IRazorPage"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<IRazorPage> PageFactory { get; }
|
public Func<IRazorPage> PageFactory { get; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
@ -31,8 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
|
|
||||||
private const string ControllerKey = "controller";
|
private const string ControllerKey = "controller";
|
||||||
private const string AreaKey = "area";
|
private const string AreaKey = "area";
|
||||||
private static readonly ViewLocationCacheItem[] EmptyViewStartLocationCacheItems =
|
|
||||||
new ViewLocationCacheItem[0];
|
|
||||||
private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20);
|
private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20);
|
||||||
|
|
||||||
private readonly IRazorPageFactoryProvider _pageFactory;
|
private readonly IRazorPageFactoryProvider _pageFactory;
|
||||||
|
|
@ -415,7 +414,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
// Only need to lookup _ViewStarts for the main page.
|
// Only need to lookup _ViewStarts for the main page.
|
||||||
var viewStartPages = isMainPage ?
|
var viewStartPages = isMainPage ?
|
||||||
GetViewStartPages(relativePath, expirationTokens) :
|
GetViewStartPages(relativePath, expirationTokens) :
|
||||||
EmptyViewStartLocationCacheItems;
|
EmptyArray<ViewLocationCacheItem>.Instance;
|
||||||
|
|
||||||
return new ViewLocationCacheResult(
|
return new ViewLocationCacheResult(
|
||||||
new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),
|
new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.FileSystemGlobbing;
|
using Microsoft.Extensions.FileSystemGlobbing;
|
||||||
|
|
@ -18,13 +19,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GlobbingUrlBuilder
|
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.
|
// Valid whitespace characters defined by the HTML5 spec.
|
||||||
private static readonly char[] ValidAttributeWhitespaceChars =
|
private static readonly char[] ValidAttributeWhitespaceChars =
|
||||||
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
||||||
|
|
@ -116,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(include))
|
if (string.IsNullOrEmpty(include))
|
||||||
{
|
{
|
||||||
return EmptyList;
|
return EmptyArray<string>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = new GlobbingUrlKey(include, exclude);
|
var cacheKey = new GlobbingUrlKey(include, exclude);
|
||||||
|
|
@ -130,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
|
||||||
var includeEnumerator = includeTokenizer.GetEnumerator();
|
var includeEnumerator = includeTokenizer.GetEnumerator();
|
||||||
if (!includeEnumerator.MoveNext())
|
if (!includeEnumerator.MoveNext())
|
||||||
{
|
{
|
||||||
return EmptyList;
|
return EmptyArray<string>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new MemoryCacheEntryOptions();
|
var options = new MemoryCacheEntryOptions();
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||||
|
|
||||||
var fieldNameBase = oldPrefix;
|
|
||||||
var result = new HtmlContentBuilder();
|
var result = new HtmlContentBuilder();
|
||||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||||
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
||||||
|
|
@ -138,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
container: htmlHelper.ViewData.ModelExplorer,
|
container: htmlHelper.ViewData.ModelExplorer,
|
||||||
metadata: itemMetadata,
|
metadata: itemMetadata,
|
||||||
model: item);
|
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(
|
var templateBuilder = new TemplateBuilder(
|
||||||
viewEngine,
|
viewEngine,
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||||
|
|
||||||
var fieldNameBase = oldPrefix;
|
|
||||||
var result = new HtmlContentBuilder();
|
var result = new HtmlContentBuilder();
|
||||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||||
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
|
||||||
|
|
@ -105,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
container: htmlHelper.ViewData.ModelExplorer,
|
container: htmlHelper.ViewData.ModelExplorer,
|
||||||
metadata: itemMetadata,
|
metadata: itemMetadata,
|
||||||
model: item);
|
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(
|
var templateBuilder = new TemplateBuilder(
|
||||||
viewEngine,
|
viewEngine,
|
||||||
|
|
|
||||||
|
|
@ -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.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
@ -37,86 +38,166 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
return expressionText;
|
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 containsIndexers = false;
|
||||||
|
var lastIsModel = false;
|
||||||
|
var length = 0;
|
||||||
|
var segmentCount = 0;
|
||||||
|
var trailingMemberExpressions = 0;
|
||||||
|
|
||||||
var part = expression.Body;
|
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)
|
while (part != null)
|
||||||
{
|
{
|
||||||
if (part.NodeType == ExpressionType.Call)
|
switch (part.NodeType)
|
||||||
{
|
{
|
||||||
containsIndexers = true;
|
case ExpressionType.Call:
|
||||||
var methodExpression = (MethodCallExpression)part;
|
var methodExpression = (MethodCallExpression)part;
|
||||||
if (!IsSingleArgumentIndexer(methodExpression))
|
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.
|
// Unsupported.
|
||||||
|
part = null;
|
||||||
break;
|
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;
|
expressionTextCache.Entries.TryAdd(expression, string.Empty);
|
||||||
var binaryExpression = (BinaryExpression)part;
|
|
||||||
|
|
||||||
InsertIndexerInvocationText(
|
|
||||||
builder,
|
|
||||||
binaryExpression.Right,
|
|
||||||
expression);
|
|
||||||
|
|
||||||
part = binaryExpression.Left;
|
|
||||||
}
|
}
|
||||||
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-
|
return string.Empty;
|
||||||
// or Razor-generated e.g. the name of a field in a delegate's generated class.
|
}
|
||||||
if (name.Contains("__"))
|
|
||||||
{
|
var builder = new StringBuilder(length);
|
||||||
// Exit loop. Should have the entire name because previous MemberAccess has same name as the
|
part = expression.Body;
|
||||||
// leftmost expression node (a variable).
|
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;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
builder.Insert(0, name);
|
case ExpressionType.ArrayIndex:
|
||||||
builder.Insert(0, '.');
|
Debug.Assert(containsIndexers);
|
||||||
part = memberExpressionPart.Expression;
|
var binaryExpression = (BinaryExpression)part;
|
||||||
}
|
|
||||||
else
|
InsertIndexerInvocationText(builder, binaryExpression.Right, expression);
|
||||||
{
|
|
||||||
break;
|
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.
|
Debug.Assert(segmentCount == 0);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
expressionText = builder.ToString();
|
expressionText = builder.ToString();
|
||||||
|
|
||||||
if (expressionTextCache != null && !containsIndexers)
|
if (expressionTextCache != null && !containsIndexers)
|
||||||
{
|
{
|
||||||
expressionTextCache.Entries.TryAdd(expression, expressionText);
|
expressionTextCache.Entries.TryAdd(expression, expressionText);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
public class ExpressionTextCache
|
public class ExpressionTextCache
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConcurrentDictionary<LambdaExpression, string> Entries { get; } =
|
public ConcurrentDictionary<LambdaExpression, string> Entries { get; } =
|
||||||
new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
|
new ConcurrentDictionary<LambdaExpression, string>(LambdaExpressionComparer.Instance);
|
||||||
|
|
||||||
// This comparer is tightly coupled with the logic of ExpressionHelper.GetExpressionText.
|
// This comparer is tightly coupled with the logic of ExpressionHelper.GetExpressionText.
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
return true;
|
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.
|
// to be equal only if they are identical member access expressions.
|
||||||
var expression1 = lambdaExpression1.Body;
|
var expression1 = lambdaExpression1.Body;
|
||||||
var expression2 = lambdaExpression2.Body;
|
var expression2 = lambdaExpression2.Body;
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -14,7 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
internal static class MvcViewFeaturesLoggerExtensions
|
internal static class MvcViewFeaturesLoggerExtensions
|
||||||
{
|
{
|
||||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
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, string[], Exception> _viewComponentExecuting;
|
||||||
private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted;
|
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)
|
if (arguments == null || arguments.Length == 0)
|
||||||
{
|
{
|
||||||
return EmptyArguments;
|
return EmptyArray<string>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var formattedArguments = new string[arguments.Length];
|
var formattedArguments = new string[arguments.Length];
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
public static class ValidationHelpers
|
public static class ValidationHelpers
|
||||||
{
|
{
|
||||||
private static readonly ModelStateEntry[] EmptyModelStateEntries = new ModelStateEntry[0];
|
|
||||||
|
|
||||||
public static string GetModelErrorMessageOrDefault(ModelError modelError)
|
public static string GetModelErrorMessageOrDefault(ModelError modelError)
|
||||||
{
|
{
|
||||||
Debug.Assert(modelError != null);
|
Debug.Assert(modelError != null);
|
||||||
|
|
@ -80,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
return EmptyModelStateEntries;
|
return EmptyArray<ModelStateEntry>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Visit(
|
private static void Visit(
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
// We check if expression is wrapped with conversion to object expression
|
// We check if expression is wrapped with conversion to object expression
|
||||||
// and unwrap it if necessary, because Expression<Func<TModel, object>>
|
// 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
|
// returning value types
|
||||||
var unaryExpression = expression.Body as UnaryExpression;
|
var unaryExpression = expression.Body as UnaryExpression;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public class RemoteAttribute : ValidationAttribute, IClientModelValidator
|
public class RemoteAttribute : ValidationAttribute, IClientModelValidator
|
||||||
{
|
{
|
||||||
private string _additionalFields = string.Empty;
|
private string _additionalFields = string.Empty;
|
||||||
private string[] _additionalFieldsSplit = new string[0];
|
private string[] _additionalFieldsSplit = EmptyArray<string>.Instance;
|
||||||
private bool _checkedForLocalizer;
|
private bool _checkedForLocalizer;
|
||||||
private IStringLocalizer _stringLocalizer;
|
private IStringLocalizer _stringLocalizer;
|
||||||
|
|
||||||
|
|
@ -275,7 +276,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(original))
|
if (string.IsNullOrEmpty(original))
|
||||||
{
|
{
|
||||||
return new string[0];
|
return EmptyArray<string>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var split = original
|
var split = original
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="name">The original element name.</param>
|
/// <param name="name">The original element name.</param>
|
||||||
/// <param name="invalidCharReplacement">
|
/// <param name="invalidCharReplacement">
|
||||||
|
|
@ -99,9 +99,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
/// <paramref name="name"/>.
|
/// <paramref name="name"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>
|
/// <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>
|
/// </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)
|
public static string CreateSanitizedId(string name, string invalidCharReplacement)
|
||||||
{
|
{
|
||||||
if (invalidCharReplacement == null)
|
if (invalidCharReplacement == null)
|
||||||
|
|
@ -205,7 +207,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
writer.Write(" ");
|
writer.Write(" ");
|
||||||
writer.Write(key);
|
writer.Write(key);
|
||||||
writer.Write("=\"");
|
writer.Write("=\"");
|
||||||
encoder.Encode(writer, attribute.Value ?? string.Empty);
|
if (attribute.Value != null)
|
||||||
|
{
|
||||||
|
encoder.Encode(writer, attribute.Value);
|
||||||
|
}
|
||||||
|
|
||||||
writer.Write("\"");
|
writer.Write("\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
Writer = writer;
|
Writer = writer;
|
||||||
|
|
||||||
FormContext = new FormContext();
|
FormContext = new FormContext();
|
||||||
|
|
||||||
ClientValidationEnabled = htmlHelperOptions.ClientValidationEnabled;
|
ClientValidationEnabled = htmlHelperOptions.ClientValidationEnabled;
|
||||||
Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode;
|
Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode;
|
||||||
ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement;
|
ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement;
|
||||||
|
|
@ -228,7 +228,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
|
|
||||||
public FormContext GetFormContextForClientValidation()
|
public FormContext GetFormContextForClientValidation()
|
||||||
{
|
{
|
||||||
return (ClientValidationEnabled) ? FormContext : null;
|
return ClientValidationEnabled ? FormContext : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -558,6 +558,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
_attributes = attributes;
|
_attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Count => _attributes.Count;
|
public int Count => _attributes.Count;
|
||||||
|
|
||||||
public bool IsReadOnly => true;
|
public bool IsReadOnly => true;
|
||||||
|
|
|
||||||
|
|
@ -404,10 +404,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
var resolvedLabelText = labelText ??
|
var resolvedLabelText = labelText ??
|
||||||
modelExplorer.Metadata.DisplayName ??
|
modelExplorer.Metadata.DisplayName ??
|
||||||
modelExplorer.Metadata.PropertyName;
|
modelExplorer.Metadata.PropertyName;
|
||||||
if (resolvedLabelText == null)
|
if (resolvedLabelText == null && expression != null)
|
||||||
{
|
{
|
||||||
resolvedLabelText =
|
var index = expression.LastIndexOf('.');
|
||||||
string.IsNullOrEmpty(expression) ? string.Empty : expression.Split('.').Last();
|
if (index == -1)
|
||||||
|
{
|
||||||
|
// Expression does not contain a dot separator.
|
||||||
|
resolvedLabelText = expression;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedLabelText = expression.Substring(index + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(resolvedLabelText))
|
if (string.IsNullOrEmpty(resolvedLabelText))
|
||||||
|
|
@ -1289,8 +1297,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
object typeAttributeValue;
|
object typeAttributeValue;
|
||||||
if (htmlAttributes != null && htmlAttributes.TryGetValue("type", out typeAttributeValue))
|
if (htmlAttributes != null && htmlAttributes.TryGetValue("type", out typeAttributeValue))
|
||||||
{
|
{
|
||||||
if (string.Equals(typeAttributeValue.ToString(), "file", StringComparison.OrdinalIgnoreCase) ||
|
var typeAttributeString = typeAttributeValue.ToString();
|
||||||
string.Equals(typeAttributeValue.ToString(), "image", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(typeAttributeString, "file", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(typeAttributeString, "image", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// 'value' attribute is not needed for 'file' and 'image' input types.
|
// 'value' attribute is not needed for 'file' and 'image' input types.
|
||||||
addValue = false;
|
addValue = false;
|
||||||
|
|
|
||||||
|
|
@ -181,16 +181,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a dictionary of HTML attributes from the input object,
|
/// Creates a dictionary of HTML attributes from the input object,
|
||||||
/// translating underscores to dashes in each public instance property.
|
/// translating underscores to dashes in each public instance property.
|
||||||
///
|
/// </summary>
|
||||||
/// If the object is already an <see cref="IDictionary{String, Object}"/> instance, then it is
|
/// <param name="htmlAttributes">Anonymous object describing HTML attributes.</param>
|
||||||
/// returned as-is.
|
/// <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>
|
/// <example>
|
||||||
/// <c>new { data_name="value" }</c> will translate to the entry <c>{ "data-name", "value" }</c>
|
/// <c>new { data_name="value" }</c> will translate to the entry <c>{ "data-name", "value" }</c>
|
||||||
/// in the resulting dictionary.
|
/// in the resulting dictionary.
|
||||||
/// </example>
|
/// </example>
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="htmlAttributes">Anonymous object describing HTML attributes.</param>
|
|
||||||
/// <returns>A dictionary that represents HTML attributes.</returns>
|
|
||||||
public static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
|
public static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
|
||||||
{
|
{
|
||||||
var dictionary = htmlAttributes as IDictionary<string, object>;
|
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.
|
// 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.
|
// This is similar to how the GenerateLabel get the text of a label.
|
||||||
var resolvedDisplayName = modelExplorer.Metadata.DisplayName ?? modelExplorer.Metadata.PropertyName;
|
var resolvedDisplayName = modelExplorer.Metadata.DisplayName ?? modelExplorer.Metadata.PropertyName;
|
||||||
if (resolvedDisplayName == null)
|
if (resolvedDisplayName == null && expression != null)
|
||||||
{
|
{
|
||||||
resolvedDisplayName =
|
var index = expression.LastIndexOf('.');
|
||||||
string.IsNullOrEmpty(expression) ? string.Empty : expression.Split('.').Last();
|
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)
|
protected virtual string GenerateDisplayText(ModelExplorer modelExplorer)
|
||||||
|
|
@ -978,14 +987,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
bool useViewData,
|
bool useViewData,
|
||||||
object htmlAttributes)
|
object htmlAttributes)
|
||||||
{
|
{
|
||||||
var tagBuilder =
|
var tagBuilder = _htmlGenerator.GenerateHidden(
|
||||||
_htmlGenerator.GenerateHidden(
|
ViewContext,
|
||||||
ViewContext,
|
modelExplorer,
|
||||||
modelExplorer,
|
expression,
|
||||||
expression,
|
value,
|
||||||
value,
|
useViewData,
|
||||||
useViewData,
|
htmlAttributes);
|
||||||
htmlAttributes);
|
|
||||||
if (tagBuilder == null)
|
if (tagBuilder == null)
|
||||||
{
|
{
|
||||||
return HtmlString.Empty;
|
return HtmlString.Empty;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
private object _model;
|
private object _model;
|
||||||
private Func<object, object> _modelAccessor;
|
private Func<object, object> _modelAccessor;
|
||||||
private Type _modelType;
|
|
||||||
private List<ModelExplorer> _properties;
|
private List<ModelExplorer> _properties;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -30,8 +29,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
|
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
|
||||||
/// <param name="model">The model object. May be <c>null</c>.</param>
|
/// <param name="model">The model object. May be <c>null</c>.</param>
|
||||||
public ModelExplorer(
|
public ModelExplorer(
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
ModelMetadata metadata,
|
ModelMetadata metadata,
|
||||||
object model)
|
object model)
|
||||||
{
|
{
|
||||||
if (metadataProvider == null)
|
if (metadataProvider == null)
|
||||||
|
|
@ -172,29 +171,23 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_modelType == null)
|
if (Metadata.IsNullableValueType)
|
||||||
{
|
{
|
||||||
if (Model == null)
|
// 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,
|
||||||
// If the model is null, then use the declared model type;
|
// just go with the declared type.
|
||||||
_modelType = Metadata.ModelType;
|
return 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Includes a <see cref="ModelExplorer"/> for each property of the <see cref="ModelMetadata"/>
|
/// Includes a <see cref="ModelExplorer"/> for each property of the <see cref="ModelMetadata"/>
|
||||||
/// for <see cref="ModelType"/>.
|
/// for <see cref="ModelType"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IEnumerable<ModelExplorer> Properties
|
public IEnumerable<ModelExplorer> Properties
|
||||||
{
|
{
|
||||||
|
|
@ -260,8 +253,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
return Properties.FirstOrDefault(p => string.Equals(
|
return Properties.FirstOrDefault(p => string.Equals(
|
||||||
p.Metadata.PropertyName,
|
p.Metadata.PropertyName,
|
||||||
name,
|
name,
|
||||||
StringComparison.Ordinal));
|
StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,7 +376,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// A <see cref="ModelExplorer"/> created by
|
/// A <see cref="ModelExplorer"/> created by
|
||||||
/// <see cref="GetExplorerForExpression(Type, Func{object, object})"/>
|
/// <see cref="GetExplorerForExpression(Type, Func{object, object})"/>
|
||||||
/// represents the result of executing an arbitrary expression against the model contained
|
/// represents the result of executing an arbitrary expression against the model contained
|
||||||
/// in the current <see cref="ModelExplorer"/> instance.
|
/// in the current <see cref="ModelExplorer"/> instance.
|
||||||
|
|
@ -411,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
/// <returns>A <see cref="ModelExplorer"/>.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// A <see cref="ModelExplorer"/> created by
|
/// A <see cref="ModelExplorer"/> created by
|
||||||
/// <see cref="GetExplorerForExpression(ModelMetadata, Func{object, object})"/>
|
/// <see cref="GetExplorerForExpression(ModelMetadata, Func{object, object})"/>
|
||||||
/// represents the result of executing an arbitrary expression against the model contained
|
/// represents the result of executing an arbitrary expression against the model contained
|
||||||
/// in the current <see cref="ModelExplorer"/> instance.
|
/// in the current <see cref="ModelExplorer"/> instance.
|
||||||
|
|
@ -444,7 +437,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelExplorer CreateExplorerForProperty(
|
private ModelExplorer CreateExplorerForProperty(
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
PropertyHelper propertyHelper)
|
PropertyHelper propertyHelper)
|
||||||
{
|
{
|
||||||
if (propertyHelper == null)
|
if (propertyHelper == null)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Bson;
|
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
|
// 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.
|
// 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);
|
return tempDataDictionary ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
|
||||||
if (bufferScope == null)
|
if (bufferScope == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(_bufferScope));
|
throw new ArgumentNullException(nameof(bufferScope));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewContext == null)
|
if (viewContext == null)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
@ -98,57 +99,58 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
private static ViewDataInfo InnerEvalComplexExpression(object indexableObject, string expression)
|
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 targetInfo = GetPropertyValue(indexableObject, leftExpression);
|
||||||
var postExpression = expressionPair.Right;
|
if (targetInfo != null)
|
||||||
|
|
||||||
var subTargetInfo = GetPropertyValue(indexableObject, subExpression);
|
|
||||||
if (subTargetInfo != 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);
|
var rightExpression = expression.Substring(leftExpression.Length + 1);
|
||||||
if (potential != null)
|
targetInfo = InnerEvalComplexExpression(targetInfo.Value, rightExpression);
|
||||||
|
if (targetInfo != null)
|
||||||
{
|
{
|
||||||
return potential;
|
return targetInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leftExpression = GetNextShorterExpression(leftExpression);
|
||||||
}
|
}
|
||||||
|
while (!string.IsNullOrEmpty(leftExpression));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces an enumeration of combinations of property names given a complex expression in the following order:
|
// Given "one.two.three.four" initially, calls return
|
||||||
// this["one.two.three.four"]
|
// "one.two.three"
|
||||||
// this["one.two.three][four"]
|
// "one.two"
|
||||||
// this["one.two][three.four"]
|
// "one"
|
||||||
// this["one][two.three.four"]
|
// ""
|
||||||
// Recursion of InnerEvalComplexExpression() further sub-divides these cases to cover the full set of
|
// Recursion of InnerEvalComplexExpression() further sub-divides these cases to cover the full set of
|
||||||
// combinations shown in Eval(ViewDataDictionary, string) comments.
|
// 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 lastDot = expression.LastIndexOf('.');
|
||||||
|
if (lastDot == -1)
|
||||||
var subExpression = expression;
|
|
||||||
var postExpression = string.Empty;
|
|
||||||
|
|
||||||
while (lastDot > -1)
|
|
||||||
{
|
{
|
||||||
subExpression = expression.Substring(0, lastDot);
|
return string.Empty;
|
||||||
postExpression = expression.Substring(lastDot + 1);
|
|
||||||
yield return new ExpressionPair(subExpression, postExpression);
|
|
||||||
|
|
||||||
lastDot = subExpression.LastIndexOf('.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return expression.Substring(startIndex: 0, length: lastDot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ViewDataInfo GetIndexedPropertyValue(object indexableObject, string key)
|
private static ViewDataInfo GetIndexedPropertyValue(object indexableObject, string key)
|
||||||
|
|
@ -205,17 +207,5 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
|
||||||
return new ViewDataInfo(container, propertyInfo);
|
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
|
public class OverloadActionConstraint : IActionConstraint
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int Order { get; } = int.MaxValue;
|
public int Order => int.MaxValue;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Accept(ActionConstraintContext context)
|
public bool Accept(ActionConstraintContext context)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 Microsoft.Net.Http.Headers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
generator.Verify();
|
generator.Verify();
|
||||||
Assert.Equal("a", output.TagName);
|
Assert.Equal("a", output.TagName);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
Assert.Empty(output.Content.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
generator.Verify();
|
generator.Verify();
|
||||||
Assert.Equal("a", output.TagName);
|
Assert.Equal("a", output.TagName);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
Assert.Empty(output.Content.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
Assert.Equal("a", output.TagName);
|
Assert.Equal("a", output.TagName);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
Assert.Empty(output.Content.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
Assert.Equal("a", output.TagName);
|
Assert.Equal("a", output.TagName);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
Assert.Empty(output.Content.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
Assert.Equal("a", output.TagName);
|
Assert.Equal("a", output.TagName);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
Assert.Empty(output.Content.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[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", "\r\n Something Else \r\n", "\r\n Something Else \r\n")]
|
||||||
[InlineData("\r\n \r\n", "Some Content", "Some Content")]
|
[InlineData("\r\n \r\n", "Some Content", "Some Content")]
|
||||||
public async Task ProcessAsync_DoesNotOverrideOutputContent(
|
public async Task ProcessAsync_DoesNotOverrideOutputContent(
|
||||||
string childContent, string outputContent, string expectedOutputContent)
|
string childContent,
|
||||||
|
string outputContent,
|
||||||
|
string expectedOutputContent)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var tagBuilder = new TagBuilder("span2");
|
var tagBuilder = new TagBuilder("span2");
|
||||||
|
|
@ -209,7 +211,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
[InlineData("Content of validation message", "Content of validation message")]
|
[InlineData("Content of validation message", "Content of validation message")]
|
||||||
[InlineData("\r\n \r\n", "New HTML")]
|
[InlineData("\r\n \r\n", "New HTML")]
|
||||||
public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage(
|
public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage(
|
||||||
string childContent, string expectedOutputContent)
|
string childContent,
|
||||||
|
string expectedOutputContent)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var tagBuilder = new TagBuilder("span2");
|
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.Extensions.Primitives;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
(Expression<Func<TestModel, string>>)(m => value)
|
(Expression<Func<TestModel, string>>)(m => value)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// These two expressions are not actually equivalent. However ExpressionHelper returns
|
// 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
|
// string.Empty for these two expressions and hence they are considered as equivalent by the
|
||||||
// cache.
|
// cache.
|
||||||
(Expression<Func<TestModel, string>>)(m => Model),
|
(Expression<Func<TestModel, string>>)(m => Model),
|
||||||
(Expression<Func<TestModel, TestModel>>)(m => m)
|
(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<IDictionary<string, TestModel>, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory),
|
||||||
(Expression<Func<TestModel, Category>>)(model => model.SelectedCategory)
|
(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 => Model),
|
||||||
(Expression<Func<TestModel, string>>)(m => 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 => key),
|
||||||
(Expression<Func<TestModel, string>>)(m => value)
|
(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);
|
var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(text1, text2); // Cached
|
Assert.Same(text1, text2); // cached
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -270,6 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.Equal(text1, text2, StringComparer.Ordinal);
|
||||||
Assert.NotSame(text1, text2); // not cached
|
Assert.NotSame(text1, text2); // not cached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +277,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(text1, text2);
|
Assert.Same(text1, text2); // cached
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -298,6 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.NotEqual(text1, text2, StringComparer.Ordinal);
|
||||||
Assert.NotSame(text1, text2);
|
Assert.NotSame(text1, text2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var expected1 = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
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 Dictionary<string, object> { { "baz", "BazValue" } }, expected1 };
|
||||||
yield return new object[] { new { 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]]"" " +
|
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 Dictionary<string, object> { { "foo-baz", "BazValue" } }, expected2 };
|
||||||
yield return new object[] { new { foo_baz = "BazValue" }, expected2 };
|
yield return new object[] { new { foo_baz = "BazValue" }, expected2 };
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" key=""HtmlEncode[[value]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
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 attributes = new { key = "value" };
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = @"<input data-key=""HtmlEncode[[value]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
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 attributes = new Dictionary<string, object> { { "data-key", "value" } };
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithNullModelAndNonNullViewData());
|
||||||
|
|
||||||
|
|
@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[keyNotFound]]"" name=""HtmlEncode[[keyNotFound]]"" type=""HtmlEncode[[hidden]]"" " +
|
var expected = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[keyNotFound]]"" name=""HtmlEncode[[keyNotFound]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||||
@"value="""" />";
|
@"value="""" />";
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
||||||
var attributes = new Dictionary<string, object> { { "baz", "BazValue" } };
|
var attributes = new Dictionary<string, object> { { "baz", "BazValue" } };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
public void LabelHelpers_DisplayPropertyName()
|
public void LabelHelpers_DisplayPropertyName()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expectedLabel = "<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>";
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -45,14 +46,15 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
var labelForResult = helper.LabelFor(m => m.Property1);
|
var labelForResult = helper.LabelFor(m => m.Property1);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>", HtmlContentUtilities.HtmlContentToString(labelResult));
|
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||||
Assert.Equal("<label for=\"HtmlEncode[[Property1]]\">HtmlEncode[[Property1]]</label>", HtmlContentUtilities.HtmlContentToString(labelForResult));
|
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LabelHelpers_DisplayPropertyName_ForNestedProperty()
|
public void LabelHelpers_DisplayPropertyName_ForNestedProperty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expectedLabel = "<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>";
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper<OuterClass>(model: null);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper<OuterClass>(model: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -60,8 +62,8 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
var labelForResult = helper.LabelFor(m => m.Inner.Id);
|
var labelForResult = helper.LabelFor(m => m.Inner.Id);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>", HtmlContentUtilities.HtmlContentToString(labelResult));
|
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelResult));
|
||||||
Assert.Equal("<label for=\"HtmlEncode[[Inner_Id]]\">HtmlEncode[[Id]]</label>", HtmlContentUtilities.HtmlContentToString(labelForResult));
|
Assert.Equal(expectedLabel, HtmlContentUtilities.HtmlContentToString(labelForResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
Expression<Func<PasswordModel, string>> expression,
|
Expression<Func<PasswordModel, string>> expression,
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new PasswordModel();
|
var model = new PasswordModel();
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||||
helper.ViewData.TemplateInfo.HtmlFieldPrefix = "pre";
|
helper.ViewData.TemplateInfo.HtmlFieldPrefix = "pre";
|
||||||
|
|
@ -489,7 +489,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
// Act
|
// Act
|
||||||
var result = helper.PasswordFor(expression);
|
var result = helper.PasswordFor(expression);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TextAreaFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid()
|
public void TextAreaFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new TextAreaModelWithAPlaceholder();
|
var model = new TextAreaModelWithAPlaceholder();
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var textArea = helper.TextAreaFor(m => m.Property1);
|
var textArea = helper.TextAreaFor(m => m.Property1);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
||||||
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
@ -30,14 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TextAreaFor_DoesNotGeneratePlaceholderAttribute_WhenNoPlaceholderPresentInModel()
|
public void TextAreaFor_DoesNotGeneratePlaceholderAttribute_WhenNoPlaceholderPresentInModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new TextAreaModelWithoutAPlaceholder();
|
var model = new TextAreaModelWithoutAPlaceholder();
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var textArea = helper.TextAreaFor(m => m.Property1);
|
var textArea = helper.TextAreaFor(m => m.Property1);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
var result = HtmlContentUtilities.HtmlContentToString(textArea);
|
||||||
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
[InlineData("number")]
|
[InlineData("number")]
|
||||||
public void TextBoxFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid(string type)
|
public void TextBoxFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid(string type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new TextBoxModel();
|
var model = new TextBoxModel();
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
||||||
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
[InlineData("file")]
|
[InlineData("file")]
|
||||||
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
|
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new TextBoxModel();
|
var model = new TextBoxModel();
|
||||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
var textBox = helper.TextBoxFor(m => m.Property1, new { type });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
var result = HtmlContentUtilities.HtmlContentToString(textBox);
|
||||||
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue