Exemplo n.º 1
0
    public void AddConventionalLinkGenerationRoute(
        List <Endpoint> endpoints,
        HashSet <string> routeNames,
        HashSet <string> keys,
        ConventionalRouteEntry route,
        IReadOnlyList <Action <EndpointBuilder> > conventions)
    {
        if (endpoints == null)
        {
            throw new ArgumentNullException(nameof(endpoints));
        }

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

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

        var requiredValues = new RouteValueDictionary();

        foreach (var key in keys)
        {
            if (route.Pattern.GetParameter(key) != null)
            {
                // Parameter (allow any)
                requiredValues[key] = RoutePattern.RequiredValueAny;
            }
            else if (route.Pattern.Defaults.TryGetValue(key, out var value))
            {
                requiredValues[key] = value;
            }
            else
            {
                requiredValues[key] = null;
            }
        }

        // We have to do some massaging of the pattern to try and get the
        // required values to be correct.
        var pattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, requiredValues);

        if (pattern == null)
        {
            // We don't expect this to happen, but we want to know if it does because it will help diagnose the bug.
            throw new InvalidOperationException("Failed to create a conventional route for pattern: " + route.Pattern);
        }

        var builder = new RouteEndpointBuilder(context => Task.CompletedTask, pattern, route.Order)
        {
            DisplayName = "Route: " + route.Pattern.RawText,
            Metadata    =
            {
                new SuppressMatchingMetadata(),
            },
        };

        if (route.RouteName != null)
        {
            builder.Metadata.Add(new RouteNameMetadata(route.RouteName));
        }

        // See comments on the other usage of EndpointNameMetadata in this class.
        //
        // The set of cases for a conventional route are much simpler. We don't need to check
        // for Endpoint Name already exising here because there's no way to add an attribute to
        // a conventional route.
        if (route.RouteName != null && routeNames.Add(route.RouteName))
        {
            builder.Metadata.Add(new EndpointNameMetadata(route.RouteName));
        }

        for (var i = 0; i < conventions.Count; i++)
        {
            conventions[i](builder);
        }

        for (var i = 0; i < route.Conventions.Count; i++)
        {
            route.Conventions[i](builder);
        }

        endpoints.Add((RouteEndpoint)builder.Build());
    }
Exemplo n.º 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)
                {
                    // This kind of thing can happen when a route pattern uses a *reserved* route value such as `action`.
                    // See: https://github.com/aspnet/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 parmaeter 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());
            }
        }