Preencher uma seção de navalha de um parcial

102

Minha principal motivação para tentar fazer isso é obter o Javascript que só é exigido por uma parcial na parte inferior da página com o restante do Javascript e não no meio da página onde a parcial é renderizada.

Aqui está um exemplo simplificado do que estou tentando fazer:

Aqui está o layout com uma seção de Scripts logo antes do corpo.

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />    
</head>

<body>
    @RenderBody()
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
    @RenderSection("Scripts", false)
</body>
</html>

Aqui está um exemplo de visualização usando este layout.

<h2>This is the view</h2>

@{Html.RenderPartial("_Partial");}

@section Scripts {
<script type="text/javascript">
        alert("I'm a view.");
</script>
}

E aqui está o parcial sendo renderizado a partir da vista.

<p>This is the partial.</p>

@* this never makes it into the rendered page *@
@section Scripts {
<script type="text/javascript">
    alert("I'm a partial."); 
</script>
}

Neste exemplo, a marcação especificada na vista é colocada na seção, mas a marcação parcial não é. É possível preencher uma seção de uma visualização parcial com o Razor? Se não, quais são alguns outros métodos de obtenção de Javascript que são necessários apenas para parciais na parte inferior da página, sem incluí-lo globalmente?

Craig M
fonte
talvez seja um problema porque você tem outra seção de script parcial .. IDK .. seu código é um pouco confuso ..
gideon
Não é. Mesmo que a seção seja deixada de fora da visualização, o código parcial não chega à página final renderizada. Acho que o SLaks está correto ao dizer que parciais não podem participar das seções da visão pai.
Craig M de

Respostas:

78

A maneira como lidei com isso é escrever alguns métodos de extensão para a classe HtmlHelper. Isso permite que as visualizações parciais digam que precisam de um script e, em seguida, na visualização de layout que grava a tag, chamo meu método auxiliar para emitir os scripts necessários

Aqui estão os métodos auxiliares:

public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
    if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
    return null;
}

public static HtmlString EmitRequiredScripts(this HtmlHelper html)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) return null;
    StringBuilder sb = new StringBuilder();
    foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
    {
        sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
    }
    return new HtmlString(sb.ToString());
}
public class ResourceInclude
{
    public string Path { get; set; }
    public int Priority { get; set; }
}

Depois de ter isso no lugar, sua visão parcial só precisa ser ativada @Html.RequireScript("/Path/To/Script").

E na seção principal da visualização de layout você chama @Html.EmitRequiredScripts().

Um bônus adicional disso é que ele permite eliminar solicitações de script duplicadas. Se você tiver múltiplas visões / visões parciais que precisam de um dado script, você pode assumir com segurança que só o produzirá uma vez

Sr. Bell
fonte
Solução elegante e limpa. +1
bevacqua
Acabei de encontrar esta solução depois de arrancar a maior parte do meu cabelo - excelente solução ....
higgsy
Não consigo fazer essa solução funcionar. Parece que EmitRequiredScripts () é chamado antes que qualquer visualização parcial chame RequireScript (). Estou fazendo algo errado?
Bryan Roth
Algo não parece certo, Bryan. Usei essa solução extensivamente ao longo do último ano e tem funcionado bem. Talvez poste uma nova pergunta com os detalhes do seu problema e vincule o url aqui
Sr. Bell
1
Isso tem suporte para impedimento de cache ao implantar uma nova versão do aplicativo? O método @ scripts.Render () pronto para uso coloca um parâmetro de URL no final, que é gerado no momento da construção, para que o navegador seja forçado a buscar a versão mais recente quando uma nova versão for implantada.
Simon Green
28

Vistas parciais não podem participar das seções de suas vistas-pai.

SLaks
fonte
1
Isso é o que eu suspeitei. Obrigado.
Craig M de
@JohnBubriski Existe no Razor 2. Não sei sobre o anterior. versões.
Shimmy Weitzhandler
@SLaks, por que isso é intencional? No meu cenário, tenho um parcial que é um rotador de banner, quero que seus scripts / estilos sejam carregados apenas quando estiver ativado. Por que é ruim carregá-lo embutido?
Shimmy Weitzhandler
2
@Shimmy: Você deve usar um sistema de gerenciamento de recursos, como Cassete.
SLaks
Obrigado. Vou dar uma olhada nisso.
Shimmy Weitzhandler
13

Você poderia ter uma segunda parcial encarregada apenas de injetar o javascript necessário. Coloque vários scripts em torno dos @ifblocos, se quiser:

@model string
@if(Model == "bla") {
    <script type="text/javascript">...</script>
}

@else if(Model == "bli") {
    <script type="text/javascript">...</script>
}

Isso obviamente poderia ser um pouco limpo, mas então, na Scriptsseção de sua visão:

@section Scripts
{
    @Html.Partial("_Scripts", "ScriptName_For_Partial1")
}

Novamente, pode não ganhar um prêmio de beleza, mas funcionará.

Sergi Papaseit
fonte
1
Isso é muito próximo do que acabei fazendo. Definitivamente não é bonito, mas funciona. A única desvantagem disso é que você não pode obter o parcial por meio de uma chamada ajax e ter o JS incluído. Acho que a longo prazo, vou acabar refatorando usando modelos jQuery e apenas enviar JSON dos meus controladores em vez de construir o html no lado do servidor.
Craig M
@CraigM é para onde estou indo também. MVC é legítimo, mas faz muito mais sentido (para mim) usar modelos do lado do cliente (estou procurando no Backbone.js) e, em seguida, enviar / receber push / pull de uma API.
one.beat.consumer
@ one.beat.customer - Tenho usado os modelos de sublinhado desde que também uso o Backbone, mas estou pensando em mudar para a biblioteca Hogan do Twitter ou Plates do Nodejitsu. Ambos têm recursos muito bons.
Craig M
10

A maneira mais elegante de fazer isso é mover scripts de visualização parcial para um arquivo separado e, em seguida, renderizá-lo na seção Scripts de visualização:

<h2>This is the view</h2>

@Html.RenderPartial("_Partial")

@section Scripts
{
    @Html.RenderPartial("_PartialScripts")

    <script type="text/javascript">
        alert("I'm a view script.");
    </script>
}

A visão parcial _ Partial.cshtml :

<p>This is the partial.</p>

A visão parcial _ PartialScripts.cshtml apenas com scripts:

<script type="text/javascript">
    alert("I'm a partial script!");
</script>
Vlad Rudenko
fonte
Isso não é tão automático quanto alguns dos métodos de extensão ou plug-ins sugeridos em outras respostas, mas tem a vantagem de simplicidade e clareza. Ele gosta disso.
Mark Meuer de
7

Instale o pacote nuget Forloop.HtmlHelpers - ele adiciona alguns auxiliares para gerenciar scripts em visualizações parciais e modelos de editor.

Em algum lugar do seu layout, você precisa ligar

@Html.RenderScripts()

É aqui que quaisquer arquivos de script e blocos de script serão produzidos na página, então eu recomendo colocá-lo após seus scripts principais no layout e após uma seção de scripts (se você tiver uma).

Se estiver usando o Web Optimization Framework com empacotamento, você pode usar a sobrecarga

@Html.RenderScripts(Scripts.Render)

para que este método seja usado para escrever arquivos de script.

Agora, sempre que desejar adicionar arquivos de script ou blocos em uma visão, visão parcial ou modelo, basta usar

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

Os ajudantes garantem que apenas uma referência de arquivo de script seja renderizada se adicionada várias vezes e também garante que os arquivos de script sejam renderizados em uma ordem esperada, ou seja,

  1. Layout
  2. Parciais e modelos (na ordem em que aparecem na visualização, de cima para baixo)
Russ Cam
fonte
5

[Versão atualizada] Versão atualizada após a pergunta @Necrocubus para incluir scripts embutidos.

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_INLINESCRIPT = "RequiredInlineScript";
    const string REQ_STYLE = "RequiredStyle";

    #region Scripts
    /// <summary>
    /// Adds a script 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, bool bottom=false, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options, Type=ResourceType.Script, Bottom=bottom});
        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="script"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <returns></returns>
    public static string RequireInlineScript(this IHtmlHelper html, string script, int priority = 1, bool bottom = false)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        if (requiredScripts == null) ctxt.Items[REQ_INLINESCRIPT] = requiredScripts = new List<InlineResource>();
        requiredScripts.Add(new InlineResource() { Content=script, Priority = priority, Bottom=bottom, Type=ResourceType.Script});
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredScripts(false)
    /// at the end of your head tag and 
    /// @Html.EmitRequiredScripts(true) at the end of the body if some scripts are set to be at the bottom.
    /// </summary>
    public static HtmlString EmitRequiredScripts(this IHtmlHelper html, bool bottom)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        var requiredInlineScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        var scripts = new List<Resource>();
        scripts.AddRange(requiredScripts ?? new List<ResourceToInclude>());
        scripts.AddRange(requiredInlineScripts ?? new List<InlineResource>());
        if (scripts.Count==0) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in scripts.Where(s=>s.Bottom==bottom).OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Scripts

    #region Styles
    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredStyles()
    /// at the end of your head tag
    /// </summary>
    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Styles

    #region Models
    public class InlineResource : Resource
    {
        public string Content { get; set; }
        public override string ToString()
        {
            return "<script>"+Content+"</script>";
        }
    }

    public class ResourceToInclude : Resource
    {
        public string Path { get; set; }
        public string[] Options { get; set; }
        public override string ToString()
        {
            switch(Type)
            {
                case ResourceType.CSS:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", Path);
                    else
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", Path, String.Join(" ", Options));
                default:
                case ResourceType.Script:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\"></script>\n", Path);
                    else
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", Path, String.Join(" ", Options));
            }
        }
    }
    public class Resource
    {
        public ResourceType Type { get; set; }
        public int Priority { get; set; }
        public bool Bottom { get; set; }
    }
    public enum ResourceType
    {
        Script,
        CSS
    }
    #endregion Models
}

Meus 2 centavos, é um post antigo, mas ainda relevante, então aqui está uma atualização atualizada da solução do Sr. Bell que funciona com ASP.Net Core.

Ele permite adicionar scripts e estilos ao layout principal a partir de visualizações parciais e subvisualizações importadas e a possibilidade de adicionar opções às importações de script / estilo (como adiamento assíncrono etc):

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_STYLE = "RequiredStyle";

    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredScripts(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            else
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", item.Path, String.Join(" ", item.Options));

        }
        return new HtmlString(sb.ToString());
    }


    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", item.Path);
            else
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", item.Path, String.Join(" ", item.Options));
        }
        return new HtmlString(sb.ToString());
    }


    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
        public string[] Options { get; set; }
    }
}
Jean
fonte
Obrigado cara! Isso deve ser mais votado porque é mais relevante do que a resposta de 6 anos.
Necroqubus
Além disso, essas extensões podem ser modificadas para permitir que seções de scripts sejam entradas? @ <text> </text> ou algo como seções? Caso contrário, ainda preciso de um pequeno script JS para inicializar o outro script com as variáveis ​​do modelo do lado do servidor: /
Necroqubus
@Necroqubus você pode conferir a versão atualizada, porém ainda não testei :)
Jean
Tudo bem, vou tentar e testar para você. Espero que funcione com ASP.NET Core 1.0 MVC. Para o contexto, tenho vários níveis de parciais aninhados e quero que seus scripts sejam renderizados no rodapé.
Necroqubus
Não precisa do <text>, apenas adicione-o como uma string (você ainda pode prefixar com @ "" para várias linhas, se preferir) e sem as <script>tags
Jean
1

Você pode criar uma nova Layoutpágina e envolver o PartialView dentro de um Full View que é responsável por renderizar o conteúdo e também quaisquer seções da biblioteca.

Por exemplo, digamos que eu tenha o seguinte código:

HomeController.cs

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return View("About", vm);
}

Quando a visualização de página inteira é renderizada, normalmente é renderizada pela fusão de dois arquivos:

About.cshtml

@model AboutViewModel

@{
    ViewBag.Title = "About CSHN";
}

<h3>@ViewBag.Title</h3>

@section Styles {
    <style> /* style info here */ </style>
}

@section Scripts {
    <script> /* script info here */ </script>
}

_Layout.cshtml (ou o que for especificado em _ViewStart ou substituído na página)

<!DOCTYPE html>

<html>
<head>
    @RenderSection("Styles", false)
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderBody()

    @RenderSection("scripts", false)
</body>
</html>

Agora , suponha que você queira renderizar About.cshtmlcomo uma visualização parcial , talvez como uma janela modal em resposta à chamada AJAX. O objetivo aqui é retornar apenas o conteúdo especificado na página sobre, scripts e tudo, sem todo o inchaço incluído no _Layout.cshtmllayout mestre (como um<html> documento ).

Você pode tentar assim, mas não virá com nenhum dos blocos de seção:

return PartialView("About", vm);

Em vez disso, adicione uma página de layout mais simples como esta:

_PartialLayout.cshtml

<div>
    @RenderBody()
    @RenderSection("Styles", false)
    @RenderSection("scripts", false)
</div>

Ou para suportar uma janela modal como esta:

_ModalLayout.cshtml

<div class="modal modal-page fade" tabindex="-1" role="dialog" >
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">

            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title">@ViewBag.Title</h4>
            </div>

            <div class="modal-body">

                @RenderBody()
                @RenderSection("Styles", false)
                @RenderSection("scripts", false)

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-inverse" data-dismiss="modal">Dismiss</button>
            </div>
        </div>
    </div>
</div>

Então você pode especificar uma Visão Mestre personalizada neste controlador ou qualquer outro manipulador que você deseja renderizar o conteúdo e scripts de uma visão simultaneamente

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return !Request.IsAjaxRequest()
              ? View("About", vm)
              : View("About", "~/Views/Shared/_ModalLayout.cshtml", vm);
}
KyleMit
fonte
1

Para quem procura a versão aspnet core 2.0:

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Http;

    public static class HttpContextAccessorExtensions
    {
        public static string RequireScript(this IHttpContextAccessor htmlContextAccessor, string path, int priority = 1)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) htmlContextAccessor.HttpContext.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
            if (requiredScripts.All(i => i.Path != path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
            return null;
        }

        public static HtmlString EmitRequiredScripts(this IHttpContextAccessor htmlContextAccessor)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) return null;
            StringBuilder sb = new StringBuilder();
            foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
            {
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            }
            return new HtmlString(sb.ToString());
        }
        public class ResourceInclude
        {
            public string Path { get; set; }
            public int Priority { get; set; }
        }
    }

Adicione ao seu layout após a chamada da seção de renderização dos scripts:

@HttpContextAccessor.EmitRequiredScripts()

E em sua visão parcial:

@inject IHttpContextAccessor HttpContextAccessor

...

@HttpContextAccessor.RequireScript("/scripts/moment.min.js")
Sebastian
fonte
0

Com base na resposta do Sr. Bell e Shimmy acima, adicionei uma função extra para o script Bundle.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Mvc;
namespace ABC.Utility
{
public static  class PartialViewHelper
{
    public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
        return null;
    }

    public static string RequireBundleStyles(this HtmlHelper html, string bundleName)
    {
        var a = System.Web.Optimization.Styles.Render(bundleName);
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) HttpContext.Current.Items["RequiredStyles"] = requiredStyles = a;
        return null;
    }

    public static string RequireBundleScripts(this HtmlHelper html, string bundleName)
    {
        var a=System.Web.Optimization.Scripts.Render(bundleName);
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = a;
        return null;
    }

    public static HtmlString EmitRequiredBundleStyles(this HtmlHelper html)
    {
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) return null;
        return MvcHtmlString.Create(requiredStyles.ToHtmlString()) ;
    }

    public static HtmlString EmitRequiredBundleScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) return null;
        return MvcHtmlString.Create(requiredScripts.ToHtmlString());
    }

    public static HtmlString EmitRequiredScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
        }
        return new HtmlString(sb.ToString());
    }
    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
    }
}//end class
}// end namespace  

Amostra em PartialView: - @ Html.RequireBundleStyles ("~ / bundles / fileupload / bootstrap / BasicPlusUI / css"); @ Html.RequireBundleScripts ("~ / bundles / fileupload / bootstrap / BasicPlusUI / js");

Amostra na MasterPage: - @ Html.EmitRequiredBundleStyles ()

Harris Yer
fonte
0

Use as @using(Html.Delayed()){ ...your content... }extensões da resposta https://stackoverflow.com/a/18790222/1037948 para renderizar qualquer conteúdo (scripts ou apenas HTML) posteriormente na página. Interno Queuedeve garantir o pedido correto.

drzaus
fonte
0

Essa funcionalidade também é implementada em ClientDependency.Core.Mvc.dll. Ele fornece os auxiliares de html: @ Html.RequiresJs e @ Html.RenderJsHere (). Pacote Nuget: ClientDependency-Mvc

Evert
fonte
0

Aqui vem minha solução para as perguntas mais frequentes "como injetar seções de visualizações parciais em visualizações principais ou visualização de layout principal para asp.net mvc?". Se você fizer uma pesquisa sobre stackoverflow por palavras-chave "seção + parcial", obterá uma lista bastante grande de perguntas relacionadas e respostas dadas, mas nenhuma delas parece elegante para mim por meio da gramática do mecanismo de navalha. Então, eu só dei uma olhada no motor do Razor para ver se poderia haver uma solução melhor para essa questão.

Felizmente, encontrei algo interessante para mim de como o mecanismo do Razor faz a compilação do arquivo de modelo de visão (* .cshtml, * .vbhtml). (Explicarei mais tarde), abaixo está meu código da solução que eu acho bastante simples e elegante no uso.

namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        /// <summary>
        /// 确保所有视图,包括分部视图(PartialView)中的节(Section)定义被按照先后顺序追加到最终文档输出流中
        /// </summary>
        public static MvcHtmlString EnsureSection(this HtmlHelper helper)
        {
            var wp = (WebViewPage)helper.ViewDataContainer;
            Dictionary<string, WebPages.SectionWriter> sw = (Dictionary<string, WebPages.SectionWriter>)typeof(WebPages.WebPageBase).GetProperty("SectionWriters", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance).GetValue(wp);
            if (!helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = new Dictionary<string, Stack<WebPages.SectionWriter>>();
                helper.ViewContext.HttpContext.Items["SectionWriter"] = qss;
            }
            var eqs = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
            foreach (var kp in sw)
            {
                if (!eqs.ContainsKey(kp.Key)) eqs[kp.Key] = new Stack<WebPages.SectionWriter>();
                eqs[kp.Key].Push(kp.Value);
            }
            return MvcHtmlString.Create("");
        }

        /// <summary>
        /// 在文档流中渲染指定的节(Section)
        /// </summary>
        public static MvcHtmlString RenderSectionEx(this HtmlHelper helper, string section, bool required = false)
        {
            if (helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
                if (qss.ContainsKey(section))
                {
                    var wp = (WebViewPage)helper.ViewDataContainer;
                    var qs = qss[section];
                    while (qs.Count > 0)
                    {
                        var sw = qs.Pop();
                        var os = ((WebViewPage)sw.Target).OutputStack;
                        if (os.Count == 0) os.Push(wp.Output);
                        sw.Invoke();
                    }
                }
                else if (!qss.ContainsKey(section) && required)
                {
                    throw new Exception(string.Format("'{0}' section is not defined.", section));
                }
            }
            return MvcHtmlString.Create("");
        }
    }
}

uso : Usar o código também é bastante simples e parece quase o mesmo estilo do usual. Ele também oferece suporte a qualquer nível para as visualizações parciais aninhadas. ie. Eu tenho uma cadeia de modelo de exibição: _ViewStart.cshtml-> layout.cshtml-> index.cshtml -> [head.cshtml, foot.cshtml] -> ad.cshtml.

Em layout.cshtml, temos:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>@ViewBag.Title - @ViewBag.WebSetting.Site.WebName</title>
    <base href="@ViewBag.Template/" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1.0, user-scalable=0,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <meta name="renderer" content="webkit">
    <meta name="author" content="Taro Technology Co.,LTD" />
    <meta name="robots" content="index,follow" />
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <link rel="alternate icon" type="@ViewBag.WebSetting.Site.WebFavIcon" href="@ViewBag.WebSetting.Site.WebFavIcon">
    @Html.RenderSectionEx("Head")
</head>
<body>
    @RenderBody()
    @Html.RenderSectionEx("Foot")
</body>
</html>

E em index.cshtml, temos:

@{
    ViewBag.Title = "首页";
}

@Html.Partial("head")
<div class="am-container-1">
    .......
</div>
@Html.Partial("foot")

E em head.cshtml, teríamos o código:

@section Head{
    <link rel="stylesheet" href="assets/css/amazeui.css" />
    <link rel="stylesheet" href="assets/css/style.css" />
}

<header class="header">
   ......
</header>
@Html.EnsureSection()

é o mesmo em foot.cshtml ou ad.cshtml, você ainda pode definir a seção Head ou Foot neles, certifique-se de chamar @ Html.EnsureSection () uma vez no final do arquivo de visualização parcial. Isso é tudo que você precisa fazer para se livrar do problema exposto no asp mvc.

Acabei de compartilhar meu snippet de código para que outros possam usá-lo. Se você acha que é útil, por favor, não hesite em avaliar minha postagem. :)

Jonathan
fonte