/// <summary> /// Starts the generation of a <see cref="TagHelperChunk"/>. /// </summary> /// <param name="target"> /// The <see cref="Block"/> responsible for this <see cref="TagHelperChunkGenerator"/>. /// </param> /// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about /// the current chunk generation process.</param> public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { var tagHelperBlock = target as TagHelperBlock; Debug.Assert( tagHelperBlock != null, $"A {nameof(TagHelperChunkGenerator)} must only be used with {nameof(TagHelperBlock)}s."); var attributes = new List <TagHelperAttributeTracker>(); // We need to create a chunk generator to create chunks for each of the attributes. var chunkGenerator = context.Host.CreateChunkGenerator( context.ClassName, context.RootNamespace, context.SourceFile); foreach (var attribute in tagHelperBlock.Attributes) { ParentChunk attributeChunkValue = null; if (attribute.Value != null) { // Populates the chunk tree with chunks associated with attributes attribute.Value.Accept(chunkGenerator); var chunks = chunkGenerator.Context.ChunkTreeBuilder.Root.Children; var first = chunks.FirstOrDefault(); attributeChunkValue = new ParentChunk { Association = first?.Association, Children = chunks, Start = first == null ? SourceLocation.Zero : first.Start }; } var attributeChunk = new TagHelperAttributeTracker( attribute.Name, attributeChunkValue, attribute.ValueStyle); attributes.Add(attributeChunk); // Reset the chunk tree builder so we can build a new one for the next attribute chunkGenerator.Context.ChunkTreeBuilder = new ChunkTreeBuilder(); } var unprefixedTagName = tagHelperBlock.TagName.Substring(_tagHelperDescriptors.First().Prefix.Length); context.ChunkTreeBuilder.StartParentChunk( new TagHelperChunk( unprefixedTagName, tagHelperBlock.TagMode, attributes, _tagHelperDescriptors), target, topLevel: false); }
private static bool CanPreallocateBoundAttribute( IEnumerable <TagHelperAttributeDescriptor> associatedAttributeDescriptors, TagHelperAttributeTracker attribute) { // If the attribute value is a Dynamic value, it cannot be preallocated. if (CSharpTagHelperCodeRenderer.IsDynamicAttributeValue(attribute.Value)) { return(false); } // Only attributes that are associated with string typed properties can be preallocated. var attributeName = attribute.Name; var allStringProperties = associatedAttributeDescriptors .All(attributeDescriptor => attributeDescriptor.IsStringProperty); return(allStringProperties); }
private void RenderUnboundAttribute(TagHelperAttributeTracker attribute) { // Render children to provide IntelliSense at design time. No need for the execution context logic, it's // a runtime feature. if (_designTimeMode) { if (attribute.Value != null) { _bodyVisitor.Accept(attribute.Value); } return; } Debug.Assert(attribute.Value != null); var attributeValueStyleParameter = $"global::{typeof(HtmlAttributeValueStyle).FullName}.{attribute.ValueStyle}"; // All simple text and minimized attributes will be pre-allocated. var preallocatedValue = attribute.Value as PreallocatedTagHelperAttributeChunk; if (preallocatedValue != null) { _writer .WriteStartInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName) .Write(preallocatedValue.AttributeVariableAccessor) .WriteEndMethodInvocation(); } else if (IsDynamicAttributeValue(attribute.Value)) { // Dynamic attribute value should be run through the conditional attribute removal system. It's // unbound and contains C#. // TagHelper attribute rendering is buffered by default. We do not want to write to the current // writer. var currentTargetWriter = _context.TargetWriterName; var currentWriteAttributeMethodName = _context.Host.GeneratedClassContext.WriteAttributeValueMethodName; _context.TargetWriterName = null; Debug.Assert(attribute.Value is ParentChunk); var children = ((ParentChunk)attribute.Value).Children; var attributeCount = children.Count(c => c is DynamicCodeAttributeChunk || c is LiteralCodeAttributeChunk); _writer .WriteStartMethodInvocation(_tagHelperContext.BeginAddHtmlAttributeValuesMethodName) .Write(ExecutionContextVariableName) .WriteParameterSeparator() .WriteStringLiteral(attribute.Name) .WriteParameterSeparator() .Write(attributeCount.ToString(CultureInfo.InvariantCulture)) .WriteParameterSeparator() .Write(attributeValueStyleParameter) .WriteEndMethodInvocation(); _attributeCodeVisitor.Accept(attribute.Value); _writer.WriteMethodInvocation( _tagHelperContext.EndAddHtmlAttributeValuesMethodName, ExecutionContextVariableName); _context.TargetWriterName = currentTargetWriter; } else { // This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or // other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to // determine its final value. // Attribute value is not plain text, must be buffered to determine its final value. BuildBufferedWritingScope(attribute.Value, htmlEncodeValues: true); _writer .WriteStartInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName) .WriteStringLiteral(attribute.Name) .WriteParameterSeparator() .WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName); RenderBufferedAttributeValueAccessor(_writer); _writer .WriteEndMethodInvocation(endLine: false) .WriteParameterSeparator() .Write(attributeValueStyleParameter) .WriteEndMethodInvocation(); } }
private string RenderBoundAttribute( TagHelperAttributeTracker attribute, string tagHelperVariableName, string previousValueAccessor, TagHelperAttributeDescriptor attributeDescriptor) { var currentValueAccessor = string.Format( CultureInfo.InvariantCulture, "{0}.{1}", tagHelperVariableName, attributeDescriptor.PropertyName); if (attributeDescriptor.IsIndexer) { var dictionaryKey = attribute.Name.Substring(attributeDescriptor.Name.Length); currentValueAccessor += $"[\"{dictionaryKey}\"]"; } // If this attribute value has not been seen before, need to record its value. if (previousValueAccessor == null) { var preallocatedAttributeValueChunk = attribute.Value as PreallocatedTagHelperAttributeChunk; if (preallocatedAttributeValueChunk != null) { RenderBoundPreAllocatedAttribute(preallocatedAttributeValueChunk, currentValueAccessor); return currentValueAccessor; } // Bufferable attributes are attributes that can have Razor code inside of them. Such // attributes have string values and may be calculated using a temporary TextWriter or other // buffer. var bufferableAttribute = attributeDescriptor.IsStringProperty; RenderNewAttributeValueAssignment( attributeDescriptor, bufferableAttribute, attribute.Value, currentValueAccessor); if (_designTimeMode) { // Execution contexts are a runtime feature. return currentValueAccessor; } // We need to inform the context of the attribute value. _writer .WriteStartInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName) .WriteStringLiteral(attribute.Name) .WriteParameterSeparator() .Write(currentValueAccessor) .WriteParameterSeparator() .Write($"global::{typeof(HtmlAttributeValueStyle).FullName}.{attribute.ValueStyle}") .WriteEndMethodInvocation(); return currentValueAccessor; } else { // The attribute value has already been determined and accessor was passed to us as // previousValueAccessor, we don't want to evaluate the value twice so lets just use the // previousValueLocation. _writer .WriteStartAssignment(currentValueAccessor) .Write(previousValueAccessor) .WriteLine(";"); return previousValueAccessor; } }
private string RenderBoundAttribute( TagHelperAttributeTracker attribute, string tagHelperVariableName, string previousValueAccessor, TagHelperAttributeDescriptor attributeDescriptor) { var currentValueAccessor = string.Format( CultureInfo.InvariantCulture, "{0}.{1}", tagHelperVariableName, attributeDescriptor.PropertyName); if (attributeDescriptor.IsIndexer) { var dictionaryKey = attribute.Name.Substring(attributeDescriptor.Name.Length); currentValueAccessor += $"[\"{dictionaryKey}\"]"; } // If this attribute value has not been seen before, need to record its value. if (previousValueAccessor == null) { var preallocatedAttributeValueChunk = attribute.Value as PreallocatedTagHelperAttributeChunk; if (preallocatedAttributeValueChunk != null) { RenderBoundPreAllocatedAttribute(preallocatedAttributeValueChunk, currentValueAccessor); return(currentValueAccessor); } // Bufferable attributes are attributes that can have Razor code inside of them. Such // attributes have string values and may be calculated using a temporary TextWriter or other // buffer. var bufferableAttribute = attributeDescriptor.IsStringProperty; RenderNewAttributeValueAssignment( attributeDescriptor, bufferableAttribute, attribute.Value, currentValueAccessor); if (_designTimeMode) { // Execution contexts are a runtime feature. return(currentValueAccessor); } // We need to inform the context of the attribute value. _writer .WriteStartInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName) .WriteStringLiteral(attribute.Name) .WriteParameterSeparator() .Write(currentValueAccessor) .WriteParameterSeparator() .Write($"global::{typeof(HtmlAttributeValueStyle).FullName}.{attribute.ValueStyle}") .WriteEndMethodInvocation(); return(currentValueAccessor); } else { // The attribute value has already been determined and accessor was passed to us as // previousValueAccessor, we don't want to evaluate the value twice so lets just use the // previousValueLocation. _writer .WriteStartAssignment(currentValueAccessor) .Write(previousValueAccessor) .WriteLine(";"); return(previousValueAccessor); } }