Kentico 13:PartialWidgetPage中HtmlHelper扩展无法写入_Layout.cshtml
问题描述
我正在使用PartialWidgetPage重现旧版Kentico的MasterPage功能,导航(Nav)和页脚(Footer)能正常渲染到_Layout.cshtml中,但页面内的某个组件/小部件出现问题:该部件通过自定义HtmlHelper扩展,以类似@RenderSection()的方式向_Layout.cshtml写入内容,在普通页面中可正常工作,但当它位于由PartialWidgetPage渲染的页面中时,无法写入_Layout.cshtml。
自定义HtmlHelper扩展代码如下:
public static class HtmlHelperExtensions { private static IHttpContextAccessor httpContextAccessor; public static HtmlString AddResource(this IHtmlHelper htmlHelper, PageResourceType resourceType, string identifier, Func<object, HelperResult> template) { httpContextAccessor = Service.Resolve<IHttpContextAccessor>(); if (httpContextAccessor.HttpContext.Items[resourceType] != null) { ((List<(Func<object, HelperResult>, string)>)httpContextAccessor.HttpContext.Items[resourceType]).Add((template, identifier)); } else { httpContextAccessor.HttpContext.Items[resourceType] = new List<(Func<object, HelperResult>, string)>() { (template, identifier) }; } return new HtmlString(string.Empty); } public static HtmlString RenderResources(this IHtmlHelper htmlHelper, PageResourceType resourceType) { httpContextAccessor = Service.Resolve<IHttpContextAccessor>(); if (httpContextAccessor.HttpContext.Items[resourceType] != null) { List<(Func<object, HelperResult>, string)> resources = (List<(Func<object, HelperResult>, string)>)httpContextAccessor.HttpContext.Items[resourceType]; foreach (var resource in resources.DistinctBy(r => r.Item2)) { if (resource.Item1 != null) { htmlHelper.ViewContext.Writer.Write(resource.Item1(null)); } } } return new HtmlString(string.Empty); } }
_Layout.cshtml中的调用方式:
@Html.RenderResources(PageResourceType.HeadScript)
问题原因
- 静态字段线程安全问题:扩展中使用了静态的
httpContextAccessor,多请求并发场景下会导致上下文混乱,影响资源的正确存储与读取。 - 渲染时机不匹配:如果
@Html.RenderResources在_Layout.cshtml的<head>标签中调用(早于@RenderBody()执行),PartialWidgetPage渲染时添加的资源会在RenderResources执行之后才存入HttpContext.Items,无法被渲染输出。
解决方案
1. 修复HtmlHelper扩展的线程安全问题
移除静态的httpContextAccessor,直接从当前IHtmlHelper的ViewContext获取请求上下文,避免并发冲突:
public static class HtmlHelperExtensions { public static HtmlString AddResource(this IHtmlHelper htmlHelper, PageResourceType resourceType, string identifier, Func<object, HelperResult> template) { var httpContext = htmlHelper.ViewContext.HttpContext; if (httpContext.Items.TryGetValue(resourceType, out var existing)) { if (existing is List<(Func<object, HelperResult>, string)> resources) { resources.Add((template, identifier)); } } else { httpContext.Items[resourceType] = new List<(Func<object, HelperResult>, string)>() { (template, identifier) }; } return new HtmlString(string.Empty); } public static HtmlString RenderResources(this IHtmlHelper htmlHelper, PageResourceType resourceType) { var httpContext = htmlHelper.ViewContext.HttpContext; if (httpContext.Items.TryGetValue(resourceType, out var existing)) { if (existing is List<(Func<object, HelperResult>, string)> resources) { foreach (var resource in resources.DistinctBy(r => r.Item2)) { resource.Item1?.Invoke(null)?.WriteTo(htmlHelper.ViewContext.Writer); } } } return new HtmlString(string.Empty); } }
2. 调整渲染顺序确保资源被收集
将@Html.RenderResources的调用位置移到@RenderBody()之后(比如</body>标签前),确保所有页面内容(包括PartialWidgetPage中的部件)渲染时添加的资源,能在RenderResources执行前被存入HttpContext.Items并输出:
<!-- _Layout.cshtml --> <body> @RenderBody() <!-- 移到此处,确保所有内容渲染完成后再输出资源 --> @Html.RenderResources(PageResourceType.HeadScript) </body>
如果必须将资源输出到<head>标签中,可考虑异步渲染或提前收集部件资源,但复杂度较高,优先推荐调整渲染顺序的方案。
内容的提问来源于stack exchange,提问作者rory
相关产品推荐
相关产品推荐

