You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.Net中实现支持多标签页的心跳控件?

Hey there, let's walk through building this multi-tab-compatible session timeout warning control for ASP.NET step by step. I've tackled similar scenarios before, so here's a practical, battle-tested approach:

ASP.NET Multi-Tab Session Timeout Warning Control Implementation

1. Server-Side Configuration

First, we need to align the ASP.NET session and authentication timeouts to match your requirements (27-minute inactivity → warning, 3-minute countdown → expiration, total 30 minutes):

  • Update your web.config (for ASP.NET Framework) to set the session timeout:
<sessionState mode="InProc" timeout="30" />
  • If using Forms Authentication, sync its timeout and enable sliding expiration (this resets the timeout on every user interaction):
<authentication mode="Forms">
  <forms loginUrl="~/Login.aspx" timeout="30" slidingExpiration="true" />
</authentication>

Pro tip: slidingExpiration="true" is critical here—it ensures any valid request (including our heartbeat calls) resets the session clock.

For ASP.NET Core, configure session in Program.cs:

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

2. Client-Side Core Logic (Multi-Tab Friendly)

The biggest challenge with multi-tab apps is avoiding duplicate warnings or conflicting timers. We'll use localStorage to share state across all tabs:

  • Track the last heartbeat timestamp to calculate global inactivity time
  • Track whether the warning modal is already active to prevent duplicates

Create a reusable SessionTimeout.js file with these foundational variables and sync logic:

// Configuration
const TOTAL_SESSION_MINUTES = 30;
const WARNING_TRIGGER_MINUTES = 27;
const COUNTDOWN_SECONDS = 180; // 3 minutes

// Cross-tab shared state (stored in localStorage)
let sessionState = {
    lastHeartbeat: localStorage.getItem('lastHeartbeat') || Date.now(),
    warningActive: localStorage.getItem('warningActive') === 'true' || false,
    countdownTimer: null
};

// Sync state when other tabs update localStorage
window.addEventListener('storage', (e) => {
    if (e.key === 'lastHeartbeat') {
        sessionState.lastHeartbeat = parseInt(e.newValue);
    } else if (e.key === 'warningActive') {
        sessionState.warningActive = e.newValue === 'true';
        if (!sessionState.warningActive && sessionState.countdownTimer) {
            clearInterval(sessionState.countdownTimer);
            $('#sessionTimeoutModal').modal('hide');
        }
    }
});

3. Heartbeat & Session Check Logic

Next, implement functions to track user activity, ping the server, and check session status:

// Update heartbeat (call on any user interaction)
function updateHeartbeat() {
    const now = Date.now();
    sessionState.lastHeartbeat = now;
    localStorage.setItem('lastHeartbeat', now.toString());

    // If warning is active, extend session and close modal
    if (sessionState.warningActive) {
        pingServerForHeartbeat();
        sessionState.warningActive = false;
        localStorage.setItem('warningActive', 'false');
        clearInterval(sessionState.countdownTimer);
        $('#sessionTimeoutModal').modal('hide');
    }
}

// Check session status every 10 seconds
function checkSessionStatus() {
    const idleMinutes = (Date.now() - sessionState.lastHeartbeat) / (1000 * 60);

    // If user becomes active again, dismiss warning if shown
    if (idleMinutes < WARNING_TRIGGER_MINUTES && sessionState.warningActive) {
        sessionState.warningActive = false;
        localStorage.setItem('warningActive', 'false');
        clearInterval(sessionState.countdownTimer);
        $('#sessionTimeoutModal').modal('hide');
        return;
    }

    // Trigger warning if inactivity threshold is hit and no warning is active
    if (idleMinutes >= WARNING_TRIGGER_MINUTES && !sessionState.warningActive) {
        sessionState.warningActive = true;
        localStorage.setItem('warningActive', 'true');
        startCountdown();
        $('#sessionTimeoutModal').modal({ backdrop: 'static', keyboard: false });
    }
}

// Ping server to reset session timeout
function pingServerForHeartbeat() {
    fetch('/api/Session/Heartbeat', {
        method: 'POST',
        credentials: 'include' // Ensure cookies are sent to reset session
    })
    .then(res => {
        if (!res.ok) window.location.href = '/Login.aspx';
    })
    .catch(() => window.location.href = '/Login.aspx');
}

4. Modal UI & Countdown

Add the warning modal to your master page (or a shared layout component for ASP.NET Core) so it's available on all pages:

<!-- Session Timeout Warning Modal -->
<div id="sessionTimeoutModal" class="modal fade" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Session Expiring Soon</h5>
            </div>
            <div class="modal-body">
                <p>Your session will expire in <span id="countdownDisplay" class="fw-bold">3:00</span>. Choose an action:</p>
            </div>
            <div class="modal-footer">
                <button id="extendBtn" class="btn btn-primary">Extend Session</button>
                <button id="logoutBtn" class="btn btn-danger">Log Out</button>
            </div>
        </div>
    </div>
</div>

Add the countdown and modal event bindings:

// Start countdown timer
function startCountdown() {
    let remaining = COUNTDOWN_SECONDS;
    sessionState.countdownTimer = setInterval(() => {
        remaining--;
        const mins = Math.floor(remaining / 60);
        const secs = remaining % 60;
        $('#countdownDisplay').text(`${mins}:${secs.toString().padStart(2, '0')}`);

        if (remaining <= 0) {
            clearInterval(sessionState.countdownTimer);
            window.location.href = '/Login.aspx';
        }
    }, 1000);
}

// Bind modal button actions
function bindModalEvents() {
    $('#extendBtn').click(updateHeartbeat);
    
    $('#logoutBtn').click(() => {
        fetch('/Account/Logout', { method: 'POST', credentials: 'include' })
        .then(() => window.location.href = '/Login.aspx');
    });
}

5. Page Initialization Logic

Finally, wire everything up when the page loads, and listen for user activity to reset the heartbeat:

$(document).ready(() => {
    // Initialize modal events
    bindModalEvents();

    // Update heartbeat on any user interaction
    $(document).on('click keydown scroll', updateHeartbeat);

    // Start periodic session checks
    setInterval(checkSessionStatus, 10000);

    // Initial heartbeat to set the starting point
    updateHeartbeat();
});

6. Server-Side Heartbeat Endpoint

Create a simple endpoint to accept heartbeat requests (ASP.NET Framework MVC example):

public class SessionController : Controller
{
    [HttpPost]
    public JsonResult Heartbeat()
    {
        // ASP.NET automatically resets the session timeout on valid requests
        return Json(new { success = true });
    }
}

ASP.NET Core example:

[ApiController]
[Route("api/[controller]")]
public class SessionController : ControllerBase
{
    [HttpPost("Heartbeat")]
    public IActionResult Heartbeat()
    {
        // Reset session by accessing it (ASP.NET Core handles the rest)
        HttpContext.Session.SetString("LastHeartbeat", DateTime.UtcNow.ToString());
        return Ok(new { success = true });
    }
}

Key Notes

  • Multi-Tab Sync: Using localStorage and the storage event ensures all tabs stay in sync—no duplicate warnings or conflicting timers.
  • Modal Lockdown: The modal uses backdrop: static and keyboard: false to prevent users from accidentally dismissing it and losing their session.
  • Failure Handling: If the heartbeat request fails, we immediately redirect to the login page to avoid stale session states.

内容的提问来源于stack exchange,提问作者MPaul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:05:13