tag route parameter page net form data asp all asp.net-core tag-helpers

asp.net core - route - ¿Cómo renderizar scripts, generados en el método de proceso TagHelper, al final de la página en lugar de al lado del elemento de etiqueta?



tag helpers asp net core (6)

Estoy generando scripts en el método de proceso de la clase TagHelper de la siguiente manera

[TargetElement("MyTag")] public Class MYClass: TagHelper{ public override void Process(TagHelperContext context, TagHelperOutput output) { StringBuilder builder = new StringBuilder(); builder.Append("<script>"); builder.Append("//some javascript codes here); builder.Append("</script>"); output.Content.Append(builder.ToString()); } }

Ahora coloca el script muy al lado del elemento de etiqueta como su hermano.

Necesito colocar los guiones al final de la sección del cuerpo.


Sé que este hilo es viejo, pero si alguien está buscando una solución fácil para ejecutar algunos javascript, aquí hay una manera.

En primer lugar, ViewComponents representa el lado del servidor, por lo que, naturalmente, los scripts del lado del cliente no estarán listos en este momento. Como han señalado otros, puedes renderizar algunos scripts de sección cuando sea necesario para interpretar tu tag helper, esto es ideal para desacoplar, y solo debes incluir el script donde sea necesario.

Pero a menudo su etiqueta auxiliar toma datos como entrada que es relevante para el script del lado del cliente. Para poder ejecutar estos datos a través de una función js, podría hacer algo como esto.

TagHelper.cs

var data= $@" ''{Id}'', ''{Title}'', {JsonConvert.SerializeObject(MyList)}"; output.Attributes.SetAttribute("data-eval", data);

site.js

$(".tag-helper-class").each((i, e) => { const jq = $(e); const data= jq.data("eval"); if (!data) { return; } jq.attr("data-eval", ""); eval(`myJsFunction(${data})`); });

Ahora, cuando los scripts estén listos, pueden buscar su etiqueta auxiliar y ejecutar la función adecuada con los datos relevantes.


No creo que sea posible desde dentro de tagHelper agregar una secuencia de comandos en la parte inferior o en cualquier otro lugar, excepto la ubicación de la etiqueta que está renderizando taghelper. Creo que si Taghelper depende de algún archivo js externo, no debería ser responsabilidad del propio Taghelper agregar el script. Por ejemplo, los validados de etiqueta integrados como:

<span asp-validation-for="Email" class="text-danger"></span>

todo lo que validación taghelper hace es decorar el lapso con atributos de datos, no agrega ningún guión a la página y los atributos de datos simplemente se ignorarán si faltan los guiones.

Tenga en cuenta que una vista puede tener múltiples validadores de etiquetas de validación utilizados y no deseamos que cada uno agregue otra secuencia de comandos.

En la plantilla de la aplicación web inicial VS, puede ver que los scripts de validación se agregan mediante una vista parcial en la parte inferior de la vista (Login.cshtml, por ejemplo)

@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }

Una posible estrategia para automatizar la inclusión de scripts es su tagHelper podría tomar IHttpContextAccessor en su constructor para que sea inyectado por DI, luego podría acceder a la colección HttpContext.Items y agregar una variable para indicar la necesidad de un script, luego en una vista parcial que agrega scripts, puede detectar la variable agregada para decidir qué script (s) incluir.

Pero a mí me parece más sencillo simplemente agregar el guión donde sea necesario para apoyar el uso del taghelper en lugar de tratar de ser elegante y agregar cosas automáticamente.

Esta idea solo funcionaría para archivos js externos, no para js, que se escriban dinámicamente dentro de taghelper, pero es mejor no tener tales scripts y solo usar archivos de script externos si es posible. Si realmente necesita generar script dentro de Taghelper, creo que solo podrá procesarlo en la ubicación del elemento que Taghelper está procesando.


Tenga una @section scripts {} que se represente en Layout con @RenderSection("scripts") y coloque su tag helper dentro de la sección de scripts. Cuando se procesa, se colocará donde se define en Diseño (en la parte inferior de su html).

<!DOCTYPE html> <html> <head> </head> <body> <div> <p>some html ... bla bla bla</p> @RenderBody() </div> @RenderSection("scripts", required: false) </body> </html>

luego en cualquier otro archivo cshtml,

<p>Some page</p> @section scripts { <mytaghelper>foo</mytaghelper> }


Cree un BodyTagHelper que inserta un valor en TagHelperContext.Items y luego se configura en su TagHelper personalizado.

Código completo:

public class BodyContext { public bool AddCustomScriptTag { get; set; } } public class BodyTagHelper : TagHelper { public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { var builder = new StringBuilder(); var bodyContext = new BodyContext(); context.Items["BodyContext"] = bodyContext; // Execute children, they can read the BodyContext await context.GetChildContentAsync(); if (bodyContext.AddCustomScriptTag) { // Add script tags after the body content but before end tag. output .PostContent .Append("<script>") .Append("//some javascript codes here") .Append("</script>"); } } } [TargetElement("MyTag")] public class MYClass : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) { // Do whatever you want object bodyContextObj; if (context.Items.TryGetValue("BodyContext", out bodyContextObj)) { // Notify parent that we need the script tag ((BodyContext)bodyContextObj).AddCustomScriptTag = true; } } }

¡Espero que esto ayude!


He creado un par de ayudantes de etiquetas personalizadas que pueden resolver su problema.

El primero es <storecontent> y simplemente almacena el contenido html envuelto dentro del diccionario TempData. No proporciona salida directa. El contenido puede ser un script en línea o cualquier otro html. Muchos asistentes de etiquetas de este tipo se pueden colocar en varias ubicaciones, por ejemplo, en vistas parciales.

El segundo ayudante de etiqueta es <renderstoredcontent> y procesa todos los contenidos previamente almacenados en la ubicación deseada, por ejemplo, al final del elemento del cuerpo.

Código para StoreContentTagHelper.cs :

using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace YourProjectHere.TagHelpers { [TargetElement("storecontent", Attributes = KeyAttributeName)] public class StoreContentTagHelper : TagHelper { private const string KeyAttributeName = "asp-key"; private const string _storageKey = "storecontent"; private const string _defaultListKey = "DefaultKey"; [HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; } [HtmlAttributeName(KeyAttributeName)] public string Key { get; set; } public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { output.SuppressOutput(); TagHelperContent childContent = await context.GetChildContentAsync(); var storageProvider = ViewContext.TempData; Dictionary<string, List<HtmlString>> storage; List<HtmlString> defaultList; if (!storageProvider.ContainsKey(_storageKey) || !(storageProvider[_storageKey] is Dictionary<string,List<HtmlString>>)) { storage = new Dictionary<string, List<HtmlString>>(); storageProvider[_storageKey] = storage; defaultList = new List<HtmlString>(); storage.Add(_defaultListKey, defaultList); } else { storage = ViewContext.TempData[_storageKey] as Dictionary<string, List<HtmlString>>; if (storage.ContainsKey(_defaultListKey)) { defaultList = storage[_defaultListKey]; } else { defaultList = new List<HtmlString>(); storage.Add(_defaultListKey, defaultList); } } if (String.IsNullOrEmpty(Key)) { defaultList.Add(new HtmlString(childContent.GetContent())); } else { if(storage.ContainsKey(Key)) { storage[Key].Add(new HtmlString(childContent.GetContent())); } else { storage.Add(Key, new List<HtmlString>() { new HtmlString(childContent.GetContent()) }); } } } } }

Código para RenderStoredContentTagHelper.cs :

using System; using System.Linq; using System.Collections.Generic; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace YourProjectHere.TagHelpers { [TargetElement("renderstoredcontent", Attributes = KeyAttributeName)] public class RenderStoredContentTagHelper : TagHelper { private const string KeyAttributeName = "asp-key"; private const string _storageKey = "storecontent"; [HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; } [HtmlAttributeName(KeyAttributeName)] public string Key { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = String.Empty; var storageProvider = ViewContext.TempData; Dictionary<string, List<HtmlString>> storage; if (!storageProvider.ContainsKey(_storageKey) || !(storageProvider[_storageKey] is Dictionary<string, List<HtmlString>>)) { return; } storage = storageProvider[_storageKey] as Dictionary<string, List<HtmlString>>; string html = ""; if (String.IsNullOrEmpty(Key)) { html = String.Join("", storage.Values.SelectMany(x => x).ToList()); } else { if (!storage.ContainsKey(Key)) return; html = String.Join("", storage[Key]); } TagBuilder tagBuilder = new TagBuilder("dummy"); tagBuilder.InnerHtml = html; output.Content.SetContent(tagBuilder.InnerHtml); } } }

Uso básico:

En alguna vista o vista parcial:

<storecontent asp-key=""> <script> your inline script... </script> </storecontent>

En otro lugar:

<storecontent asp-key=""> <script src="..."></script> </storecontent>

Y finalmente en la ubicación deseada donde se deben renderizar ambos scripts:

<renderstoredcontent asp-key=""></renderstoredcontent>

Eso es.

Algunas notas:

  1. Puede haber cualquier cantidad de etiquetas <storecontent> . El atributo asp-key es obligatorio, al menos tan vacío como "". Si especifica valores específicos para este atributo, puede agrupar el contenido almacenado y representar grupos específicos en diferentes ubicaciones. Por ejemplo, si especifica algún contenido con asp-key="scripts" y algún otro contenido con asp-key="footnotes" entonces puede representar solo el primer grupo como una ubicación utilizando:

<renderstoredcontent asp-key="scripts"></renderstoredcontent>

El otro grupo de "notas al pie" se puede representar en otra ubicación.

  1. El <storecontent> debe definirse antes de que se <renderstoredcontent> . En ASP.NET, la respuesta se genera en un orden jerárquico inverso, en primer lugar se generan las vistas parciales más internas, luego la vista parcial primaria, luego la vista principal y finalmente la página de diseño. Por lo tanto, puede usar fácilmente estos ayudantes de etiquetas para definir scripts en una vista parcial y luego renderizar los scripts al final de la sección del cuerpo en la página de diseño.

  2. No olvides hacer referencia a los ayudantes de etiquetas personalizadas en el archivo _ViewImports.cshtml con el comando @addTagHelper "*, YourProjectHere"

Perdón por la publicación larga, ¡y espero que ayude!


En lugar de poner el javascript en la parte inferior de la página, puedes ir un paso más allá y separar por completo tu html (taghelper) de tu javascript. Escriba su Javascript para que encuentre su taghelper e inicialice a sí mismo.

Como ejemplo, aquí está un Taghelper / Javascript que uso que toma un horario UTC y lo muestra en la hora local del usuario, formateado como fecha, hora o fecha.

El Tag Helper

[HtmlTargetElement("datetime", Attributes = "format,value")] public class DateTimeTagHelper : TagHelper { [HtmlAttributeName("format")] public DateTimeFormat Format { get; set; } [HtmlAttributeName("value")] public DateTime Value { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "span"; output.TagMode = TagMode.StartTagAndEndTag; output.Attributes.Add("class", "datetime_init"); output.Attributes.Add("format", Format); output.Attributes.Add("value", Value.ToString("u")); } }

Javascript (requiere moment.js pero irrelevante para el concepto)

$(document).ready(function () { DateTime_Init(); } function DateTime_Init() { $(".datetime_init").each(function () { var utctime = $(this).attr("value"); var localTime = moment.utc(utctime).toDate(); switch($(this).attr("format")) { case "Date": $(this).html(moment(localTime).format(''DD/MM/YYYY'')); break; case "Time": $(this).html(moment(localTime).format(''HH:mm'')); break; default: $(this).html(moment(localTime).format(''DD/MM/YYYY HH:mm'')); break; } //Ensure this code only runs once $(this).removeClass("datetime_init"); }); }