File: Benchmarks\AntiforgeryBenchmarks.cs
Web Access
Project: src\src\Antiforgery\benchmarks\Microsoft.AspNetCore.Antiforgery.Microbenchmarks\Microsoft.AspNetCore.Antiforgery.Microbenchmarks.csproj (Microsoft.AspNetCore.Antiforgery.Microbenchmarks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Claims;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
 
namespace Microsoft.AspNetCore.Antiforgery.Microbenchmarks.Benchmarks;
 
[AspNetCoreBenchmark]
public class AntiforgeryBenchmarks
{
    private IServiceProvider _serviceProvider = null!;
    private IAntiforgery _antiforgery = null!;
    private string _cookieName = null!;
    private string _formFieldName = null!;
 
    // Reusable contexts - reset between iterations instead of recreating
    private DefaultHttpContext _getAndStoreTokensContext = null!;
    private DefaultHttpContext _validateRequestContext = null!;
    private TestHttpResponseFeature _getAndStoreTokensResponseFeature = null!;
 
    // Pre-generated tokens for validation benchmark
    private string _cookieToken = null!;
    private string _requestToken = null!;
 
    // Pre-allocated form collection for validation benchmark
    private FormCollection _validationFormCollection = null!;
 
    [GlobalSetup]
    public void Setup()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddAntiforgery();
        serviceCollection.AddLogging();
        _serviceProvider = serviceCollection.BuildServiceProvider();
 
        _antiforgery = _serviceProvider.GetRequiredService<IAntiforgery>();
 
        // Get the actual cookie and form field names from options
        var options = _serviceProvider.GetRequiredService<IOptions<AntiforgeryOptions>>().Value;
        _cookieName = options.Cookie.Name!;
        _formFieldName = options.FormFieldName;
 
        // Create reusable context for GetAndStoreTokens
        _getAndStoreTokensResponseFeature = new TestHttpResponseFeature();
        _getAndStoreTokensContext = CreateHttpContext(_getAndStoreTokensResponseFeature);
 
        // Generate tokens for validation benchmark
        var tokenContext = CreateHttpContext(new TestHttpResponseFeature());
        var tokenSet = _antiforgery.GetAndStoreTokens(tokenContext);
        _cookieToken = tokenSet.CookieToken!;
        _requestToken = tokenSet.RequestToken!;
 
        // Pre-allocate form collection for validation
        _validationFormCollection = new FormCollection(new Dictionary<string, StringValues>
        {
            { _formFieldName, _requestToken }
        });
 
        // Create reusable context for ValidateRequestAsync
        _validateRequestContext = CreateHttpContextWithTokens();
    }
 
    [IterationSetup(Target = nameof(GetAndStoreTokens))]
    public void SetupGetAndStoreTokens()
    {
        // Reset the context instead of creating a new one
        ResetHttpContextForGetAndStoreTokens();
    }
 
    [IterationSetup(Target = nameof(ValidateRequestAsync))]
    public void SetupValidateRequest()
    {
        // Reset the context instead of creating a new one
        ResetHttpContextForValidation();
    }
 
    [Benchmark]
    public AntiforgeryTokenSet GetAndStoreTokens()
    {
        return _antiforgery.GetAndStoreTokens(_getAndStoreTokensContext);
    }
 
    [Benchmark]
    public Task ValidateRequestAsync()
    {
        return _antiforgery.ValidateRequestAsync(_validateRequestContext);
    }
 
    private DefaultHttpContext CreateHttpContext(TestHttpResponseFeature responseFeature)
    {
        var context = new DefaultHttpContext();
        context.RequestServices = _serviceProvider;
 
        // Create an authenticated identity with a Name claim (required by antiforgery)
        var identity = new ClaimsIdentity(
            [new Claim(ClaimsIdentity.DefaultNameClaimType, "testuser@example.com")],
            "TestAuth");
        context.User = new ClaimsPrincipal(identity);
 
        context.Request.Method = "POST";
        context.Request.ContentType = "application/x-www-form-urlencoded";
 
        // Setup response features to allow cookie writing
        context.Features.Set<IHttpResponseFeature>(responseFeature);
        context.Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
 
        return context;
    }
 
    private DefaultHttpContext CreateHttpContextWithTokens()
    {
        var context = new DefaultHttpContext();
        context.RequestServices = _serviceProvider;
 
        // Create an authenticated identity with a Name claim (required by antiforgery)
        var identity = new ClaimsIdentity(
            [new Claim(ClaimsIdentity.DefaultNameClaimType, "testuser@example.com")],
            "TestAuth");
        context.User = new ClaimsPrincipal(identity);
 
        context.Request.Method = "POST";
        context.Request.ContentType = "application/x-www-form-urlencoded";
 
        // Set the cookie token using the actual cookie name from options
        context.Request.Headers.Cookie = $"{_cookieName}={_cookieToken}";
 
        // Set the request token in form using the pre-allocated form collection
        context.Request.Form = _validationFormCollection;
 
        return context;
    }
 
    private void ResetHttpContextForGetAndStoreTokens()
    {
        // Clear the antiforgery feature so it generates fresh tokens
        _getAndStoreTokensContext.Features.Set<IAntiforgeryFeature>(null);
 
        // Reset response headers that antiforgery sets
        _getAndStoreTokensResponseFeature.Headers.Clear();
    }
 
    private void ResetHttpContextForValidation()
    {
        // Clear the antiforgery feature so it deserializes tokens fresh
        _validateRequestContext.Features.Set<IAntiforgeryFeature>(null);
    }
 
    private sealed class TestHttpResponseFeature : IHttpResponseFeature
    {
        public int StatusCode { get; set; } = 200;
        public string? ReasonPhrase { get; set; }
        public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
        public Stream Body { get; set; } = Stream.Null;
        public bool HasStarted => false;
 
        public void OnStarting(Func<object, Task> callback, object state) { }
        public void OnCompleted(Func<object, Task> callback, object state) { }
    }
}