0

I am using the @RenderSection("scripts", required: false) on the _Layout.cshtml at bottom of the page after the script bundles. My View using this layout defined the scripts section (@section scripts{ //script block}). Every thing works fine when I render the view from controller action via full page call.

But when I render the same view's content inside the container using the $.ajax call done handler like ($('#container').html(result)) then the scripts section defined on the _Layout.cshtml page does not load and executed on the DOM. Note in this case _Layout is not loaded as the call was ajax and layout page was already on the DOM and I do not want to load layout page again to improve the page load time. I am only concerned to load the Child view's content.

Please find below setup

_Layout.cshtml

 <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    @Styles.Render("~/Content/css")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">                

            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Load Full", "LoadPartial", "Home")</li>
                    <li><a href="#" id="lnkMenu" data-url="@Url.Action("LoadPartial","Home")">Load via SPA</a></li>
                </ul>
            </div>
        </div>
    </div>
    <div id="container" class="container body-content">
        @RenderBody()
        <hr />
    </div>    
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    <script src="~/Scripts/Common.js"></script>
    @RenderSection("scripts", required: false)
</body>
</html>

Controller

public ViewResult LoadPartial()
    {       
        return View("_LoadPartial");
    }

_ViewStart.cshtml:

   @{
    Layout = Request.IsAjaxRequest() ? null : "~/Views/Shared/_Layout.cshtml";
    }

Partial View: _LoadPartial.cshtml

     <div class="jumbotron">
        <h2>Script loading demo</h2>
    </div>
    <p id="partial-id">Partial view content</p>
    @section scripts
        {
        <script>
                $(function () {
                    console.log("full");
                    $('#partial-id').css('color', 'blue');
                });
            </script>
        }

Java script code to load the view using ajax call

    $(function () {
    $('#lnkMenu').click(function () {
        var url = $(this).data("url");
        $.ajax({
            url: url,
            type: 'GET',
            cache: false
        }).done(function (result) {
            $('#container').html(result);
        });
    });
  });

Now when I load page using with Layout in 1st case then it works fine. But If i try to load the page using jquery ajax then script section does not work.

=========================Solution I tried===============================

To fix this I tried the following solution, i.e. On my view I load the script section based on condition that if request is Ajax then run script normally and if it's full page request with Layout then run the script section. With this solution my _LoadPartial.cshtml view look like as following

    <div class="jumbotron">
    <h2>Script loading demo</h2>
</div>
<p id="partial-id">Partial view content</p>
@{
    if (Request.IsAjaxRequest())
    {
        <script>
            $(function () {
                console.log("partial");     
                $('#partial-id').css('color', 'blue');
            });
        </script>
    }
    else
    {
        @section scripts
        {
        <script>
                $(function () {
                    console.log("full");
                    $('#partial-id').css('color', 'blue');
                });
            </script>
        }
    }
}

This solution works fine in both the cases, but Here the problem with the solution is this that it's violating the DRY principle as I have to write same script block in both if and else block.

Does someone has idea what is the best way to handle such scenarios?

Ashish Shukla
  • 1,239
  • 12
  • 23
  • 1
    Sections are not supported in partials. –  Sep 03 '18 at 09:52
  • 1
    You need to use `@section scripts` inside main view page. You can found the reason [here](https://stackoverflow.com/questions/17876538/asp-mvc-define-section-in-partial-view). – Tetsuya Yamamoto Sep 03 '18 at 09:56
  • @StephenMuecke, I am aware that sections are not supported in the partial view, But Here I am implementing SPA kind functionality in MVC, so when user click on the link from top navigation, then I only want to render page without layout. Thats the reason I intentionally kept the Layout null for ajax requests mentioned in my question. To implement same I am looking for the solution. – Ashish Shukla Sep 03 '18 at 10:37
  • @TetsuyaYamamoto, Initially I thought to keep the script section on the _Layout page, but here the problem is there of 100+ pages in my application using same _Layout. So if i with this then I end up putting all the code of 100+ pages on single Layout page. Here the main point is this I am trying to render all the application pages using the same approach to achieve the SPA behavior i.e. load the views without the layout as it's already loaded with very first request – Ashish Shukla Sep 03 '18 at 10:43

1 Answers1

0

$(function() {}); is just a short-hand for $(document).ready({});

$(document).ready will only be fired once after the initial document load is complete. If I'm understanding you correctly your flow is as follows:

  1. Load Page
  2. Click a link to load partial view
  3. Ajax call gets partial view markup from server and inserts into the DOM
  4. You expect $(function() {}); to run at this point

$(document).ready is going to be called between Steps 1 & 2. By the time you load in your partial view, it's already been called and will not be called again just because you're loading additional markup.

I would suggest changing the $(function ()) code into an actual function and calling it in the success section of your ajax.

Partial View: _LoadPartial.cshtml

@section scripts
    {
    <script>
            function changeTextColour() {
                console.log("full");
                $('#partial-id').css('color', 'blue');
            };
        </script>
    }

JavaScript

$(function () {
$('#lnkMenu').click(function () {
    var url = $(this).data("url");
    $.ajax({
        url: url,
        type: 'GET',
        cache: false
    }).done(function (result) {
        $('#container').html(result);
        changeTextColour();
    });
});
});

=========================================================

If you need a more generic solution, you can return your partial view markup as a string as part of a JSON object, in order to return multiple values. You would need the following method:

public static string RenderPartialToString(Controller controller, string partialViewName, object model, ViewDataDictionary viewData, TempDataDictionary tempData)
    {
        ViewEngineResult result = ViewEngines.Engines.FindPartialView(controller.ControllerContext, partialViewName);

        if (result.View != null)
        {
            controller.ViewData.Model = model;
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            {
                using (HtmlTextWriter output = new HtmlTextWriter(sw))
                {
                    ViewContext viewContext = new ViewContext(controller.ControllerContext, result.View, viewData, tempData, output);
                    result.View.Render(viewContext, output);
                }
            }

            return sb.ToString();
        }

        return String.Empty;
    }

Usage:

Action:

public ActionResult Action() 
{
     var viewModel = new WhateverTypeYourViewModelIs();
     string partialHtml = RenderPartialToString(this, "PartialViewLocation.cshtml", viewModel, ViewData, TempData);
     return Json(new { Html = partialHtml, Color = "Blue" });
}

Javascript:

$(function () {
$('#lnkMenu').click(function () {
    var url = $(this).data("url");
    $.ajax({
        url: url,
        type: 'GET',
        cache: false
    }).done(function (result) {
        $('#container').html(result.Html);
        changeTextColour(result.Color);
    });
});
});

You could then just change the colour in your action depending on which action you're calling. You'd then just register the function changeTextColor() on page load instead of in the partial view.

Hope some of that helps.

Jacob
  • 628
  • 5
  • 17
  • Hi Jacob, I already tried this, this solution works fine in any specific scenario or in a single case but this is not suited well for my application because in my case this javescript block is generic for all application. All the pages user open from the main menu handled by this ajax call. So I can not put script code of all the pages on the done event of the $.ajax call. Doing this it will loose the separation of concern principle, i mean each application page should be responsible for execution it's script on rendering. when you have 100 of pages and then this is not an appropriate. – Ashish Shukla Sep 04 '18 at 04:41