/// <summary> /// Renders the code for the given <paramref name="chunk"/>. /// </summary> /// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param> public void RenderTagHelper(TagHelperChunk chunk) { // Remove any duplicate TagHelperDescriptors that reference the same type name. Duplicates can occur when // multiple HtmlTargetElement attributes are on a TagHelper type and matches overlap for an HTML element. // Having more than one descriptor with the same TagHelper type results in generated code that runs // the same TagHelper X many times (instead of once) over a single HTML element. var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default); RenderBeginTagHelperScope(chunk.TagName, chunk.TagMode, chunk.Children); RenderTagHelpersCreation(chunk, tagHelperDescriptors); RenderAttributes(chunk.Attributes, tagHelperDescriptors); // No need to run anything in design time mode. if (!_designTimeMode) { RenderRunTagHelpers(); RenderWriteTagHelperMethodCall(chunk); RenderEndTagHelpersScope(); } }
private void RenderTagHelpersCreation( TagHelperChunk chunk, IEnumerable<TagHelperDescriptor> tagHelperDescriptors) { // This is to maintain value accessors for attributes when creating the TagHelpers. // Ultimately it enables us to do scenarios like this: // myTagHelper1.Foo = DateTime.Now; // myTagHelper2.Foo = myTagHelper1.Foo; var htmlAttributeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (var tagHelperDescriptor in tagHelperDescriptors) { var tagHelperVariableName = GetVariableName(tagHelperDescriptor); // Create the tag helper _writer.WriteStartAssignment(tagHelperVariableName) .WriteStartMethodInvocation(_tagHelperContext.CreateTagHelperMethodName, tagHelperDescriptor.TypeName) .WriteEndMethodInvocation(); // Execution contexts and throwing errors for null dictionary properties are a runtime feature. if (_designTimeMode) { continue; } _writer.WriteInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddMethodName, tagHelperVariableName); // Track dictionary properties we have confirmed are non-null. var confirmedDictionaries = new HashSet<string>(StringComparer.Ordinal); // Ensure that all created TagHelpers have initialized dictionary bound properties which are used // via TagHelper indexers. foreach (var chunkAttribute in chunk.Attributes) { var associatedAttributeDescriptor = tagHelperDescriptor.Attributes.FirstOrDefault( attributeDescriptor => attributeDescriptor.IsNameMatch(chunkAttribute.Key)); if (associatedAttributeDescriptor != null && associatedAttributeDescriptor.IsIndexer && confirmedDictionaries.Add(associatedAttributeDescriptor.PropertyName)) { // Throw a reasonable Exception at runtime if the dictionary property is null. _writer .Write("if (") .Write(tagHelperVariableName) .Write(".") .Write(associatedAttributeDescriptor.PropertyName) .WriteLine(" == null)"); using (_writer.BuildScope()) { // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName // of InvalidOperationException type. _writer .Write("throw ") .WriteStartNewObject(nameof(InvalidOperationException)) .WriteStartMethodInvocation(_tagHelperContext.FormatInvalidIndexerAssignmentMethodName) .WriteStringLiteral(chunkAttribute.Key) .WriteParameterSeparator() .WriteStringLiteral(tagHelperDescriptor.TypeName) .WriteParameterSeparator() .WriteStringLiteral(associatedAttributeDescriptor.PropertyName) .WriteEndMethodInvocation(endLine: false) // End of method call .WriteEndMethodInvocation(endLine: true); // End of new expression / throw statement } } } } }
private void RenderWriteTagHelperMethodCall(TagHelperChunk chunk) { _writer .WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false) .Write("await "); if (!string.IsNullOrEmpty(_context.TargetWriterName)) { _writer .WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperToAsyncMethodName) .Write(_context.TargetWriterName) .WriteParameterSeparator(); } else { _writer.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperAsyncMethodName); } _writer .Write(ExecutionContextVariableName) .WriteEndMethodInvocation() .WriteEndInstrumentationContext(_context); }
private void RenderTagHelperOutput(TagHelperChunk chunk) { var tagHelperOutputAccessor = $"{ExecutionContextVariableName}.{_tagHelperContext.ExecutionContextOutputPropertyName}"; if (ContainsChildContent(chunk.Children)) { _writer .Write("if (!") .Write(tagHelperOutputAccessor) .Write(".") .Write(_tagHelperContext.TagHelperOutputIsContentModifiedPropertyName) .WriteLine(")"); using (_writer.BuildScope()) { _writer .Write(tagHelperOutputAccessor) .Write(".") .WriteStartAssignment(_tagHelperContext.TagHelperOutputContentPropertyName) .Write("await ") .WriteInstanceMethodInvocation( tagHelperOutputAccessor, _tagHelperContext.TagHelperOutputGetChildContentAsyncMethodName); } } _writer .WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false); if (!string.IsNullOrEmpty(_context.TargetWriterName)) { _writer .WriteStartMethodInvocation(_context.Host.GeneratedClassContext.WriteToMethodName) .Write(_context.TargetWriterName) .WriteParameterSeparator(); } else { _writer.WriteStartMethodInvocation(_context.Host.GeneratedClassContext.WriteMethodName); } _writer .Write(tagHelperOutputAccessor) .WriteEndMethodInvocation() .WriteEndInstrumentationContext(_context); }