Подтвердить что ты не робот

Как использовать 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

Я нашел код, который решает проблему. Посмотрите мой ответ ниже.

4b9b3361

Ответ 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 в нижней части страницы, чтобы он не вызывался до тех пор, пока не будут запущены все дочерние действия.

Надеюсь, что это поможет.