This is a follow up question from this question that I asked previously.
How can I re-render the page or the partial (Ideally only the partial), when StatusReport
is updated?
Each time that the user selects a different status from the dropdown list, a new POST request is send, which then updated the variable StatusReport
at PartialIndexModel, I am trying to achieve so that when this happens, the partial _MyPartial
re-renders with the new data.
I am new to Razor Pages, but here is my code.
@page
@model Demo.Pages.PartialIndex
@{
ViewData["Title"] = "Home";
}
@Html.AntiForgeryToken()
<h1>@Model.StatusReport.Name</h1>
<section class="hero-style-2">
<div class="slider">
....
</div>
</section>
<partial name="_MyPartial" model="@Model.StatusReport" />
@section Scripts{
<script>
$("#sel-options").change(function () {
var value = $("#sel-options option:selected").val();
var data = {
"value": value
};
$.ajax({
type: 'Post',
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
url: '@Url.Page("PartialIndex","Ajax")',
data: data,
success: function (data) {
// Do something
},
error: function (err){
toast.error(err.message);
}
})
})
</script>
}
Model:
public class StatusReport
{
public string Name { get; set; }
public Report Report { get; set; }
}
Partial
@using Models.Entities;
@model StatusReport
@if (Model != null)
{
<div class="row">
<div class="col-md">
<label class="label-text" for="sel-options">Status</label>
</div>
<div class="col-md-2">
<select id="sel-options" class="form-control lable-text">
<option selected value="Do">Done</option>
<option value="Done">Done</option>
<option value="Canceled">Canceled</option>
</select>
</div>
</div>
<div>
// Render @Model.Report
</div>
}
PartialIndex.cs
namespace Demo.Pages;
public class PartialIndexModel : PageModel
{
[BindProperty]
public StatusReport StatusReport { get; set; } = new();
[BindProperty]
public string status { get; set; } = "Do";
private readonly IStatusService _statusService;
public PartialIndexModel (IStatusService statusService)
{
_statusService = statusService;
}
public async Task<IActionResult> OnGet()
{
StatusReport = await _statusService.GetItemsWithSatus(status);
return Page();
}
public async Task<IActionResult> OnPostAjax(string value)
{
StatusReport = await _statusService.GetItemsWithSatus(value);
// After StatusReport is updated from the request, I want to be able to re-render the Partial _MyPartial
return Page();
}
}
}
How can I re-render the page or the partial (Ideally only the partial), when StatusReport is updated?
There are several steps needed to achieve rendering partial view HTML only. One key step is creating and registering a view service that will generate a string that represents the HTML of the status report partial view.
This Stack Overflow post provides multiple answers on how to integrate a view service into your Razor project. I've used this specific answer. (I did not duplicate the view service code here in this post but the OnPostAsync
method in PartialIndex.cs
lists where to call the view service.)
I made a few adjustments to the code you posted to round trip data between the client browser and the server. Specifically note two things: The first is that the OnPostAsync
method for your Razor page accepts a JSON string that's converted to a JsonDocument
object from the namespace System.Text.Json
.
public async Task<IActionResult> OnPostAsync([FromBody] JsonDocument data) { }
The second is that OnPostAsync
does not return a Page()
but a string through Content(json)
:
// Create an anonymous object if you want to return
// more than HTML representing the status report
// partial view.
object result = new
{
StatusReport = StatusReport,
SelectedValue = value,
Html = html
};
string json = JsonSerializer.Serialize(result);
return Content(json);
I moved the row with the <select id="sel-options">
element to the main view (PartialIndex.cshtml
) from the partial view (_MyPartial.cshtml
). The partial view should only contain the HTML structure for the status report.
Also, in the main view page (PartialIndex.cshtml
), a div
is used to render the status report partial view when the JavaScript successfully fetches the partial view HTML string from the server.
<div id="report-container">
<span class="report-name">@Model.StatusReport?.Name</span>
<div id="report-content">
<partial name="_MyPartial" model="@Model.StatusReport" />
</div>
</div>
I replaced the use of jQuery's $.ajax
with the native, vanilla JavaScript Fetch API. It does the same thing but it's a preference as to which you want to use.
I recommend moving the contents in the <script>
tag in the main view page to a JavaScript file so that the JavaScript can be debugged. I left the code in the <script>
tag to reduce the number of changes from the code in your original post.
<script src="~/js/partial-index.js"></script>
PartialIndex.cshtml
@page
@model Demo.Pages.PartialIndexModel
@{
ViewData["Title"] = "Home";
}
@Html.AntiForgeryToken()
<h1>@Model.StatusReport.Name</h1>
<section class="hero-style-2">
<div class="slider">
....
</div>
</section>
<div class="row">
<div class="col-md">
<label class="label-text" for="sel-options">Status</label>
</div>
<div class="col-md-2">
<select id="sel-options" class="form-control lable-text">
<option selected value="Do">Done</option>
<option value="Done">Done</option>
<option value="Canceled">Canceled</option>
</select>
</div>
</div>
<div id="debug-only-sel-value"></div>
<div id="report-container">
<span class="report-name">@Model.StatusReport?.Name</span>
<div id="report-content">
<partial name="_MyPartial" model="@Model.StatusReport" />
</div>
</div>
@section Scripts{
<script>
$("#sel-options").change(function () {
var value = $("#sel-options option:selected").val();
var data = {
"value": value
};
const url = "/PartialIndex";
const csrfToken = $('input:hidden[name="__RequestVerificationToken"]').val();
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// POST request with body set to 'data' parameter
// converted to JSON format.
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': csrfToken
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(responseData => {
console.log('Success');
document.querySelector("#debug-only-sel-value").innerHTML = responseData.SelectedValue;
const reportElem = document.querySelector("#report-container");
reportElem.querySelector(".report-name").innerHTML = responseData.StatusReport.Name;
//reportElem.querySelector(".report-content").innerHTML = responseData.html;
})
.catch(error => {
console.error(error);
});
});
</script>
@*<script src="~/js/partial-index.js"></script>*@
}
PartialIndex.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Models.Entities;
using System.Text.Json;
using WebApplication1.Services;
namespace WebApplication1.Pages
{
public class PartialIndexModel : PageModel
{
[BindProperty]
public StatusReport StatusReport { get; set; } = new();
[BindProperty]
public string status { get; set; } = "Do";
private readonly IStatusService _statusService;
public PartialIndexModel(IStatusService statusService)
{
_statusService = statusService;
}
public async Task<IActionResult> OnGet()
{
StatusReport = await _statusService.GetItemsWithSatus(status);
return Page();
}
public async Task<IActionResult> OnPostAsync([FromBody] JsonDocument data)
{
// 'JsonElement' is the list of properties within an object.
// That is, all of the items within the curly brackets '{ }'.
JsonElement root = data.RootElement;
string value = root.GetProperty("value").ToString();
StatusReport = await _statusService.GetItemsWithSatus(value);
// Pass the 'StatusReport' object into the
// 'RenderViewToStringAsync' method in the view service.
// This will generate a string representing the
// partial view's HTML.
// stackoverflow.com/questions/40912375/return-view-as-string-in-net-core
// stackoverflow.com/a/69075473 ASP.NET 5
string reportPartialPath = "~/Pages/Shared/_MyPartial.cshtml";
string html = ""; // await _viewService.RenderViewToStringAsync(ControllerContext, reportPartialPath, ("Foo", "Bar"));
// After StatusReport is updated from the request,
// I want to be able to re-render the Partial _MyPartial.
// Create an anonymous object if you want to return
// more than HTML representing the status report
// partial view.
object result = new
{
StatusReport = StatusReport,
SelectedValue = value,
Html = html
};
string json = JsonSerializer.Serialize(result);
return Content(json);
}
}
}
~/Pages/Shared/_MyPartial.cshtml
@using Models.Entities;
@model StatusReport
@if (Model != null)
{
<span class="report-title">@Model.Name</span>
<span class="report-description">@Model.Report?.Description</span>
}