/// <summary> /// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class. /// </summary> public HtmlHelper( IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, #pragma warning disable PUB0001 // Pubternal type in public API IViewBufferScope bufferScope, #pragma warning restore PUB0001 HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, #pragma warning disable PUB0001 // Pubternal type in public API ExpressionTextCache expressionTextCache #pragma warning restore PUB0001 ) : base( htmlGenerator, viewEngine, metadataProvider, bufferScope, htmlEncoder, urlEncoder) { if (expressionTextCache == null) { throw new ArgumentNullException(nameof(expressionTextCache)); } _expressionTextCache = expressionTextCache; }
/// <summary> /// Creates a new <see cref="ModelExpressionProvider"/>. /// </summary> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="expressionTextCache">The <see cref="ExpressionTextCache"/>.</param> public ModelExpressionProvider( IModelMetadataProvider modelMetadataProvider, ExpressionTextCache expressionTextCache) { if (modelMetadataProvider == null) { throw new ArgumentNullException(nameof(modelMetadataProvider)); } if (expressionTextCache == null) { throw new ArgumentNullException(nameof(expressionTextCache)); } _modelMetadataProvider = modelMetadataProvider; _expressionTextCache = expressionTextCache; }
/// <summary> /// Creates a new <see cref="ModelExpressionProvider"/>. /// </summary> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="expressionTextCache">The <see cref="ExpressionTextCache"/>.</param> public ModelExpressionProvider( IModelMetadataProvider modelMetadataProvider, #pragma warning disable PUB0001 // Pubternal type in public API ExpressionTextCache expressionTextCache #pragma warning restore PUB0001 ) { if (modelMetadataProvider == null) { throw new ArgumentNullException(nameof(modelMetadataProvider)); } if (expressionTextCache == null) { throw new ArgumentNullException(nameof(expressionTextCache)); } _modelMetadataProvider = modelMetadataProvider; _expressionTextCache = expressionTextCache; }
/// <summary> /// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class. /// </summary> public HtmlHelper( IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, ExpressionTextCache expressionTextCache) : base( htmlGenerator, viewEngine, metadataProvider, bufferScope, htmlEncoder, urlEncoder) { if (expressionTextCache == null) { throw new ArgumentNullException(nameof(expressionTextCache)); } _expressionTextCache = expressionTextCache; }
public static string GetExpressionText(LambdaExpression expression, ExpressionTextCache expressionTextCache) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } if (expressionTextCache != null && expressionTextCache.Entries.TryGetValue(expression, out var 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. doNotCache 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 doNotCache = false; var lastIsModel = false; var length = 0; var segmentCount = 0; var trailingMemberExpressions = 0; var part = expression.Body; while (part != null) { switch (part.NodeType) { case ExpressionType.Call: // Will exit loop if at Method().Property or [i,j].Property. In that case (like [i].Property), // don't cache and don't remove ".Model" (if that's .Property). doNotCache = true; lastIsModel = false; var methodExpression = (MethodCallExpression)part; if (IsSingleArgumentIndexer(methodExpression)) { length += "[99]".Length; part = methodExpression.Object; segmentCount++; trailingMemberExpressions = 0; } else { // Unsupported. part = null; } break; case ExpressionType.ArrayIndex: var binaryExpression = (BinaryExpression)part; doNotCache = 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; } } // 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(!doNotCache); if (expressionTextCache != null) { expressionTextCache.Entries.TryAdd(expression, string.Empty); } 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(doNotCache); var methodExpression = (MethodCallExpression)part; InsertIndexerInvocationText(builder, methodExpression.Arguments.Single(), expression); part = methodExpression.Object; break; case ExpressionType.ArrayIndex: Debug.Assert(doNotCache); 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; } } Debug.Assert(segmentCount == 0); expressionText = builder.ToString(); if (expressionTextCache != null && !doNotCache) { expressionTextCache.Entries.TryAdd(expression, expressionText); } return(expressionText); }