Keep Track of Your Users in an ASP.NET Core Application

cover

Tracking what users do in your app matters for security, debugging, and business insights. Doing it by hand usually means lots of boilerplate: managing request context, logging operations, tracking entity changes, and more. It adds complexity and makes mistakes more likely.

Why Applications Need Audit Logs

Audit logs are time-ordered records that show what happened in your app.

A good audit log should capture details for every web request, including:

1. Request and Response Details

  • Basic info like URL, HTTP method, browser, and HTTP status code
  • Network info like client IP address and user agent
  • Request parameters and response content when needed

2. Operations Performed

  • Controller actions and application service method calls with parameters
  • Execution time and duration for performance tracking
  • Call chains and dependencies where helpful

3. Entity Changes

  • Entity changes that happen during requests
  • Property-level changes, with old and new values
  • Change types (create, update, delete) and timestamps

4. Exception Information

  • Errors and exceptions during request execution
  • Exception stack traces and error context
  • Clear records of failed operations

5. Request Duration

  • Key metrics for measuring performance
  • Finding bottlenecks and optimization opportunities
  • Useful data for monitoring system health

The Challenge with Doing It by Hand

In ASP.NET Core, developers often use middleware or MVC filters for tracking. Here’s what that looks like and the common problems you’ll hit.

Using Middleware

Middleware are components in the ASP.NET Core pipeline that run during request processing.

Manual tracking typically requires:

  • Writing custom middleware to intercept HTTP requests
  • Extracting user info (user ID, username, IP address, and so on)
  • Recording request start time and execution duration
  • Handling both success and failure cases
  • Saving audit data to logs or a database

Tracking Inside Business Methods

In your business code, you also need to:

  • Log the start and end of important operations
  • Capture errors and related context
  • Link business operations to the request-level audit data
  • Make sure you track all critical actions

Problems with Manual Tracking

Manual tracking has some big downsides:

Code duplication and maintenance pain: Each controller ends up repeating similar tracking logic. Changing the rules means touching many places, and it’s easy to miss some.

Consistency and reliability issues: Different people implement tracking differently. Exception paths are easy to forget. It’s hard to ensure complete coverage.

Performance and scalability concerns: Homegrown tracking can slow the app if not designed well. Tuning and extending it takes effort.

Entity change tracking is especially hard. It often requires:

  • Recording original values before updates
  • Comparing old and new values for each property
  • Handling complex types, collections, and navigation properties
  • Designing and saving change records
  • Capturing data even when exceptions happen

This usually leads to:

  • A lot of code in every update method
  • Easy-to-miss edge cases and subtle bugs
  • High maintenance when entity models change
  • Extra queries and comparisons that can hurt performance
  • Incomplete coverage for complex scenarios

ABP Framework’s Built-in Solution

ABP Framework includes a built-in audit logging system. It solves the problems above and adds useful features on top.

Simple Setup vs. Manual Tracking

Instead of writing lots of code, you configure it once:

// Configure audit log options in the module's ConfigureServices method
Configure<AbpAuditingOptions>(options =>
{
    options.IsEnabled = true; // Enable audit log system (default value)
    options.IsEnabledForAnonymousUsers = true; // Track anonymous users (default value)
    options.IsEnabledForGetRequests = false; // Skip GET requests (default value)
    options.AlwaysLogOnException = true; // Always log on errors (default value)
    options.HideErrors = true; // Hide audit log errors (default value)
    options.EntityHistorySelectors.AddAllEntities(); // Track all entity changes
});
// Add middleware in the module's OnApplicationInitialization method
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var app = context.GetApplicationBuilder();
    
    // Add audit log middleware - one line of code solves all problems!
    app.UseAuditing();
}

By contrast, manual tracking needs middleware, controller logic, exception handling, and often hundreds of lines. With ABP, a couple of lines enable it and it just works.

What You Get with ABP

Here’s how ABP removes tracking code from your application and still captures what you need.

1. Application Services: No Tracking Code

Manual approach: You’d log inside each method and still risk missing cases.

ABP approach: Tracking is automatic—no tracking code in your methods.

public class BookAppService : ApplicationService
{
    private readonly IRepository<Book, Guid> _bookRepository;
    private readonly IRepository<Author, Guid> _authorRepository;
    
    [Authorize(BookPermissions.Create)]
    public virtual async Task<BookDto> CreateAsync(CreateBookDto input)
    {
        // No need to write any tracking code!
        // ABP automatically tracks:
        // - Method calls and parameters
        // - Calling user
        // - Execution duration
        // - Any exceptions thrown
        
        var author = await _authorRepository.GetAsync(input.AuthorId);
        var book = new Book(input.Title, author, input.Price);
        
        await _bookRepository.InsertAsync(book);
        
        return ObjectMapper.Map<Book, BookDto>(book);
    }
    
    [Authorize(BookPermissions.Update)]
    public virtual async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input)
    {
        var book = await _bookRepository.GetAsync(id);
        
        // No need to write any entity change tracking code!
        // ABP automatically tracks entity changes:
        // - Which properties changed
        // - Old and new values
        // - When the change happened
        
        book.ChangeTitle(input.Title);
        book.ChangePrice(input.Price);
        
        await _bookRepository.UpdateAsync(book);
        
        return ObjectMapper.Map<Book, BookDto>(book);
    }
}

With manual code, each method might need 20–30 lines for tracking. With ABP, it’s zero—and you still get richer data.

For entity changes, ABP also saves you from writing comparison code. It handles:

  • Property change detection
  • Recording old and new values
  • Complex types and collections
  • Navigation property changes
  • All with no extra code to maintain

2. Entity Change Tracking: One Line to Turn It On

Manual approach: You’d compare properties, serialize complex types, track collection changes, and write to storage.

ABP approach: Mark the entity or select entities globally.

// Enable audit log for specific entity - one line of code solves all problems!
[Audited]
public class MyEntity : Entity<Guid>
{
    public string Name { get; set; }
    public string Description { get; set; }
    
    [DisableAuditing] // Exclude sensitive data - security control
    public string InternalNotes { get; set; }
}
// Or global configuration - batch processing
Configure<AbpAuditingOptions>(options =>
{
    // Track all entities - one line of code tracks all entity changes
    options.EntityHistorySelectors.AddAllEntities();
    
    // Or use custom selector - precise control
    options.EntityHistorySelectors.Add(
        new NamedTypeSelector(
            "MySelectorName",
            type => typeof(IEntity).IsAssignableFrom(type)
        )
    );
});

3. Extension Features

Manual approach: Adding custom tracking usually spreads across many places and is hard to test.

ABP approach: Use a contributor for clean, centralized extensions.

public class MyAuditLogContributor : AuditLogContributor
{
    public override void PreContribute(AuditLogContributionContext context)
    {
        var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
        
        // Easily add custom properties - manual implementation needs lots of work
        context.AuditInfo.SetProperty(
            "MyCustomClaimValue",
            currentUser.FindClaimValue("MyCustomClaim")
        );
    }
    
    public override void PostContribute(AuditLogContributionContext context)
    {
        // Add custom comments - business logic integration
        context.AuditInfo.Comments.Add("Some comment...");
    }
}

// Register contributor - one line of code enables extension features
Configure<AbpAuditingOptions>(options =>
{
    options.Contributors.Add(new MyAuditLogContributor());
});

4. Precise Control

Manual approach: You end up with complex conditional logic.

ABP approach: Use attributes for simple, precise control.

// Disable audit log for specific controller - precise control
[DisableAuditing]
public class HomeController : AbpController
{
    // Health check endpoints won't be audited - avoid meaningless logs
}

// Disable for specific action - method-level control
public class HomeController : AbpController
{
    [DisableAuditing]
    public async Task<ActionResult> Home()
    {
        // This action won't be audited - public data access
    }
    
    public async Task<ActionResult> OtherActionLogged()
    {
        // This action will be audited - important business operation
    }
}

5. Visual Management of Audit Logs

ABP also provides a UI to browse and inspect audit logs:

Manual vs. ABP: A Quick Comparison

The benefits of ABP’s audit log system compared to doing it by hand:

Aspect Manual Implementation ABP Audit Logs
Setup Complexity High — Write middleware, services, repository code Low — A few lines of config, works out of the box
Code Maintenance High — Tracking code spread across the app Low — Centralized, convention-based
Consistency Variable — Depends on discipline Consistent — Automated and standardized
Performance Risky without careful tuning Built-in optimizations and scope control
Functionality Completeness Basic tracking only Comprehensive by default
Error Handling Easy to miss edge cases Automatic and reliable
Data Integrity Manual effort required Handled by the framework
Extensibility Custom work is costly Rich extension points
Development Efficiency Weeks to build Minutes to enable
Learning Cost Understand many details Convention-based, low effort

Why ABP Audit Logs Matter

ABP’s audit logging removes the boilerplate from user tracking in ASP.NET Core apps.

Core Idea

Manual tracking is error-prone and hard to maintain. ABP gives you a convention-based, automated system that works with minimal setup.

Key Benefits

ABP runs by convention, so you don’t need repetitive code. You can control behavior at the request, entity, and method levels. It automatically captures request details, operations, entity changes, and exceptions, and you can extend it with contributors when needed.

Results in Practice

Metric Manual Implementation ABP Implementation Improvement
Development Time Weeks Minutes 99%+
Lines of Code Hundreds of lines 2 lines of config 99%+
Maintenance Cost High Low Significant
Functionality Completeness Basic Comprehensive Significant
Error Rate Higher risk Lower risk Improved

Recommendation

If you need audit logs, start with ABP’s built-in system. It reduces effort, improves consistency, and stays flexible as your app grows. You can focus on your business logic and let the framework handle the infrastructure.

References