/// <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);
        }
Example #2
0
        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;
            }
        }
Example #5
0
        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();
            }
        }
Example #6
0
        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);
            }
        }