Editör / Ekran şablonlarındaki bölümleri kullanma


104

Tüm JavaScript kodumu tek bir bölümde tutmak istiyorum; kapanıştan hemen öncebodyana düzen sayfamdaki etiketinden bunun için en iyisinin ne olduğunu merak ediyorum, MVC stili.

Örneğin, DisplayTemplate\DateTime.cshtmljQuery UI's DateTime Picker'ı kullanan bir dosya oluşturursam , JavaScript'i doğrudan o şablona gömeceğim, ancak daha sonra sayfanın ortasını oluşturacaktır.

Normal görünümlerimde @section JavaScript { //js here }ve sonra @RenderSection("JavaScript", false)ana düzenimde kullanabilirim, ancak bu görüntü / düzenleyici şablonlarında çalışmıyor gibi görünüyor - herhangi bir fikir?


4
buna daha sonra gelen herkes için - bununla başa çıkmak için bir nuget paketi var: nuget.org/packages/Forloop.HtmlHelpers
Russ Cam

Yanıtlar:


189

İki yardımcıdan oluşan bir birleşimle ilerleyebilirsiniz:

public static class HtmlExtensions
{
    public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
    {
        htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
        return MvcHtmlString.Empty;
    }

    public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
    {
        foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
        {
            if (key.ToString().StartsWith("_script_"))
            {
                var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
                if (template != null)
                {
                    htmlHelper.ViewContext.Writer.Write(template(null));
                }
            }
        }
        return MvcHtmlString.Empty;
    }
}

ve sonra sizin _Layout.cshtml:

<body>
...
@Html.RenderScripts()
</body>

ve bazı şablonlarda bir yerde:

@Html.Script(
    @<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
)

3
Sözlük düzensiz olduğu için ilk giren ilk çıkarımı nasıl yaparım? Verdiği sıra rastgele (muhtemelen Kılavuz nedeniyle) ..
eth0

Belki de statik bir tamsayı alanı ayarlayabilir ve sıralama elde etmek için GUID yerine Interlocked.Increment () kullanabilirsiniz, ancak o zaman bile bir sözlüğün sıralamayı asla garanti etmediğini düşünüyorum. İkinci olarak, statik bir alan, sayfa görüntülerinde tutulabileceği için tehlikeli olabilir. Bunun yerine Öğeler sözlüğüne bir tamsayı ekleyebilir, ancak etrafına bir kilit koymanız gerekir.
Mark Adamson

Son zamanlarda bu çözümü kullanmaya başladım, ancak HelperResult'un nasıl çalıştığından emin olmadığım için tek bir @ Html.Script () satırında iki komut dosyası dolduramıyorum. 1 Html.Script çağrısında 2 betik bloğu yapmak mümkün değil mi?
Langdon

2
@TimMeers, ne demek istiyorsun? Benim için tüm bunlar her zaman demode oldu. O yardımcıları hiç kullanmazdım. Kısmi görünümlerime herhangi bir komut dosyası ekleme ihtiyacım hiç olmadı. Standart Razor'a sadık kalırdım sections. MVC4'te Bundling gerçekten kullanılabilir ve komut dosyalarının boyutunu azaltmaya yardımcı olur.
Darin Dimitrov

4
Komut dosyalarınızı veya stillerinizi headetiketin sonu yerine etikete yerleştirmek istiyorsanız bu yaklaşım işe yaramaz body, çünkü @Html.RenderScripts()kısmi görünümünüzden önce ve dolayısıyla daha önce yürütülecektir @Html.Script().
Maksim Vi.

41

Siparişi sağlamak için Darin'in cevabının değiştirilmiş versiyonu. Ayrıca CSS ile de çalışır:

public static IHtmlString Resource(this HtmlHelper HtmlHelper, Func<object, HelperResult> Template, string Type)
{
    if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type]).Add(Template);
    else HtmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, HelperResult>>() { Template };

    return new HtmlString(String.Empty);
}

public static IHtmlString RenderResources(this HtmlHelper HtmlHelper, string Type)
{
    if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null)
    {
        List<Func<object, HelperResult>> Resources = (List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type];

        foreach (var Resource in Resources)
        {
            if (Resource != null) HtmlHelper.ViewContext.Writer.Write(Resource(null));
        }
    }

    return new HtmlString(String.Empty);
}

Aşağıdaki gibi JS ve CSS kaynakları ekleyebilirsiniz:

@Html.Resource(@<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>, "js")
@Html.Resource(@<link rel="stylesheet" href="@Url.Content("~/CSS/style.css")" />, "css")

JS ve CSS kaynaklarını şu şekilde oluşturun:

@Html.RenderResources("js")
@Html.RenderResources("css")

Komut dosyası / bağlantı ile başlayıp başlamadığını görmek için bir dize kontrolü yapabilirsiniz, böylece her bir kaynağın ne olduğunu açıkça tanımlamanız gerekmez.


Teşekkürler eth0. Bu konudan ödün verdim, ancak bunu kontrol etmem gerekecek.
one.beat.consumer

Bunu neredeyse 2 yıl önce biliyorum, ancak css / js dosyasının zaten var olup olmadığını kontrol etmenin ve onu oluşturmamanın bir yolu var mı? Teşekkürler
CodingSlayer

1
tamam. Ne kadar etkili olduğundan emin değilim, ancak şu anda bunu yapıyorum: var httpTemplates = HtmlHelper.ViewContext.HttpContext.Items [Type] as List <Func <object, HelperResult >>; var prevItem = http Şablonlarındaki q'dan q (null) .ToString () == Şablon (null) .ToString () q seçin; if (! prevItem.Any ()) {// Şablon Ekle}
CodingSlayer

@imAbhi teşekkürler, tam da ihtiyacım olan şey, item ile birlikte 1 for-loop paketine benziyor.ToString yani yeterince hızlı olması gerektiğini düşünüyorum
Kunukn

35

Aynı sorunla karşılaştım, ancak burada önerilen çözümler yalnızca kaynağa referans eklemek için iyi çalışıyor ve satır içi JS kodu için çok uygun değil. Çok yararlı bir makale buldum ve tüm satır içi JS'mi (ve ayrıca komut dosyası etiketlerimi)

@using (Html.BeginScripts())
{
    <script src="@Url.Content("~/Scripts/jquery-ui-1.8.18.min.js")" type="text/javascript"></script>
    <script>
    // my inline scripts here
    <\script>
}

Ve _Layout görünümünde, @Html.PageScripts()'body' etiketini kapatmadan hemen önce yerleştirilir . Benim için bir cazibe gibi çalışıyor.


Yardımcıların kendileri:

public static class HtmlHelpers
{
    private class ScriptBlock : IDisposable
    {
        private const string scriptsKey = "scripts";
        public static List<string> pageScripts
        {
            get
            {
                if (HttpContext.Current.Items[scriptsKey] == null)
                    HttpContext.Current.Items[scriptsKey] = new List<string>();
                return (List<string>)HttpContext.Current.Items[scriptsKey];
            }
        }

        WebViewPage webPageBase;

        public ScriptBlock(WebViewPage webPageBase)
        {
            this.webPageBase = webPageBase;
            this.webPageBase.OutputStack.Push(new StringWriter());
        }

        public void Dispose()
        {
            pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString());
        }
    }

    public static IDisposable BeginScripts(this HtmlHelper helper)
    {
        return new ScriptBlock((WebViewPage)helper.ViewDataContainer);
    }

    public static MvcHtmlString PageScripts(this HtmlHelper helper)
    {
        return MvcHtmlString.Create(string.Join(Environment.NewLine, ScriptBlock.pageScripts.Select(s => s.ToString())));
    }
}

3
bu en iyi cevap; aynı zamanda hemen hemen her şeyi enjekte etmenizi ve sonuna kadar ertelemenizi sağlar
drzaus

1
Aşağı inme ihtimaline karşı makaledeki kodu kopyalayıp yapıştırmalısınız! Bu mükemmel bir cevap!
Shaamaan

Bunu asp.net çekirdeğinde nasıl yapabiliriz
ramanmittal

13

Ben sevdim çözüm ben bunu kombine yüzden, @ john-w-Harding tarafından gönderildi cevap bir kullanarak blok içinde (çok komut) herhangi bir html render gecikme sağlar aşağıdaki muhtemelen overcomplicated çözüm yapmak için @ darin-Dimitrov.

KULLANIM

Tekrarlanan kısmi görünümde, bloğu yalnızca bir kez dahil edin:

@using (Html.Delayed(isOnlyOne: "MYPARTIAL_scripts")) {
    <script>
        someInlineScript();
    </script>
}

Bir (tekrarlanan?) Kısmi görünümde, kısmi her kullanıldığında için bloğu dahil edin:

@using (Html.Delayed()) {
    <b>show me multiple times, @Model.Whatever</b>
}

Bir (tekrarlanan?) Kısmi görünümde, bloğu bir kez ekleyin ve daha sonra özellikle adıyla oluşturun one-time:

@using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    <b>show me once by name</b>
    <span>@Model.First().Value</span>
}

İşlemek için:

@Html.RenderDelayed(); // the "default" unidentified blocks
@Html.RenderDelayed("one-time", false); // render the specified block by name, and allow us to render it again in a second call
@Html.RenderDelayed("one-time"); // render the specified block by name
@Html.RenderDelayed("one-time"); // since it was "popped" in the last call, won't render anything

KOD

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
    /// </summary>
    private class DelayedInjectionBlock : IDisposable {
        /// <summary>
        /// Unique internal storage key
        /// </summary>
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// <summary>
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// </summary>
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// <summary>
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// </summary>
        private const string EMPTY_IDENTIFIER = "";

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// <summary>
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// </summary>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// <summary>
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
        /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// <summary>
        /// Append the internal content to the context's cached list of output delegates
        /// </summary>
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();

            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// <summary>
    /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
    /// <para>
    /// <example>
    /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show at later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
    /// <code>
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     <b>show me once</b>
    ///     <span>@Model.First().Value</span>
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
    /// <returns>using block to wrap delayed output</returns>
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// <summary>
    /// Render all queued output blocks injected via <see cref="Delayed"/>.
    /// <para>
    /// <example>
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show me later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>more for later</b>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
    /// <code>
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// </code>
    /// </example>
    /// </para>

    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="removeAfterRendering">only render this once</param>
    /// <returns>rendered output content</returns>
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}

Tuhaf. Cevabı bu diğer konuya kopyaladığımı hatırlamıyorum , ama orada biraz daha iyi bir
yazı yazdım

12

Forloop.HtmlHelpers nuget paketini yükleyin - kısmi görünümlerde ve düzenleyici şablonlarında komut dosyalarını yönetmek için bazı yardımcılar ekler.

Düzeninizde bir yerde aramanız gerekir

@Html.RenderScripts()

Bu, herhangi bir komut dosyası dosyasının ve komut dosyası bloğunun sayfada çıktısının alınacağı yer olacaktır, bu nedenle bunu, mizanpajdaki ana komut dosyalarınızın ve bir komut dosyaları bölümünün (varsa) sonrasına koymanızı tavsiye ederim.

Paketleme ile Web Optimizasyon Çerçevesi kullanıyorsanız, aşırı yüklemeyi kullanabilirsiniz.

@Html.RenderScripts(Scripts.Render)

böylece bu yöntem komut dosyalarını yazmak için kullanılır.

Artık bir görünüme, kısmi görünüme veya şablona komut dosyası dosyaları veya bloklar eklemek istediğinizde,

@using (Html.BeginScriptContext())
{
  Html.AddScriptFile("~/Scripts/jquery.validate.js");
  Html.AddScriptBlock(
    @<script type="text/javascript">
       $(function() { $('#someField').datepicker(); });
     </script>
  );
}

Yardımcılar, birden çok kez eklenirse yalnızca bir komut dosyası referansının oluşturulmasını ve ayrıca komut dosyalarının beklenen bir sırada işlenmesini sağlar.

  1. Yerleşim
  2. Parçalar ve Şablonlar (görünümde göründükleri sırayla, yukarıdan aşağıya)

5

Bu gönderi bana gerçekten yardımcı oldu, bu yüzden temel fikir uygulamamı göndereceğimi düşündüm. @ Html.Resource işlevinde kullanılmak üzere komut dosyası etiketlerini döndürebilen bir yardımcı işlev geliştirdim.

Ayrıca bir JS veya CSS kaynağını tanımlamak için yazılan değişkenleri kullanabilmem için basit bir statik sınıf ekledim.

public static class ResourceType
{
    public const string Css = "css";
    public const string Js = "js";
}

public static class HtmlExtensions
{
    public static IHtmlString Resource(this HtmlHelper htmlHelper, Func<object, dynamic> template, string Type)
    {
        if (htmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type]).Add(template);
        else htmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, dynamic>>() { template };

        return new HtmlString(String.Empty);
    }

    public static IHtmlString RenderResources(this HtmlHelper htmlHelper, string Type)
    {
        if (htmlHelper.ViewContext.HttpContext.Items[Type] != null)
        {
            List<Func<object, dynamic>> resources = (List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type];

            foreach (var resource in resources)
            {
                if (resource != null) htmlHelper.ViewContext.Writer.Write(resource(null));
            }
        }

        return new HtmlString(String.Empty);
    }

    public static Func<object, dynamic> ScriptTag(this HtmlHelper htmlHelper, string url)
    {
        var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        var script = new TagBuilder("script");
        script.Attributes["type"] = "text/javascript";
        script.Attributes["src"] = urlHelper.Content("~/" + url);
        return x => new HtmlString(script.ToString(TagRenderMode.Normal));
    }
}

Ve kullanımda

@Html.Resource(Html.ScriptTag("Areas/Admin/js/plugins/wysiwyg/jquery.wysiwyg.js"), ResourceType.Js)

Yanıtı verilen @Darin Dimitrov sayesinde burada sorumu .


2

HtmlHelper kullanarak Kısmi Bir Razor Bölümünü Doldurma bölümünde verilen yanıt RequireScriptaynı modeli izler. Aynı Javascript URL'sine yinelenen referansları kontrol etmesi ve bastırması avantajına da sahiptir ve prioritysıralamayı kontrol etmek için kullanılabilecek açık bir parametreye sahiptir.

Bu çözümü aşağıdakiler için yöntemler ekleyerek genişlettim:

// use this for scripts to be placed just before the </body> tag
public static string RequireFooterScript(this HtmlHelper html, string path, int priority = 1) { ... }
public static HtmlString EmitRequiredFooterScripts(this HtmlHelper html) { ... }

// use this for CSS links
public static string RequireCSS(this HtmlHelper html, string path, int priority = 1) { ... }
public static HtmlString EmitRequiredCSS(this HtmlHelper html) { ... }

Darin'in ve eth0'ın çözümlerini HelperResult, sadece Javascript ve CSS dosyalarına değil, komut dosyası ve CSS bloklarına izin veren şablonu kullandıkları için seviyorum .


1

Paket genişletme kullanımıyla kullanılacak @Darin Dimitrov ve @ eth0 yanıtları:

@Html.Resources(a => new HelperResult(b => b.Write( System.Web.Optimization.Scripts.Render("~/Content/js/formBundle").ToString())), "jsTop")
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.