Injetando conteúdo em seções específicas de uma exibição parcial ASP.NET MVC 3 com o Razor View Engine


324

Eu tenho esta seção definida no meu _Layout.cshtml

@RenderSection("Scripts", false)

Eu posso usá-lo facilmente de uma vista:

@section Scripts { 
    @*Stuff comes here*@
}

O que estou enfrentando é como injetar algum conteúdo nesta seção a partir de uma exibição parcial.

Vamos assumir que esta é minha página de visualização:

@section Scripts { 

    <script>
        //code comes here
    </script>
}

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>

Preciso injetar algum conteúdo dentro da Scriptsseção da _myPartialvista parcial.

Como posso fazer isso?


17
para qualquer pessoa que venha a isso mais tarde - há um pacote nuget para lidar com isso: nuget.org/packages/Forloop.HtmlHelpers
Russ Cam

@RussCam, você deve responder a esta pergunta. +1 no pacote de pepitas resolve o problema exato que o OP está tendo.
Carrie Kendall

1
O pacote @RussCam NuGet não é uma solução, pode ser o código do pacote.
Maksim Vi.

8
@MaksimVi. bem, eu escrevi o pacote nuget e não tenho nenhuma intenção de removê-lo, então, em vez de repetir o código ( bitbucket.org/forloop/forloop-htmlhelpers/src ) ou o wiki ( bitbucket.org/forloop/forloop-htmlhelpers/wiki / Home ) aqui, um link para ele como comentário é mantido dentro do espírito do stackoverflow, IMO.
Russ Cam

Aqui está outra solução que parece muito bom: stackoverflow.com/questions/5355427/...
jkokorian

Respostas:


235

As seções não funcionam em vistas parciais e isso é por design. Você pode usar alguns auxiliares personalizados para obter um comportamento semelhante, mas, honestamente, é responsabilidade da exibição incluir os scripts necessários, não a responsabilidade da parte. Eu recomendaria usar a seção @scripts da exibição principal para fazer isso e não deixar as partes parciais se preocuparem com os scripts.


445
Mas e se o script for muito específico para o parcial? Não faz sentido lógico que seja definido na parcial, e não na visão?
Jez

43
Por que é por design?
Shimmy Weitzhandler

56
@Darin: eu discordo. E o princípio DRY? Não gosto de me repetir, mesmo que sejam apenas referências de script.
fretje

14
@ fretje, todos têm o direito de expressar sua opinião sobre o assunto. Eu respeito o seu. Na minha resposta, expressei a minha e vinculei-a a uma resposta que lhe permitiria realizar essa tarefa. Mas também destaquei o que eu recomendaria e faria para esta situação.
precisa

33
apoiar @JoshNoe e o resto - um "widget" (exibição + interação rica) é um exemplo perfeito de uma exibição parcial fortemente acoplada ao javascript associado. Por design , não preciso escrever duas declarações de inclusão em locais diferentes para obter a funcionalidade completa, porque a exibição nunca ficará sem a interação do atendente e a interação nunca aparecerá em outro lugar.
drzaus 12/09

83

Essa é uma pergunta bastante popular, então postarei minha solução.
Eu tive o mesmo problema e, embora não seja o ideal, acho que realmente funciona muito bem e não torna a parcial dependente da exibição.
Meu cenário era que uma ação era acessível por si só, mas também poderia ser incorporada em uma visualização - um mapa do Google.

No meu _layouteu tenho:

@RenderSection("body_scripts", false)

Na minha indexopinião, tenho:

@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

Na minha clientsopinião, eu tenho (todo o mapa e assoc. Html):

@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

Minha Clients_Scriptsvisão contém o javascript a ser renderizado na página

Dessa maneira, meu script é isolado e pode ser renderizado na página quando necessário, com a body_scriptstag sendo renderizada apenas na primeira ocorrência que o mecanismo de exibição do razor encontra.

Isso me permite separar tudo - é uma solução que funciona muito bem para mim, outras pessoas podem ter problemas com isso, mas corrige o buraco "por design".


2
Não fui eu quem recusou o voto, mas direi que realmente não gosto dessa solução porque ela ainda separa os scripts específicos da exibição da própria exibição.
esmagamento

3
20 outras pessoas e eu tenho uma opinião diferente. Você ainda pode ter scripts diretamente relacionados a uma visualização que está em um arquivo separado. É um erro de programação se você não incluir o script junto com a visualização. Tê-lo em um arquivo separado separa a interação da apresentação e permite uma abundância de outros benefícios por estar em um arquivo separado.
dan Richardson

1
Você está completamente certo. Na verdade, eu concordo completamente e prefiro esse método pessoalmente. O verdadeiro problema para mim é que meus colegas lutam com tanta separação. Esse é um problema de domínio, no entanto. Eu acho que esse método é ideal, especialmente quando você considera um processo de construção de JavaScript. Continuarei trabalhando para educar meus colegas a usar esse método e apoiá-lo totalmente. Eu acho que sua resposta poderia ser melhorada, no entanto. Você não precisou mencionar as "20 pessoas concordam". Só porque uma resposta é popular, nem sempre significa que está certa. Nesse caso, está certo.
esmagamento

É verdade, e eu estou sempre feliz em aceitar feedback construtivo e alterar o meu próprio código e resposta se houver uma melhoria a ser tido :)
Dan Richardson

1
Essa solução tem o benefício adicional de ainda poder fazer todo o material MVC-ish que você esperaria poder fazer em uma Visualização típica, como poder codificar um JSON passado no Model e gerar URLs usando URL. Açao. Essa abordagem é uma maneira elegante de configurar seus controladores AngularJS - cada vista parcial pode representar um controlador separado no módulo Angular. Tão limpo!
21416 Dan

40

Das soluções neste segmento , criei a seguinte solução provavelmente super complicada que permite atrasar a renderização de qualquer html (scripts também) dentro de um bloco usando

USO

Crie a "seção"

  1. Cenário típico: em uma vista parcial, inclua o bloco apenas uma vez, não importa quantas vezes a vista parcial seja repetida na página:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. Em uma exibição parcial, inclua o bloco para cada vez que a parcial for usada:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. Em uma exibição parcial, inclua o bloco apenas uma vez, não importa quantas vezes o parcial seja repetido, mas depois o processe especificamente pelo nome when-i-call-you:

    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        <b>show me once by name</b>
        <span>@Model.First().Value</span>
    }

Renderize as "seções"

(ou seja, exibir a seção atrasada na visualização principal)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`

CÓDIGO

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));
    }


}

1
Wow ainda é complicado para mim entender o código, mas um para vir acima com uma solução
Rameez Ahmed Sayad

@RameezAhmedSayad, você está certo - voltando aqui, mesmo que eu esteja confuso com a forma como eu quis dizer como usá-lo. Atualizando a resposta ...
drzaus

E para esclarecer ainda mais, a razão pela qual existem dois "nomes" é que, se você deseja que ele seja renderizado apenas uma vez que precisa da chave exclusiva no parâmetro isOnlyOne, mas somente se você deseja renderizá-lo em um local específico por nome, fornece o identificador, caso contrário, é descartado Html.RenderDelayed().
drzaus

Pessoalmente, acho que não haveria necessidade de comprar o problema e usar essa abordagem; a seção em visualizações parciais simplesmente não é necessária, pois pode ser eliminada, e os scripts podem ir para lá sem definir uma seção. É porque isso é renderizado externamente e se você vir o código da página renderizada, simplesmente notará que o código para a exibição parcial não está visível lá. Portanto, se esse é o problema de uma melhor organização, etc., isso não terá nenhum efeito.
Transcendente

@Transcendente o "debate" já foi iniciado nos comentários da resposta aceita stackoverflow.com/a/7556594/1037948
drzaus

16

Eu tive esse problema e usei essa técnica.

É a melhor solução que encontrei, que é muito flexível.

Também vote aqui para adicionar suporte à declaração de seção cumulativa


9

Se você tem uma necessidade legítima de executar alguns jsde a partial, veja como você pode fazê-lo jQuery:

<script type="text/javascript">        
    function scriptToExecute()
    {
        //The script you want to execute when page is ready.           
    }

    function runWhenReady()
    {
        if (window.$)
            scriptToExecute();                                   
        else
            setTimeout(runWhenReady, 100);
    }
    runWhenReady();
</script>

Eu tentei @drzaus, ele precisa do 'SeeIfReady' ou não funciona.
Cacho Santa

8

Seguindo o princípio discreto , não é necessário que "_myPartial" injete conteúdo diretamente na seção de scripts. Você pode adicionar esses scripts de exibição parcial em um .jsarquivo separado e referenciá-los na seção @scripts da exibição pai.


10
O que aconteceria se a exibição parcial não fosse renderizada na página? Ainda fazemos referência a esses arquivos .js no pai e o sobrecarregamos?
Murali Murugesan

5

Há uma falha fundamental na maneira como pensamos sobre a web, especialmente ao usar o MVC. A falha é que o JavaScript é de alguma forma a responsabilidade da visualização. Uma visão é uma visão, JavaScript (comportamental ou não) é JavaScript. No padrão MVVM do Silverlight e do WPF, somos confrontados com "visualizar primeiro" ou "modelo primeiro". No MVC, devemos sempre tentar argumentar do ponto de vista do modelo e o JavaScript faz parte desse modelo de várias maneiras.

Eu sugeriria o uso do padrão AMD (eu mesmo gosto do RequireJS ). Separe seu JavaScript em módulos, defina sua funcionalidade e conecte-se a seu html a partir do JavaScript, em vez de confiar em uma visualização para carregar o JavaScript. Isso limpará seu código, separará suas preocupações e facilitará a vida de uma só vez.


Por cerca de dois ou três meses, estou usando o RequireJS e acho que nunca mais desenvolverei outro aplicativo Web sem o RequireJS.
tugberk

6
JavaScript também pode ser da responsabilidade da View.
Kelmen

1
Usar o padrão AMD é uma boa idéia, mas não concordo com a sua afirmação de que o JavaScript faz parte do modelo. Muitas vezes, é necessário definir o comportamento do View, especialmente quando associado a algo como Knockout. Você despeja uma representação JSON do seu modelo na sua Visualização JavaScript. Pessoalmente, eu apenas uso closures, um "namespace" personalizado no windowobjeto e incluo scripts de biblioteca antes de quaisquer parciais.
esmagar

Eu acho que há um mal-entendido aqui. Ao desenvolver a maioria dos aplicativos da Web, na verdade estamos desenvolvendo dois aplicativos: um executado no servidor e outro executado no cliente. Da perspectiva do servidor, tudo o que você envia para o navegador é a "visualização". Nesse sentido, o JavaScript faz parte da exibição. Da perspectiva do aplicativo cliente, HTML puro é visualizado e JS é um código paralelo aos M e C nos termos do MVC do servidor. Eu acho que é por isso que as pessoas estão discordando aqui.
TheAgent 9/09/19

3

O objetivo do OP é que ele queira definir scripts embutidos em sua Vista Parcial, que eu assumo que esse script seja específico apenas para essa Vista Parcial e tenha esse bloco incluído na seção de scripts.

Entendo que ele deseja que essa Vista Parcial seja independente. A ideia é semelhante aos componentes ao usar Angular.

Minha maneira seria apenas manter os scripts dentro da Vista Parcial como está. Agora, o problema disso é quando se chama Partial View, ele pode executar o script lá antes de todos os outros scripts (que geralmente são adicionados à parte inferior da página de layout). Nesse caso, você apenas tem o script Partial View aguardando os outros scripts. Existem várias maneiras de fazer isso. O mais simples, que eu já usei antes, está usando um evento body.

No meu layout, eu teria algo no fundo assim:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
  (function(){
    document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
  })();
</script>

Então, na minha Vista Parcial (na parte inferior):

<script>
  (function(){
    document.querySelector('body').addEventListener('scriptsLoaded', function() {

      // .. do your thing here

    });
  })();
</script>

Outra solução é usar uma pilha para enviar todos os seus scripts e chamar cada um no final. Outra solução, como já mencionado, é o padrão RequireJS / AMD, que também funciona muito bem.


2

A primeira solução em que consigo pensar é usar o ViewBag para armazenar os valores que devem ser renderizados.

Inicialmente, eu nunca tentei se este trabalho de uma visão parcial, mas deveria imo.


Apenas tentei; infelizmente que não trabalho (criado um ViewBag.RenderScripts = new List<string>();no topo da página principal, então chamado @Html.Partial("_CreateUpdatePartial",Model,ViewData), em seguida, colocar @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}em vista parcial Coloquei. @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan

2

Você não precisa usar seções em vista parcial.

Incluir em sua vista parcial. Ele executa a função após o jQuery carregado. Você pode alterar a cláusula de condição para seu código.

<script type="text/javascript">    
var time = setInterval(function () {
    if (window.jQuery != undefined) {
        window.clearInterval(time);

        //Begin
        $(document).ready(function () {
           //....
        });
        //End
    };
}, 10); </script>

Julio Spader


2

Você pode usar estes métodos de extensão : (Salvar como PartialWithScript.cs)

namespace System.Web.Mvc.Html
{
    public static class PartialWithScript
    {
        public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
        {
            if (htmlHelper.ViewBag.ScriptPartials == null)
            {
                htmlHelper.ViewBag.ScriptPartials = new List<string>();
            }

            if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
            {
                htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
            }

            htmlHelper.ViewBag.ScriptPartialHtml = true;
            htmlHelper.RenderPartial(partialViewName);
        }

        public static void RenderPartialScripts(this HtmlHelper htmlHelper)
        {
            if (htmlHelper.ViewBag.ScriptPartials != null)
            {
                htmlHelper.ViewBag.ScriptPartialHtml = false;
                foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
                {
                    htmlHelper.RenderPartial(partial);
                }
            }
        }
    }
}

Use assim:

Exemplo parcial: (_MyPartial.cshtml) Coloque o html no if e os js no else.

@if (ViewBag.ScriptPartialHtml ?? true)
    <p>I has htmls</p>
}
else {
    <script type="text/javascript">
        alert('I has javascripts');
    </script>
}

No seu _Layout.cshtml, ou onde você quiser que os scripts das parciais sejam renderizados, coloque o seguinte (uma vez): Ele renderizará apenas o javascript de todas as parciais na página atual neste local.

@{ Html.RenderPartialScripts(); }

Para usar o seu parcial, basta fazer o seguinte: Ele renderizará apenas o html neste local.

@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}

1

Existe uma maneira de inserir seções em vistas parciais, embora não seja bonito. Você precisa ter acesso a duas variáveis ​​na tela pai. Como parte do objetivo de sua visão parcial é criar essa seção, faz sentido exigir essas variáveis.

Veja como é inserir uma seção na vista parcial:

@model KeyValuePair<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}

E na página inserindo a vista parcial ...

@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))

Você também pode usar esta técnica para definir o conteúdo de uma seção programaticamente em qualquer classe.

Aproveitar!


1
Você pode por favor e um link para um projeto totalmente funcional?
Ehsan Zargar Ershadi

1

A idéia de Plutão de uma maneira mais agradável:

CustomWebViewPage.cs:

    public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {

    public IHtmlString PartialWithScripts(string partialViewName, object model) {
        return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
    }

    public void RenderScriptsInBasePage(HelperResult scripts) {
        var parentView = ViewBag.view as WebPageBase;
        var parentHtml = ViewBag.html as HtmlHelper;
        parentView.DefineSection("scripts", () => {
            parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
        });
    }
}

Views \ web.config:

<pages pageBaseType="Web.Helpers.CustomWebViewPage">

Visão:

@PartialWithScripts("_BackendSearchForm")

Parcial (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

@helper scripts() {
<script>
    //code will be rendered in a "scripts" section of the Layout page
</script>
}

Página de layout:

@RenderSection("scripts", required: false)

1

Isso funcionou para mim, permitindo-me co-localizar javascript e html para exibição parcial no mesmo arquivo. Ajuda no processo de pensamento para ver html e parte relacionada no mesmo arquivo de exibição parcial.


No modo de exibição que usa o modo de exibição parcial chamado "_MyPartialView.cshtml"

<div>
    @Html.Partial("_MyPartialView",< model for partial view>,
            new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>

@section scripts{

    @Html.Partial("_MyPartialView",<model for partial view>, 
                  new ViewDataDictionary { { "Region", "ScriptSection" } })

 }

No arquivo Vista parcial

@model SomeType

@{
    var region = ViewData["Region"] as string;
}

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}

0

Eu resolvi essa rota completamente diferente (porque estava com pressa e não queria implementar um novo HtmlHelper):

Embrulhei minha Vista Parcial em uma grande declaração if-else:

@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}

Em seguida, chamei o Partial duas vezes com um ViewData personalizado:

@Html.Partial("MyPartialView", Model, 
    new ViewDataDictionary { { "ShouldRenderScripts", false } })

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}

Certamente, a idéia toda é que o consumidor da visão parcial não precise saber que precisa incluir scripts, isso é meio que o problema? Caso contrário, você pode muito-bem apenas dizer @Html.Partial("MyPartialViewScripts")
Dan Richardson

Não, a idéia é permitir que os scripts sejam definidos no mesmo documento que o html, mas concordo que isso não é o ideal.
Rick Love

0

Eu tive um problema semelhante, onde eu tinha uma página mestra da seguinte maneira:

@section Scripts {
<script>
    $(document).ready(function () {
        ...
    });
</script>
}

...

@Html.Partial("_Charts", Model)

mas a exibição parcial dependia de algum JavaScript na seção Scripts. Eu o resolvi codificando a exibição parcial como JSON, carregando-a em uma variável JavaScript e usando-a para preencher uma div, portanto:

@{
    var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}

@section Scripts {
<script>
    $(document).ready(function () {
        ...
        var partial = @partial;
        $('#partial').html(partial.html);
    });
</script>
}

<div id="partial"></div>

Na IMO, você deveria ter resolvido isso movendo seu JS para um arquivo separado.
precisa saber é o seguinte

0

Como opção, você pode usar uma pasta / index.cshtml como uma página principal e adicionar scripts de seção. Então, no seu layout, você tem:

@RenderSection("scripts", required: false) 

e seu index.cshtml:

@section scripts{
     @Scripts.Render("~/Scripts/file.js")
}

e funcionará em todas as suas visualizações parciais. Funciona para mim


0

Usando o Mvc Core, você pode criar um TagHelper organizado, scriptscomo mostrado abaixo. Isso pode ser facilmente transformado em uma sectiontag na qual você também atribui um nome (ou o nome é retirado do tipo derivado). Observe que a injeção de dependência precisa ser configurada para IHttpContextAccessor.

Ao adicionar scripts (por exemplo, em parcial)

<scripts>
    <script type="text/javascript">
        //anything here
    </script>
</scripts>

Ao emitir os scripts (por exemplo, em um arquivo de layout)

<scripts render="true"></scripts>

Código

public class ScriptsTagHelper : TagHelper
    {
        private static readonly object ITEMSKEY = new Object();

        private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;

        private IHttpContextAccessor _httpContextAccessor;

        public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var attribute = (TagHelperAttribute)null;
            context.AllAttributes.TryGetAttribute("render",out attribute);

            var render = false;

            if(attribute != null)
            {
                render = Convert.ToBoolean(attribute.Value.ToString());
            }

            if (render)
            {
                if (_items.ContainsKey(ITEMSKEY))
                {
                    var scripts = _items[ITEMSKEY] as List<HtmlString>;

                    var content = String.Concat(scripts);

                    output.Content.SetHtmlContent(content);
                }
            }
            else
            {
                List<HtmlString> list = null;

                if (!_items.ContainsKey(ITEMSKEY))
                {
                    list = new List<HtmlString>();
                    _items[ITEMSKEY] = list;
                }

                list = _items[ITEMSKEY] as List<HtmlString>;

                var content = await output.GetChildContentAsync();

                list.Add(new HtmlString(content.GetContent()));
            }
        }
    }

0

Eu tive um problema quase idêntico no outro dia, exceto que a exibição parcial era uma resposta a uma solicitação AJAX. Na minha situação, o parcial era na verdade uma página inteira, mas eu queria que ele fosse acessível como um parcial de outras páginas.

Se você deseja renderizar seções parcialmente, a solução mais limpa é criar um novo layout e usar uma variável ViewBag. Isso não funciona com @Html.Partial()o novo <partial></partial>, use o AJAX.

Vista principal (que você deseja que seja processada como parcial em outro lugar):

@if(ViewBag.Partial == true) {
    Layout = "_layoutPartial";
}

<div>
    [...]
</div>    

@section Scripts {
    <script type="text/javascript">
        [...]
    </script>
}

Controlador:

public IActionResult GetPartial() {

    ViewBag.Partial = true;

    //Do not return PartialView!
    return View("/path/to/view")
}

_layoutPartial.cshtml (novo):

@RenderSection("Scripts")
@RenderBody()

Em seguida, use o AJAX em sua página.

Se você deseja renderizar a página no layout principal (não parcial), não defina ViewBag.Partial = true. Não é necessário nenhum auxiliar de HTML.


-1

Bem, acho que os outros pôsteres forneceram a você um meio de incluir diretamente uma seção @ na sua parcial (usando ajudantes html de terceiros).

Mas eu acho que, se o seu script estiver fortemente acoplado ao seu parcial, basta colocar seu javascript diretamente dentro de uma <script>tag embutida dentro do seu parcial e terminar com ele (tenha cuidado com a duplicação de script se você pretende usar o parcial mais de uma vez em uma única visualização);


1
Isso geralmente não é ideal, porque o carregamento do jQuery etc. aconteceria após os scripts embutidos ... mas, para o código nativo, acho que está bem.
precisa saber é o seguinte

-3

suponha que você tenha uma visualização parcial chamada _contact.cshtml, seu contato pode ser um assunto legal (nome) ou físico (nome e sobrenome). sua visão deve cuidar do que é renderizado e que pode ser obtido com javascript. pode ser necessário renderização atrasada e JS dentro da visualização.

a única maneira que penso, como isso pode ser omitido, é quando criamos uma maneira discreta de lidar com essas preocupações da interface do usuário.

observe também que o MVC 6 terá o chamado View Component, mesmo os futuros do MVC tinham coisas semelhantes e a Telerik também suporta isso ...


1
3 anos atrasado, e eu acho que isso nem responde à pergunta? O que você está tentando dizer aqui? Respondendo a uma pergunta de 3 anos mais tarde, com características especulativas de futuras tecnologias não é realmente uma resposta ou particularmente útil
Dan Richardson

-3

Acabei de adicionar esse código na minha visão parcial e resolvi o problema, embora não muito limpo, ele funciona. Você precisa se certificar dos IDs dos objetos que está renderizando.

<script>
    $(document).ready(function () {
        $("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
    });
</script>

-5

Eu tive o problema semelhante resolvido com isso:

@section ***{
@RenderSection("****", required: false)
}

Essa é uma maneira bonita de injetar eu acho.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.