Как использовать Razor Section несколько раз в View & PartialView (слияние) без его переопределения?
в файле _Layout.cshtml, у меня есть раздел внизу тела под названием "ScriptsContent", объявленный следующим образом:
@RenderSection("ScriptsContent", required: false)
На мой взгляд, я могу использовать этот раздел для добавления скриптов, которые будут выполняться. Но что, если у меня также есть PartialView, который также должен использовать этот раздел для добавления дополнительных скриптов?
Просмотр
@section ScriptsContent
{
<script type="text/javascript">
alert(1);
</script>
}
@Html.Partial("PartialView")
PartialView
@section ScriptsContent
{
<script type="text/javascript">
alert(2);
</script>
}
Результат
Отображается только первый script. Второй script не существует в исходном коде веб-страницы.
Кажется, что Razor выводит только первый скриптовый элемент @section ScriptsContent, который он видит. Я хотел бы знать, есть ли способ объединить каждый вызов в раздел.
Если мы не сможем сделать это, что вы предлагаете?
Благодарю вас за помощь!
UPDATE
Я нашел код, который решает проблему. Посмотрите мой ответ ниже.
Ответы
Ответ 1
Здесь решение этой проблемы. Это из этого блога: http://blog.logrythmik.com/post/A-Script-Block-Templated-Delegate-for-Inline-Scripts-in-Razor-Partials.aspx
public static class ViewPageExtensions
{
private const string SCRIPTBLOCK_BUILDER = "ScriptBlockBuilder";
public static MvcHtmlString ScriptBlock(this WebViewPage webPage, Func<dynamic, HelperResult> template)
{
if (!webPage.IsAjax)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(template(null).ToHtmlString());
webPage.Context.Items[SCRIPTBLOCK_BUILDER] = scriptBuilder;
return new MvcHtmlString(string.Empty);
}
return new MvcHtmlString(template(null).ToHtmlString());
}
public static MvcHtmlString WriteScriptBlocks(this WebViewPage webPage)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
return new MvcHtmlString(scriptBuilder.ToString());
}
}
поэтому в любом случае в вашем представлении или в PartialView вы можете использовать это:
@this.ScriptBlock(
@<script type='text/javascript'>
alert(1);
</script>
)
и в вашем _Layout или MasterView, используйте это:
@this.WriteScriptBlocks()
Ответ 2
Нет способа совместного использования разделов между представлением и частичными представлениями.
Отсутствие решения, похожего на ScriptManager, может иметь набор файлов script (инициализированных в вашем представлении и сохраненных либо в HttpContext.Items
, либо в ViewData
), к которым частичное представление добавит файл script требуемые имена. Затем в конце вашего представления вы объявите раздел, который извлекает эту коллекцию и испускает теги script.
Ответ 3
Проблема с принятым ответом заключается в том, что он прерывает кэширование выходных данных. Трюк для решения этого заключается в том, чтобы перезаписать атрибут OutputCache вашей собственной реализацией. К сожалению, мы не можем расширять исходный атрибут, поскольку он имеет множество внутренних методов, к которым нам нужно обратиться.
Я использую Donut Output Caching, которое перезаписывает атрибут OutputCache. Существуют альтернативные библиотеки, которые также используют свой собственный атрибут OutputCache, поэтому я объясню шаги, которые я сделал, чтобы заставить его работать, чтобы вы могли применить его к тому, что вы используете.
Сначала вам нужно скопировать существующий атрибут OutputCache и поместить его в ваше приложение. Вы можете получить существующий атрибут, посмотрев исходный код.
Теперь добавьте следующее свойство в класс. Здесь мы храним блоки script, чтобы мы могли отображать правильные при извлечении из кеша.
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
Теперь внутри метода OnActionExecuting вам нужно сохранить ключ кеша (уникальный идентификатор выходного кэша) внутри коллекции текущих запросов. Например:
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
Теперь измените класс ViewPageExtensions, добавив следующее (заменив CustomOutputCacheAttribute на имя вашего атрибута):
var outputCacheKey = webPage.Context.Items["OutputCacheKey"] as string;
if (outputCacheKey != null)
CustomOutputCacheAttribute.ScriptBlocks.AddOrUpdate(outputCacheKey, new StringBuilder(template(null).ToHtmlString()), (k, sb) => {
sb.Append(template(null).ToHtmlString());
return sb;
});
перед:
return new MvcHtmlString(string.Empty);
Примечание. Для небольшого повышения производительности вы также захотите, чтобы вы только один раз вызывали "шаблон (null).ToHtmlString()".
Теперь вернитесь к своему пользовательскому атрибуту OutputCache и добавьте следующий только при извлечении из кеша внутри метода OnActionExecuting:
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
Здесь последний код моего атрибута:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using DevTrends.MvcDonutCaching;
public class CustomOutputCacheAttribute : ActionFilterAttribute, IExceptionFilter {
private readonly IKeyGenerator _keyGenerator;
private readonly IDonutHoleFiller _donutHoleFiller;
private readonly IExtendedOutputCacheManager _outputCacheManager;
private readonly ICacheSettingsManager _cacheSettingsManager;
private readonly ICacheHeadersHelper _cacheHeadersHelper;
private bool? _noStore;
private CacheSettings _cacheSettings;
public int Duration { get; set; }
public string VaryByParam { get; set; }
public string VaryByCustom { get; set; }
public string CacheProfile { get; set; }
public OutputCacheLocation Location { get; set; }
public bool NoStore {
get { return _noStore ?? false; }
set { _noStore = value; }
}
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
public DonutOutputCacheAttribute() {
var keyBuilder = new KeyBuilder();
_keyGenerator = new KeyGenerator(keyBuilder);
_donutHoleFiller = new DonutHoleFiller(new EncryptingActionSettingsSerialiser(new ActionSettingsSerialiser(), new Encryptor()));
_outputCacheManager = new OutputCacheManager(OutputCache.Instance, keyBuilder);
_cacheSettingsManager = new CacheSettingsManager();
_cacheHeadersHelper = new CacheHeadersHelper();
Duration = -1;
Location = (OutputCacheLocation)(-1);
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
_cacheSettings = BuildCacheSettings();
var cacheKey = _keyGenerator.GenerateKey(filterContext, _cacheSettings);
if (_cacheSettings.IsServerCachingEnabled) {
var cachedItem = _outputCacheManager.GetItem(cacheKey);
if (cachedItem != null) {
filterContext.Result = new ContentResult {
Content = _donutHoleFiller.ReplaceDonutHoleContent(cachedItem.Content, filterContext),
ContentType = cachedItem.ContentType
};
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
}
}
if (filterContext.Result == null) {
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
var cachingWriter = new StringWriter(CultureInfo.InvariantCulture);
var originalWriter = filterContext.HttpContext.Response.Output;
filterContext.HttpContext.Response.Output = cachingWriter;
filterContext.HttpContext.Items[cacheKey] = new Action<bool>(hasErrors => {
filterContext.HttpContext.Items.Remove(cacheKey);
filterContext.HttpContext.Response.Output = originalWriter;
if (!hasErrors) {
var cacheItem = new CacheItem {
Content = cachingWriter.ToString(),
ContentType = filterContext.HttpContext.Response.ContentType
};
filterContext.HttpContext.Response.Write(_donutHoleFiller.RemoveDonutHoleWrappers(cacheItem.Content, filterContext));
if (_cacheSettings.IsServerCachingEnabled && filterContext.HttpContext.Response.StatusCode == 200)
_outputCacheManager.AddItem(cacheKey, cacheItem, DateTime.UtcNow.AddSeconds(_cacheSettings.Duration));
}
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
ExecuteCallback(filterContext, false);
if (!filterContext.IsChildAction)
_cacheHeadersHelper.SetCacheHeaders(filterContext.HttpContext.Response, _cacheSettings);
}
public void OnException(ExceptionContext filterContext) {
if (_cacheSettings != null)
ExecuteCallback(filterContext, true);
}
private void ExecuteCallback(ControllerContext context, bool hasErrors) {
var cacheKey = _keyGenerator.GenerateKey(context, _cacheSettings);
var callback = context.HttpContext.Items[cacheKey] as Action<bool>;
if (callback != null)
callback.Invoke(hasErrors);
}
private CacheSettings BuildCacheSettings() {
CacheSettings cacheSettings;
if (string.IsNullOrEmpty(CacheProfile)) {
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally,
Duration = Duration,
VaryByCustom = VaryByCustom,
VaryByParam = VaryByParam,
Location = (int)Location == -1 ? OutputCacheLocation.Server : Location,
NoStore = NoStore
};
} else {
var cacheProfile = _cacheSettingsManager.RetrieveOutputCacheProfile(CacheProfile);
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally && cacheProfile.Enabled,
Duration = Duration == -1 ? cacheProfile.Duration : Duration,
VaryByCustom = VaryByCustom ?? cacheProfile.VaryByCustom,
VaryByParam = VaryByParam ?? cacheProfile.VaryByParam,
Location = (int)Location == -1 ? ((int)cacheProfile.Location == -1 ? OutputCacheLocation.Server : cacheProfile.Location) : Location,
NoStore = _noStore.HasValue ? _noStore.Value : cacheProfile.NoStore
};
}
if (cacheSettings.Duration == -1)
throw new HttpException("The directive or the configuration settings profile must specify the 'duration' attribute.");
if (cacheSettings.Duration < 0)
throw new HttpException("The 'duration' attribute must have a value that is greater than or equal to zero.");
return cacheSettings;
}
}
Мне также пришлось модифицировать библиотеку кэша вывода Donut, чтобы сделать IExtendedOutputCacheManager и конструктор OutputCacheManager общедоступными.
Обратите внимание, что это было извлечено из моего приложения и может потребовать некоторых небольших настроек. Вы также должны разместить WriteScriptBlocks в нижней части страницы, чтобы он не вызывался до тех пор, пока не будут запущены все дочерние действия.
Надеюсь, что это поможет.