如何在ASP.NET Core Razor页面后端代码中调用前端JavaScript函数更新进度条及替代方案
First, let's clarify why your current approach won't work: ASP.NET Core Razor Pages uses HTTP, which is a stateless request-response protocol. When the OnGet method runs on the server, it generates the entire page and sends it to the client in one go. There's no way to invoke client-side JavaScript functions during the server-side execution because the connection closes once the response is sent.
Direct backend-to-frontend function calls aren't feasible here. Instead, we need a way to send progress updates from the server to the client while the long-running task executes. Below are two recommended solutions:
Solution 1: Use SignalR for Real-Time Updates
SignalR is an ASP.NET Core library that enables real-time bidirectional communication between server and client. This is ideal for progress tracking since the server can push updates to the client as soon as progress changes.
Step 1: Add SignalR to Your Project
Install the SignalR NuGet package via the CLI:
dotnet add package Microsoft.AspNetCore.SignalR
Step 2: Create a SignalR Hub
Add a new class (e.g., ProgressHub.cs) to your project:
using Microsoft.AspNetCore.SignalR; public class ProgressHub : Hub { // Send progress updates to connected clients public async Task SendProgressUpdate(int percent) { await Clients.Caller.SendAsync("ReceiveProgressUpdate", percent); } }
Step 3: Configure SignalR in Program.cs
Update your startup code to register and map the SignalR hub:
var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddRazorPages(); builder.Services.AddSignalR(); var app = builder.Build(); // Configure pipeline if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); app.MapHub<ProgressHub>("/progressHub"); // Map the hub endpoint app.Run();
Step 4: Modify Backend to Send Progress via SignalR
Update Index.cshtml.cs to inject the hub context and send progress updates:
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.Mvc.RazorPages; public class IndexModel : PageModel { private readonly IHubContext<ProgressHub> _hubContext; public IndexModel(IHubContext<ProgressHub> hubContext) { _hubContext = hubContext; } public async Task OnGet() { // Run the long task in the background (avoid async void!) _ = Task.Run(async () => await TestFunction()); } public async Task TestFunction() { int loopCount = 1; while (loopCount <= 100) { // Send progress to all connected clients await _hubContext.Clients.All.SendAsync("ReceiveProgressUpdate", loopCount); loopCount++; await Task.Delay(50); // Simulate work (remove in production) } } }
Step 5: Update Frontend to Listen for SignalR Updates
Modify Index.cshtml to connect to the hub and update the progress bar:
<style> #Progress_Status { width: 50%; background-color: #ddd; } #myprogressBar { width: 0%; height: 35px; background-color: #4CAF50; text-align: center; line-height: 32px; color: black; } </style> <div id="Progress_Status"> <div id="myprogressBar">0%</div> </div> <!-- Include SignalR client library --> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.10/signalr.min.js"></script> <script> // Connect to the SignalR hub const connection = new signalR.HubConnectionBuilder() .withUrl("/progressHub") .configureLogging(signalR.LogLevel.Information) .build(); // Listen for progress updates connection.on("ReceiveProgressUpdate", function(percent) { const progressBar = document.getElementById("myprogressBar"); progressBar.style.width = percent + '%'; progressBar.textContent = percent + '%'; }); // Start the connection async function start() { try { await connection.start(); console.log("SignalR Connected."); } catch (err) { console.log(err); setTimeout(start, 5000); } } // Reconnect if connection drops connection.onclose(async () => { await start(); }); // Initialize connection on page load start(); </script>
Solution 2: Use AJAX Polling (Simpler, Less Real-Time)
If you prefer to avoid adding SignalR, polling is a simpler alternative: the client periodically sends requests to the server to check current progress.
Step 1: Enable Session State
First, add session support in Program.cs:
builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(5); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // Add after UseRouting and before UseAuthorization app.UseSession();
Step 2: Modify Backend to Track Progress
Update Index.cshtml.cs to store progress in session and add a handler to retrieve it:
using Microsoft.AspNetCore.Mvc.RazorPages; public class IndexModel : PageModel { public async Task OnGet() { // Reset progress at task start HttpContext.Session.SetInt32("Progress", 0); // Start background task _ = Task.Run(async () => await TestFunction()); } public async Task TestFunction() { int loopCount = 1; while (loopCount <= 100) { // Update session with current progress HttpContext.Session.SetInt32("Progress", loopCount); loopCount++; await Task.Delay(50); // Simulate work } // Mark task as complete HttpContext.Session.SetInt32("Progress", 100); } // Handler to return current progress as JSON public IActionResult OnGetGetProgress() { var progress = HttpContext.Session.GetInt32("Progress") ?? 0; return new JsonResult(progress); } }
Step 3: Update Frontend to Poll for Progress
Modify Index.cshtml to periodically fetch progress and update the bar:
<style> #Progress_Status { width: 50%; background-color: #ddd; } #myprogressBar { width: 0%; height: 35px; background-color: #4CAF50; text-align: center; line-height: 32px; color: black; } </style> <div id="Progress_Status"> <div id="myprogressBar">0%</div> </div> <script> function updateProgress(percent) { const progressBar = document.getElementById("myprogressBar"); progressBar.style.width = percent + '%'; progressBar.textContent = percent + '%'; } function pollProgress() { fetch('/Index?handler=GetProgress') .then(response => response.json()) .then(percent => { updateProgress(percent); // Stop polling when task is complete if (percent < 100) { setTimeout(pollProgress, 100); // Poll every 100ms } }) .catch(error => console.error('Error fetching progress:', error)); } // Start polling on page load window.onload = pollProgress; </script>
Which Solution Should You Choose?
- SignalR: Best for real-time updates, low latency, and scenarios where you need instant feedback. Ideal for long-running tasks with frequent progress changes.
- Polling: Simpler to implement with no additional dependencies. Good for less frequent updates or when you want to avoid SignalR complexity.
Remember to handle edge cases like task cancellation, session expiration, and multi-user scenarios (session-based progress works per user, while SignalR can target specific clients if needed).
内容的提问来源于stack exchange,提问作者Chrono

