public void AddEndpoints(
        List <Endpoint> endpoints,
        HashSet <string> routeNames,
        ActionDescriptor action,
        IReadOnlyList <ConventionalRouteEntry> routes,
        IReadOnlyList <Action <EndpointBuilder> > conventions,
        bool createInertEndpoints)
    {
        if (endpoints == null)
        {
            throw new ArgumentNullException(nameof(endpoints));
        }

        if (routeNames == null)
        {
            throw new ArgumentNullException(nameof(routeNames));
        }

        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        if (routes == null)
        {
            throw new ArgumentNullException(nameof(routes));
        }

        if (conventions == null)
        {
            throw new ArgumentNullException(nameof(conventions));
        }

        if (createInertEndpoints)
        {
            var builder = new InertEndpointBuilder()
            {
                DisplayName     = action.DisplayName,
                RequestDelegate = _requestDelegate,
            };
            AddActionDataToBuilder(
                builder,
                routeNames,
                action,
                routeName: null,
                dataTokens: null,
                suppressLinkGeneration: false,
                suppressPathMatching: false,
                conventions,
                Array.Empty <Action <EndpointBuilder> >());
            endpoints.Add(builder.Build());
        }

        if (action.AttributeRouteInfo?.Template == null)
        {
            // Check each of the conventional patterns to see if the action would be reachable.
            // If the action and pattern are compatible then create an endpoint with action
            // route values on the pattern.
            foreach (var route in routes)
            {
                // A route is applicable if:
                // 1. It has a parameter (or default value) for 'required' non-null route value
                // 2. It does not have a parameter (or default value) for 'required' null route value
                var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues);
                if (updatedRoutePattern == null)
                {
                    continue;
                }

                var requestDelegate = CreateRequestDelegate(action, route.DataTokens) ?? _requestDelegate;

                // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route
                // to handle link generation.
                var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, route.Order)
                {
                    DisplayName = action.DisplayName,
                };
                AddActionDataToBuilder(
                    builder,
                    routeNames,
                    action,
                    route.RouteName,
                    route.DataTokens,
                    suppressLinkGeneration: true,
                    suppressPathMatching: false,
                    conventions,
                    route.Conventions);
                endpoints.Add(builder.Build());
            }
        }
        else
        {
            var requestDelegate       = CreateRequestDelegate(action) ?? _requestDelegate;
            var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);

            // Modify the route and required values to ensure required values can be successfully subsituted.
            // Subsitituting required values into an attribute route pattern should always succeed.
            var(resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern);

            var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues);
            if (updatedRoutePattern == null)
            {
                // This kind of thing can happen when a route pattern uses a *reserved* route value such as `action`.
                // See: https://github.com/dotnet/aspnetcore/issues/14789
                var formattedRouteKeys = string.Join(", ", resolvedRouteValues.Keys.Select(k => $"'{k}'"));
                throw new InvalidOperationException(
                          $"Failed to update the route pattern '{resolvedRoutePattern.RawText}' with required route values. " +
                          $"This can occur when the route pattern contains parameters with reserved names such as: {formattedRouteKeys} " +
                          $"and also uses route constraints such as '{{action:int}}'. " +
                          "To fix this error, choose a different parameter name.");
            }

            var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, action.AttributeRouteInfo.Order)
            {
                DisplayName = action.DisplayName,
            };
            AddActionDataToBuilder(
                builder,
                routeNames,
                action,
                action.AttributeRouteInfo.Name,
                dataTokens: null,
                action.AttributeRouteInfo.SuppressLinkGeneration,
                action.AttributeRouteInfo.SuppressPathMatching,
                conventions,
                perRouteConventions: Array.Empty <Action <EndpointBuilder> >());
            endpoints.Add(builder.Build());
        }
    }
Beispiel #2
0
        public void AddEndpoints(
            List <Endpoint> endpoints,
            HashSet <string> routeNames,
            ActionDescriptor action,
            IReadOnlyList <ConventionalRouteEntry> routes,
            IReadOnlyList <Action <EndpointBuilder> > conventions,
            bool createInertEndpoints)
        {
            if (endpoints == null)
            {
                throw new ArgumentNullException(nameof(endpoints));
            }

            if (routeNames == null)
            {
                throw new ArgumentNullException(nameof(routeNames));
            }

            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            if (routes == null)
            {
                throw new ArgumentNullException(nameof(routes));
            }

            if (conventions == null)
            {
                throw new ArgumentNullException(nameof(conventions));
            }

            if (createInertEndpoints)
            {
                var builder = new InertEndpointBuilder()
                {
                    DisplayName     = action.DisplayName,
                    RequestDelegate = _requestDelegate,
                };
                AddActionDataToBuilder(
                    builder,
                    routeNames,
                    action,
                    routeName: null,
                    dataTokens: null,
                    suppressLinkGeneration: false,
                    suppressPathMatching: false,
                    conventions,
                    Array.Empty <Action <EndpointBuilder> >());
                endpoints.Add(builder.Build());
            }

            if (action.AttributeRouteInfo == null)
            {
                // Check each of the conventional patterns to see if the action would be reachable.
                // If the action and pattern are compatible then create an endpoint with action
                // route values on the pattern.
                foreach (var route in routes)
                {
                    // A route is applicable if:
                    // 1. It has a parameter (or default value) for 'required' non-null route value
                    // 2. It does not have a parameter (or default value) for 'required' null route value
                    var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues);
                    if (updatedRoutePattern == null)
                    {
                        continue;
                    }

                    // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route
                    // to handle link generation.
                    var builder = new RouteEndpointBuilder(_requestDelegate, updatedRoutePattern, route.Order)
                    {
                        DisplayName = action.DisplayName,
                    };
                    AddActionDataToBuilder(
                        builder,
                        routeNames,
                        action,
                        route.RouteName,
                        route.DataTokens,
                        suppressLinkGeneration: true,
                        suppressPathMatching: false,
                        conventions,
                        route.Conventions);
                    endpoints.Add(builder.Build());
                }
            }
            else
            {
                var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);

                // Modify the route and required values to ensure required values can be successfully subsituted.
                // Subsitituting required values into an attribute route pattern should always succeed.
                var(resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern);

                var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues);
                if (updatedRoutePattern == null)
                {
                    throw new InvalidOperationException("Failed to update route pattern with required values.");
                }

                var builder = new RouteEndpointBuilder(_requestDelegate, updatedRoutePattern, action.AttributeRouteInfo.Order)
                {
                    DisplayName = action.DisplayName,
                };
                AddActionDataToBuilder(
                    builder,
                    routeNames,
                    action,
                    action.AttributeRouteInfo.Name,
                    dataTokens: null,
                    action.AttributeRouteInfo.SuppressLinkGeneration,
                    action.AttributeRouteInfo.SuppressPathMatching,
                    conventions,
                    perRouteConventions: Array.Empty <Action <EndpointBuilder> >());
                endpoints.Add(builder.Build());
            }
        }