public static string CalculateResourceName(
            string httpMethod,
            string routeTemplate,
            IDictionary <string, object> routeValues,
            IDictionary <string, object>?defaults,
            out string?areaName,
            out string?controllerName,
            out string?actionName,
            bool addSlashPrefix,
            bool expandRouteTemplates)
        {
            // We could calculate the actual maximum size required by looping through all the
            // route values provided, comparing the parameter sizes to the parameter values
            // (only action/controller/area unless expandRouteParameters = true,
            // but doesn't seem worth it
            var sb = StringBuilderCache.Acquire(StringBuilderCache.MaxBuilderSize);

            sb.Append(httpMethod)
            .Append(' ');

            if (addSlashPrefix)
            {
                sb.Append('/');
            }

            sb.Append(routeTemplate.ToLowerInvariant());

            areaName       = null;
            controllerName = null;
            actionName     = null;

            foreach (var kvp in routeValues)
            {
                if (string.Equals(kvp.Key, "action", StringComparison.OrdinalIgnoreCase) && kvp.Value is string action)
                {
                    actionName = action.ToLowerInvariant();
                    sb.Replace("{action}", actionName);
                }
                else if (string.Equals(kvp.Key, "controller", StringComparison.OrdinalIgnoreCase) && kvp.Value is string controller)
                {
                    controllerName = controller.ToLowerInvariant();
                    sb.Replace("{controller}", controllerName);
                }
                else if (string.Equals(kvp.Key, "area", StringComparison.OrdinalIgnoreCase) && kvp.Value is string area)
                {
                    areaName = area.ToLowerInvariant();
                    sb.Replace("{area}", areaName);
                }
                else if (expandRouteTemplates)
                {
                    var valueAsString = kvp.Value as string ?? kvp.Value?.ToString() ?? string.Empty;
                    if (UriHelpers.IsIdentifierSegment(valueAsString, 0, valueAsString.Length))
                    {
                        // We're replacing the key with itself, so that we strip out all the additional parameters etc
                        // We should probably be doing that for the non-expanded approach too, but we historically haven't
                        ReplaceValue(sb, kvp.Key, kvp.Key, ReplaceType.ValueOnly);
                    }
                    else
                    {
                        ReplaceValue(sb, kvp.Key, valueAsString, ReplaceType.ValueAndBraces);
                    }
                }
            }

            // Remove unused parameters from conventional route templates
            if (defaults is not null)
            {
                foreach (var kvp in defaults)
                {
                    if (routeValues.ContainsKey(kvp.Key))
                    {
                        continue;
                    }

                    ReplaceValue(sb, kvp.Key, null, replaceType: ReplaceType.ValueBracesAndLeadingSlash);
                }
            }

            return(StringBuilderCache.GetStringAndRelease(sb));
        }