File: ControllerActionEndpointDatasourceBenchmark.cs
Web Access
Project: src\src\Mvc\perf\Microbenchmarks\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.Microbenchmarks.csproj (Microsoft.AspNetCore.Mvc.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.Reflection;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
 
namespace Microsoft.AspNetCore.Mvc.Microbenchmarks;
 
public class ControllerActionEndpointDataSourceBenchmark
{
    private const string DefaultRoute = "{Controller=Home}/{Action=Index}/{id?}";
 
    // Attribute routes can't have controller and action as parameters, so we edit the
    // route template in the test to make it more realistic.
    private const string ControllerReplacementToken = "{Controller=Home}";
    private const string ActionReplacementToken = "{Action=Index}";
 
    private MockActionDescriptorCollectionProvider _conventionalActionProvider;
    private MockActionDescriptorCollectionProvider _attributeActionProvider;
    private List<(string routeName, string pattern)> _routes;
 
    [Params(1, 100, 1000)]
    public int ActionCount;
 
    [GlobalSetup]
    public void Setup()
    {
        _conventionalActionProvider = new MockActionDescriptorCollectionProvider(
            Enumerable.Range(0, ActionCount).Select(i => CreateConventionalRoutedAction(i)).ToList<ActionDescriptor>()
            );
 
        _attributeActionProvider = new MockActionDescriptorCollectionProvider(
            Enumerable.Range(0, ActionCount).Select(i => CreateAttributeRoutedAction(i)).ToList<ActionDescriptor>()
            );
 
        _routes = new List<(string routeName, string pattern)>
            {
                ("Default", DefaultRoute)
            };
    }
 
    [Benchmark]
    public void AttributeRouteEndpoints()
    {
        var endpointDataSource = CreateDataSource(_attributeActionProvider);
        var endpoints = endpointDataSource.Endpoints;
 
        AssertHasEndpoints(endpoints);
    }
 
    [Benchmark]
    public void ConventionalEndpoints()
    {
        var dataSource = CreateDataSource(_conventionalActionProvider);
        for (var i = 0; i < _routes.Count; i++)
        {
            var (routeName, pattern) = _routes[i];
            dataSource.AddRoute(routeName, pattern, defaults: null, constraints: null, dataTokens: null);
        }
 
        var endpoints = dataSource.Endpoints;
        AssertHasEndpoints(endpoints);
    }
 
    private ControllerActionDescriptor CreateAttributeRoutedAction(int id)
    {
        var routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["Controller"] = "Controller" + id,
            ["Action"] = "Index"
        };
 
        var template = DefaultRoute
            .Replace(ControllerReplacementToken, routeValues["Controller"])
            .Replace(ActionReplacementToken, routeValues["Action"]);
 
        return new ControllerActionDescriptor
        {
            RouteValues = routeValues,
            DisplayName = "Action " + id,
            ControllerName = "Controller" + id,
            ActionName = "Index",
            ControllerTypeInfo = typeof(object).GetTypeInfo(),
            MethodInfo = typeof(object).GetMethod(nameof(object.ToString)),
            AttributeRouteInfo = new AttributeRouteInfo()
            {
                Template = template,
            }
        };
    }
 
    private ControllerActionDescriptor CreateConventionalRoutedAction(int id)
    {
        return new ControllerActionDescriptor
        {
            RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["Controller"] = "Controller" + id,
                ["Action"] = "Index"
            },
            DisplayName = "Action " + id,
            ControllerName = "Controller" + id,
            ActionName = "Index",
            ControllerTypeInfo = typeof(object).GetTypeInfo(),
            MethodInfo = typeof(object).GetMethod(nameof(object.ToString)),
        };
    }
 
    private ControllerActionEndpointDataSource CreateDataSource(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
    {
        var dataSource = new ControllerActionEndpointDataSource(
            new ControllerActionEndpointDataSourceIdProvider(),
            actionDescriptorCollectionProvider,
            new ActionEndpointFactory(new MockRoutePatternTransformer(), Enumerable.Empty<IRequestDelegateFactory>(), new MockServiceProvider()),
            new OrderedEndpointsSequenceProvider());
 
        return dataSource;
    }
 
    private sealed class MockServiceProvider : IServiceProvider
    {
        public object GetService(Type serviceType)
        {
            return null;
        }
    }
 
    private sealed class MockRoutePatternTransformer : RoutePatternTransformer
    {
        public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
        {
            return original;
        }
 
        public override RoutePattern SubstituteRequiredValues(RoutePattern original, RouteValueDictionary requiredValues)
        {
            return original;
        }
    }
 
    private sealed class MockActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
    {
        public MockActionDescriptorCollectionProvider(List<ActionDescriptor> actionDescriptors)
        {
            ActionDescriptors = new ActionDescriptorCollection(actionDescriptors, 0);
        }
 
        public ActionDescriptorCollection ActionDescriptors { get; }
    }
 
    private sealed class MockParameterPolicyFactory : ParameterPolicyFactory
    {
        public override IParameterPolicy Create(RoutePatternParameterPart parameter, string inlineText)
        {
            throw new NotImplementedException();
        }
 
        public override IParameterPolicy Create(RoutePatternParameterPart parameter, IParameterPolicy parameterPolicy)
        {
            throw new NotImplementedException();
        }
    }
 
    private static void AssertHasEndpoints(IReadOnlyList<Http.Endpoint> endpoints)
    {
        if (endpoints.Count == 0)
        {
            throw new InvalidOperationException("Expected endpoints from data source.");
        }
    }
}