private static void ReplaceAttributeRouteTokens(
            ControllerActionDescriptor actionDescriptor,
            IList <string> routeTemplateErrors)
        {
            try
            {
                actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens(
                    actionDescriptor.AttributeRouteInfo.Template,
                    actionDescriptor.RouteValues);

                if (actionDescriptor.AttributeRouteInfo.Name != null)
                {
                    actionDescriptor.AttributeRouteInfo.Name = AttributeRouteModel.ReplaceTokens(
                        actionDescriptor.AttributeRouteInfo.Name,
                        actionDescriptor.RouteValues);
                }
            }
            catch (InvalidOperationException ex)
            {
                // Routing will throw an InvalidOperationException here if we can't parse/replace tokens
                // in the template.
                var message = Resources.FormatAttributeRoute_IndividualErrorMessage(
                    actionDescriptor.DisplayName,
                    Environment.NewLine,
                    ex.Message);

                routeTemplateErrors.Add(message);
            }
        }
Example #2
0
        internal void InferParameterBindingSources(ActionModel action)
        {
            for (var i = 0; i < action.Parameters.Count; i++)
            {
                var parameter     = action.Parameters[i];
                var bindingSource = parameter.BindingInfo?.BindingSource;
                if (bindingSource == null)
                {
                    bindingSource = InferBindingSourceForParameter(parameter);

                    parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
                    parameter.BindingInfo.BindingSource = bindingSource;
                }
            }

            var fromBodyParameters = action.Parameters.Where(p => p.BindingInfo.BindingSource == BindingSource.Body).ToList();

            if (fromBodyParameters.Count > 1)
            {
                var parameters = string.Join(Environment.NewLine, fromBodyParameters.Select(p => p.DisplayName));
                var message    = Resources.FormatApiController_MultipleBodyParametersFound(
                    action.DisplayName,
                    nameof(FromQueryAttribute),
                    nameof(FromRouteAttribute),
                    nameof(FromBodyAttribute));

                message += Environment.NewLine + parameters;
                throw new InvalidOperationException(message);
            }
        }
        private static string CreateAttributeRoutingAggregateErrorMessage(
            IEnumerable <string> individualErrors)
        {
            var errorMessages = AddErrorNumbers(individualErrors);

            var message = Resources.FormatAttributeRoute_AggregateErrorMessage(
                Environment.NewLine,
                string.Join(Environment.NewLine + Environment.NewLine, errorMessages));

            return(message);
        }
 private static IList <string> AddErrorNumbers(
     IEnumerable <string> namedRoutedErrors)
 {
     return(namedRoutedErrors
            .Select((error, i) =>
                    Resources.FormatAttributeRoute_AggregateErrorMessage_ErrorNumber(
                        i + 1,
                        Environment.NewLine,
                        error))
            .ToList());
 }
        public Task RouteAsync(RouteContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (Actions == null)
            {
                var message = Resources.FormatPropertyOfTypeCannotBeNull(
                    nameof(Actions),
                    nameof(MvcAttributeRouteHandler));
                throw new InvalidOperationException(message);
            }

            var actionDescriptor = _actionSelector.SelectBestCandidate(context, Actions);

            if (actionDescriptor == null)
            {
                _logger.NoActionsMatched(context.RouteData.Values);
                return(Task.CompletedTask);
            }

            foreach (var kvp in actionDescriptor.RouteValues)
            {
                if (!string.IsNullOrEmpty(kvp.Value))
                {
                    context.RouteData.Values[kvp.Key] = kvp.Value;
                }
            }

            context.Handler = (c) =>
            {
                var routeData = c.GetRouteData();

                var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
                if (_actionContextAccessor != null)
                {
                    _actionContextAccessor.ActionContext = actionContext;
                }

                var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
                if (invoker == null)
                {
                    throw new InvalidOperationException(
                              Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
                                  actionDescriptor.DisplayName));
                }

                return(invoker.InvokeAsync());
            };

            return(Task.CompletedTask);
        }
Example #6
0
        private static RouteInfo GetRouteInfo(
            Dictionary <string, RouteTemplate> templateCache,
            ActionDescriptor action)
        {
            var routeInfo = new RouteInfo()
            {
                ActionDescriptor = action,
            };

            try
            {
                var template = action.AttributeRouteInfo !.Template !;
                if (!templateCache.TryGetValue(template, out var parsedTemplate))
                {
                    // Parsing with throw if the template is invalid.
                    parsedTemplate = TemplateParser.Parse(template);
                    templateCache.Add(template, parsedTemplate);
                }

                routeInfo.RouteTemplate          = parsedTemplate;
                routeInfo.SuppressPathMatching   = action.AttributeRouteInfo.SuppressPathMatching;
                routeInfo.SuppressLinkGeneration = action.AttributeRouteInfo.SuppressLinkGeneration;
            }
            catch (Exception ex)
            {
                routeInfo.ErrorMessage = ex.Message;
                return(routeInfo);
            }

            foreach (var kvp in action.RouteValues)
            {
                foreach (var parameter in routeInfo.RouteTemplate.Parameters)
                {
                    if (string.Equals(kvp.Key, parameter.Name, StringComparison.OrdinalIgnoreCase))
                    {
                        routeInfo.ErrorMessage = Resources.FormatAttributeRoute_CannotContainParameter(
                            routeInfo.RouteTemplate.TemplateText,
                            kvp.Key,
                            kvp.Value);

                        return(routeInfo);
                    }
                }
            }

            routeInfo.Order     = action.AttributeRouteInfo.Order;
            routeInfo.RouteName = action.AttributeRouteInfo.Name;

            return(routeInfo);
        }
        private static string CreateMixedRoutedActionDescriptorsErrorMessage(
            ControllerActionDescriptor actionDescriptor,
            IDictionary <ActionModel, IList <ControllerActionDescriptor> > actionsForMethod)
        {
            // Text to show as the attribute route template for conventionally routed actions.
            var nullTemplate = Resources.AttributeRoute_NullTemplateRepresentation;

            var actionDescriptions = new List <string>();

            foreach (var action in actionsForMethod.SelectMany(kvp => kvp.Value))
            {
                var routeTemplate = action.AttributeRouteInfo?.Template ?? nullTemplate;

                var verbs = action.ActionConstraints?.OfType <HttpMethodActionConstraint>()
                            .FirstOrDefault()?.HttpMethods;

                var formattedVerbs = string.Empty;
                if (verbs != null)
                {
                    formattedVerbs = string.Join(", ", verbs.OrderBy(v => v, StringComparer.OrdinalIgnoreCase));
                }

                var description =
                    Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item(
                        action.DisplayName,
                        routeTemplate,
                        formattedVerbs);

                actionDescriptions.Add(description);
            }

            // Sample error message:
            //
            // A method 'MyApplication.CustomerController.Index' must not define attributed actions and
            // non attributed actions at the same time:
            // Action: 'MyApplication.CustomerController.Index' - Route Template: 'Products' - HTTP Verbs: 'PUT'
            // Action: 'MyApplication.CustomerController.Index' - Route Template: '(none)' - HTTP Verbs: 'POST'
            //
            // Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route,
            // or set a route template in all attributes that constrain HTTP verbs.
            return
                (Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod(
                     actionDescriptor.DisplayName,
                     Environment.NewLine,
                     string.Join(Environment.NewLine, actionDescriptions)));
        }
        private static IList <string> ValidateNamedAttributeRoutedActions(
            IDictionary <string,
                         IList <ActionDescriptor> > actionsGroupedByRouteName)
        {
            var namedRouteErrors = new List <string>();

            foreach (var kvp in actionsGroupedByRouteName)
            {
                // We are looking for attribute routed actions that have the same name but
                // different route templates. We pick the first template of the group and
                // we compare it against the rest of the templates that have that same name
                // associated.
                // The moment we find one that is different we report the whole group to the
                // user in the error message so that he can see the different actions and the
                // different templates for a given named attribute route.
                var firstActionDescriptor = kvp.Value[0];
                var firstTemplate         = firstActionDescriptor.AttributeRouteInfo.Template;

                for (var i = 1; i < kvp.Value.Count; i++)
                {
                    var otherActionDescriptor = kvp.Value[i];
                    var otherActionTemplate   = otherActionDescriptor.AttributeRouteInfo.Template;

                    if (!firstTemplate.Equals(otherActionTemplate, StringComparison.OrdinalIgnoreCase))
                    {
                        var descriptions = kvp.Value.Select(ad =>
                                                            Resources.FormatAttributeRoute_DuplicateNames_Item(
                                                                ad.DisplayName,
                                                                ad.AttributeRouteInfo.Template));

                        var errorDescription = string.Join(Environment.NewLine, descriptions);
                        var message          = Resources.FormatAttributeRoute_DuplicateNames(
                            kvp.Key,
                            Environment.NewLine,
                            errorDescription);

                        namedRouteErrors.Add(message);
                        break;
                    }
                }
            }

            return(namedRouteErrors);
        }
        private static void AddApiExplorerInfo(
            ControllerActionDescriptor actionDescriptor,
            ApplicationModel application,
            ControllerModel controller,
            ActionModel action)
        {
            var isVisible =
                action.ApiExplorer?.IsVisible ??
                controller.ApiExplorer?.IsVisible ??
                application.ApiExplorer?.IsVisible ??
                false;

            var isVisibleSetOnActionOrController =
                action.ApiExplorer?.IsVisible ??
                controller.ApiExplorer?.IsVisible ??
                false;

            // ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure
            // it at the application level when you have a mix of controller types. We'll just skip over enabling
            // ApiExplorer for conventional-routed controllers when this happens.
            var isVisibleSetOnApplication = application.ApiExplorer?.IsVisible ?? false;

            if (isVisibleSetOnActionOrController && !IsAttributeRoutedAction(actionDescriptor))
            {
                // ApiExplorer is only supported on attribute routed actions.
                throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
                                                        actionDescriptor.DisplayName));
            }
            else if (isVisibleSetOnApplication && !IsAttributeRoutedAction(actionDescriptor))
            {
                // This is the case where we're going to be lenient, just ignore it.
            }
            else if (isVisible)
            {
                Debug.Assert(IsAttributeRoutedAction(actionDescriptor));

                var apiExplorerActionData = new ApiDescriptionActionData()
                {
                    GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
                };

                actionDescriptor.SetProperty(apiExplorerActionData);
            }
        }
Example #10
0
        private static List <RouteInfo> GetRouteInfos(IReadOnlyList <ActionDescriptor> actions)
        {
            var routeInfos = new List <RouteInfo>();
            var errors     = new List <RouteInfo>();

            // This keeps a cache of 'Template' objects. It's a fairly common case that multiple actions
            // will use the same route template string; thus, the `Template` object can be shared.
            //
            // For a relatively simple route template, the `Template` object will hold about 500 bytes
            // of memory, so sharing is worthwhile.
            var templateCache = new Dictionary <string, RouteTemplate>(StringComparer.OrdinalIgnoreCase);

            var attributeRoutedActions = actions.Where(a => a.AttributeRouteInfo?.Template != null);

            foreach (var action in attributeRoutedActions)
            {
                var routeInfo = GetRouteInfo(templateCache, action);
                if (routeInfo.ErrorMessage == null)
                {
                    routeInfos.Add(routeInfo);
                }
                else
                {
                    errors.Add(routeInfo);
                }
            }

            if (errors.Count > 0)
            {
                var allErrors = string.Join(
                    Environment.NewLine + Environment.NewLine,
                    errors.Select(
                        e => Resources.FormatAttributeRoute_IndividualErrorMessage(
                            e.ActionDescriptor.DisplayName,
                            Environment.NewLine,
                            e.ErrorMessage)));

                var message = Resources.FormatAttributeRoute_AggregateErrorMessage(Environment.NewLine, allErrors);
                throw new RouteCreationException(message);
            }

            return(routeInfos);
        }
Example #11
0
        private async Task <ActionExecutedContext> InvokeNextActionFilterAwaitedAsync()
        {
            Debug.Assert(_actionExecutingContext != null);
            if (_actionExecutingContext.Result != null)
            {
                // If we get here, it means that an async filter set a result AND called next(). This is forbidden.
                var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
                    typeof(IAsyncActionFilter).Name,
                    nameof(ActionExecutingContext.Result),
                    typeof(ActionExecutingContext).Name,
                    typeof(ActionExecutionDelegate).Name);

                throw new InvalidOperationException(message);
            }

            await InvokeNextActionFilterAsync();

            Debug.Assert(_actionExecutedContext != null);
            return(_actionExecutedContext);
        }
Example #12
0
        public ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList <ActionDescriptor> candidates)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

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

            var matches = EvaluateActionConstraints(context, candidates);

            var finalMatches = SelectBestActions(matches);

            if (finalMatches == null || finalMatches.Count == 0)
            {
                return(null);
            }
            else if (finalMatches.Count == 1)
            {
                var selectedAction = finalMatches[0];

                return(selectedAction);
            }
            else
            {
                var actionNames = string.Join(
                    Environment.NewLine,
                    finalMatches.Select(a => a.DisplayName));

                _logger.AmbiguousActions(actionNames);

                var message = Resources.FormatDefaultActionSelector_AmbiguousActions(
                    Environment.NewLine,
                    actionNames);

                throw new AmbiguousActionException(message);
            }
        }
Example #13
0
        private static MediaTypeCollection GetContentTypes(string contentType, string[] additionalContentTypes)
        {
            var completeContentTypes = new List <string>(additionalContentTypes.Length + 1);

            completeContentTypes.Add(contentType);
            completeContentTypes.AddRange(additionalContentTypes);
            MediaTypeCollection contentTypes = new();

            foreach (var type in completeContentTypes)
            {
                var mediaType = new MediaType(type);
                if (mediaType.HasWildcard)
                {
                    throw new InvalidOperationException(Resources.FormatGetContentTypes_WildcardsNotSupported(type));
                }

                contentTypes.Add(type);
            }

            return(contentTypes);
        }
Example #14
0
        private MediaTypeCollection GetContentTypes(string firstArg, string[] args)
        {
            var completeArgs = new List <string>();

            completeArgs.Add(firstArg);
            completeArgs.AddRange(args);
            var contentTypes = new MediaTypeCollection();

            foreach (var arg in completeArgs)
            {
                var mediaType = new MediaType(arg);
                if (mediaType.MatchesAllSubTypes ||
                    mediaType.MatchesAllTypes)
                {
                    throw new InvalidOperationException(
                              Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
                }

                contentTypes.Add(arg);
            }

            return(contentTypes);
        }