Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Razor

.NET Core 7 Razor pages: Updating Section of a Razor Page (Partial Update), Without Re-rendering the Whole Content, with Means of Ajax POST

0.00/5 (No votes)
27 Sep 2023CPOL2 min read 8.4K  
On the parent razor page, we add Partial View which will serve as the updatable container. On Ajax calls from the parent page, we update only Partial view content, not the whole page, thus creating a smooth user interaction experience.

Introduction

I've spent quiet some time researching on this topic and couldn't find any place with complete implementation. Even working under the ChatGPT assistance, it took me couple of days to have a final clean solution.

Background

Once again, this is for Razor pages webproject in Visual Studio 2022, but it could be applied to MVC as well as MVC has the same concept of Partial views.

How To

We are going to deal with just three parts of the Razor pages web project: Index.cshtml, Index.cshtml.cs and _SelectTable.cshtml partial view. This is a bare minimum UI without any fancy stuff, to show just the concept. Please refer to this folders structure to have an idea of files location (files of our interest are highlighted in yellow).

The Partial View (_SelectTable.cshtml)

chtml
@model TestPartialViewWithAjax.Pages.IndexModel
@{
}

<table id="selectTable">
    <thead>
        <tr>
            <th>Select Element</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        @if (Model.SelectedValues != null)
        {
            @for (var i = 0; i < Model.SelectedValues.Count; i++)
            {
                <tr>
                    <td>
                        <select asp-for="SelectedValues[i]">
                            @foreach (var item in Model.YourSelectList)
                            {
                                var selected = Model.SelectedValues[i] == item.Value ? 
                                               "selected" : "";
                                if (Model.SelectedValues[i] == item.Value)
                                {
                                    <option value="@item.Value" selected>
                                     @item.Text</option>
                                } else
                                {
                                    <option value="@item.Value">@item.Text</option>
                                }
                            }
                        </select>
                    </td>
                    <td>
                        <button type="button" class="deleteRowButton">Delete</button>
                    </td>
                </tr>
            }
        }
    </tbody>
</table>

In this page, we render table rows with select elements. Selected option of existing rows is preserved when a new row is added. Rows can also be deleted, deletion is done by means of pure JavaScript block in the parent cshtml page.

The Parent page (Index.cshtml)

HTML
@page
@using Newtonsoft.Json;
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">
       building Web apps with ASP.NET Core</a>.</p>
    <br /><br />
    <h3>List of Nominees</h3>
    <form method="post">
    <div id="selectTableContainer">
        <partial name="_SelectTable" model="Model" />
    </div>

    <button type="button" id="addRowButton">Add Row</button>
    <button type="submit">Submit</button>
    </form>
</div>

@section Scripts {
    <script>
        document.getElementById("addRowButton").addEventListener("click", function () {
            // Make an AJAX request to add a new row

            // Capture the user's current selections
            var selectedValues = [];
            $('select').each(function () {
                selectedValues.push($(this).val());
            });
            // Create a JavaScript object that combines both data
            var requestData = {
                YourSelectList: @Html.Raw
                (JsonConvert.SerializeObject(Model.YourSelectList)),
                SelectedValues: selectedValues
            };
            // Serialize the request data
            var serializedModel = JSON.stringify(requestData);
            
            $.ajax({
                url: '/?handler=AddRow',
                method: "POST",
                contentType: "application/json; charset=utf-8",
                headers: {
                    RequestVerificationToken:
                        $('input:hidden[name="__RequestVerificationToken"]').val()
                },
                data: JSON.stringify(serializedModel),
                success: function (result) {
                    $("#selectTableContainer").html(result);
                },
                error: function () {
                    alert("Failed to add a new row.");
                }
            });
        });

        // Handle row deletion
        $("#selectTableContainer").on("click", ".deleteRowButton", function () {
            var row = $(this).closest("tr");
            row.remove();
        });
    </script>
}

We have <partial name="_SelectTable" model="Model" /> partial view included in the <form> where we add new table rows. Partial view shares parent page's Model. DeleteRow method uses pure JavaScript and simply manipulates HTML DOM to remove fields from the form. AddRowButton click uses Ajax call to the page behind AddRow method. Note, that this post call doesn't re-render the page, it updates partial view portion only.

Submit button is of type "submit" and as such, it posts back and refreshes/redirects the whole page. It submits the array of selections of the whole table, so that the parent knows all user interactions from Partial view.

Here, you could add jquery datatable plugin to have a fancy table stuff like sorting, searching, numbering, drag-and-drop, etc.

The Parent Page Behind (Index.cshtml.cs)

C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;

namespace TestPartialViewWithAjax.Pages
{
    public class IndexModel : PageModel
    {
        private readonly ILogger<IndexModel> _logger;
        [BindProperty]
        public List<string> SelectedValues { get; set; }= new List<string>();
        public List<SelectListItem> YourSelectList { get; set; }
        public IndexModel(ILogger<IndexModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
            // Initialize the YourSelectList with your options
            YourSelectList = new List<SelectListItem>
            {
                new SelectListItem { Value = "Default", Text = "Default",  
                                     Disabled = false,Group = null,Selected = false },
                new SelectListItem { Value = "Option1", Text = "Option 1",  
                                     Disabled = false,Group = null,Selected = false },
                new SelectListItem { Value = "Option2", Text = "Option 2",  
                                     Disabled = false,Group = null,Selected = false },
                new SelectListItem { Value = "Option3", Text = "Option 3",  
                                     Disabled = false,Group = null,Selected = false }
            };
            SelectedValues.Add("Default");
        }

        public IActionResult OnPost()
        {
          // breakpoint here to check SelectedValues  binding
          return RedirectToPage("./Index");
        }

        public IActionResult OnPostAddRow([FromBody] string serializedModel)
        {
            // Deserialize the model
            var model = JsonConvert.DeserializeObject<IndexModel>(serializedModel);

            // Add a new item to the SelectedValues list
            model.SelectedValues ??= new List<string>();
            model.SelectedValues.Add("Default");

            // Return the partial view with the updated model
            return Partial("_SelectTable", model);
        }
    }
}

Here, OnPostAddRow method simply adds one new item to the list and returns Partial view updated HTML code back to DOM adding more fields to the main form.

Please note, to avoid confusion for some developers, when the OnPostAddRow method is called from Ajax the IndexModel class is initialized as new one, with all default/empty properties. There is no any binding happens here ([BindProperty] is ignored), that's why we provide serialized data from the parent to send it to the Partial view.

Conclusion

The code is pasted from the workable VS 2022 solution and should be ready for running/testing/reviewing/debugging. The only thing that might be needed for successful compiling is to add Newtonsoft.Json package (if VS 2022 won't do this automatically for you).

History

  • 26th September, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)