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); } }
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); }
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); } }
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); }
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); }
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); } }
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); }
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); }