ASP.NET Core MVC实现累计访客/访问量统计的最优方案
Great question! Since ASP.NET Core doesn't have Global.asax or the Session_Start event we relied on in traditional MVC, we need to use modern pipeline-based approaches. Let's walk through the best way to implement this, including how to reliably identify new visitors.
First: Configure Session Support
We'll use Session (or persistent cookies) to mark whether a visitor has already been counted, so we don't increment the count on every page load. Start by setting up Session in your Program.cs:
var builder = WebApplication.CreateBuilder(args); // Add Session with a reasonable timeout (adjust based on your needs) builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromHours(24); // Reset if user is inactive for 24h options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; // Ensures cookie works even if user declines non-essential cookies (for compliance) }); builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<YourDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); var app = builder.Build(); // Enable Session BEFORE routing and endpoints app.UseSession(); // Add our visitor counter middleware (we'll build this next) app.UseVisitorCounter(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
1. Implement the Visitor Counter with Custom Middleware (Recommended)
Middleware is the cleanest way to handle global logic like visitor counting—it runs on every request, so you don't have to repeat code in controllers.
Create a VisitorCounterMiddleware class:
public class VisitorCounterMiddleware { private readonly RequestDelegate _next; private readonly IServiceScopeFactory _scopeFactory; public VisitorCounterMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory) { _next = next; _scopeFactory = scopeFactory; } public async Task InvokeAsync(HttpContext context) { // Check if this visitor has already been counted via Session if (!context.Session.GetBoolean("IsVisitorCounted")) { // Create a scope to access our DbContext (since middleware is singleton) using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<YourDbContext>(); // Get or create the total visitors record (adjust your entity model as needed) var visitorSetting = await dbContext.Settings .FirstOrDefaultAsync(s => s.Key == "TotalVisitors"); if (visitorSetting == null) { // Initialize if no record exists dbContext.Settings.Add(new Setting { Key = "TotalVisitors", TotalValue = 1 // Use an int field instead of string for easier increments! }); } else { // Atomically increment the count to avoid concurrency issues visitorSetting.TotalValue += 1; } await dbContext.SaveChangesAsync(); // Mark this visitor as counted in their session context.Session.SetBoolean("IsVisitorCounted", true); } // Pass the request to the next middleware in the pipeline await _next(context); } } // Extension method to easily register the middleware public static class VisitorCounterMiddlewareExtensions { public static IApplicationBuilder UseVisitorCounter(this IApplicationBuilder builder) { return builder.UseMiddleware<VisitorCounterMiddleware>(); } }
2. How to Reliably Track "New" Visitors
The above uses Session (cookie-based) to track visitors, which works for most cases. Here are ways to refine this:
- Persistent Cookies for Longer Tracking: If you want to avoid counting the same user again within a set period (e.g., 30 days), replace Session with a persistent cookie:
// Replace the Session check with this if (!context.Request.Cookies.ContainsKey("VisitorCounted")) { // Count logic here... // Set a cookie that expires in 30 days context.Response.Cookies.Append("VisitorCounted", "true", new CookieOptions { Expires = DateTimeOffset.Now.AddDays(30), HttpOnly = true, IsEssential = true }); } - Combine with User Authentication: If your site has logged-in users, use their
UserIdto avoid counting them multiple times across sessions. For anonymous users, fall back to cookies/Session. - IP + UserAgent (Less Reliable): You could combine the user's IP address and browser UserAgent to identify unique visitors, but this can miscount (e.g., shared office IPs, users switching browsers). Use this only as a backup.
3. Critical Considerations
- Concurrency Safety: When multiple visitors hit your site at once, naive updates can cause race conditions. Use EF Core's
ExecuteUpdatefor atomic increments to fix this:// Replace the manual increment with this atomic operation await dbContext.Settings .Where(s => s.Key == "TotalVisitors") .ExecuteUpdateAsync(s => s.SetProperty(p => p.TotalValue, p.TotalValue + 1)); - Performance for High Traffic: If your site gets millions of visitors, hitting the database on every new visitor will slow things down. Use a distributed cache like Redis to increment counts in memory, then sync to the database periodically (e.g., nightly).
- Compliance: If you're targeting EU users, ensure your cookie usage complies with GDPR—make sure users can opt out if your counting cookie is considered non-essential.
内容的提问来源于stack exchange,提问作者TanvirArjel

