Exemplo n.º 1
0
        /// <summary>
        /// Resolves HTTP request to a route using the provided route context and the action selector and invoker services.
        /// </summary>
        /// <param name="services">Application services from which the route will be resolved.</param>
        /// <param name="router">IRouter to resolve route values.</param>
        /// <param name="routeContext">RouteContext to use for resolving the route values.</param>
        /// <returns>Resolved route information.</returns>
        public static ResolvedRouteContext Resolve(IServiceProvider services, IRouter router, RouteContext routeContext)
        {
            try
            {
                RouteDataResolver.ResolveRouteData(router, routeContext);
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to resolve route data: '{ex.Unwrap().Message}'"));
            }

            var actionSelector = services.GetRequiredService <IActionSelector>();

            ActionDescriptor actionDescriptor;

            try
            {
                var actions = routeContext
                              .RouteData
                              .Routers
                              .OfType <MvcAttributeRouteHandler>()
                              .FirstOrDefault()
                              ?.Actions
                              ?? actionSelector.SelectCandidates(routeContext);

                actionDescriptor = actionSelector.SelectBestCandidate(
                    routeContext,
                    actions);
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to select an action: '{ex.Unwrap().Message}'"));
            }

            if (actionDescriptor == null)
            {
                return(new ResolvedRouteContext("action could not be matched"));
            }

            var actionContext = new ActionContext(routeContext.HttpContext, routeContext.RouteData, actionDescriptor);

            var controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor;

            if (controllerActionDescriptor == null)
            {
                throw new InvalidOperationException("Only controller actions are supported by the route testing.");
            }

            var actionInvokerFactory = services.GetRequiredService <IActionInvokerFactory>();

            var invoker = actionInvokerFactory.CreateInvoker(actionContext);
            var modelBindingActionInvoker = invoker as IModelBindingActionInvoker;

            if (modelBindingActionInvoker == null)
            {
                throw new InvalidOperationException("Route testing requires the selected IActionInvoker by the IActionInvokerFactory to implement IModelBindingActionInvoker.");
            }

            try
            {
                AsyncHelper.RunSync(() => modelBindingActionInvoker.InvokeAsync());
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to bind the action arguments: '{ex.Unwrap().Message}'"));
            }

            if (modelBindingActionInvoker.BoundActionArguments == null)
            {
                return(new ResolvedRouteContext("action could not be invoked because of the declared filters. You must set the request properties so that they will pass through the pipeline"));
            }

            return(new ResolvedRouteContext(
                       controllerActionDescriptor.ControllerTypeInfo,
                       controllerActionDescriptor.ControllerName,
                       controllerActionDescriptor.ActionName,
                       modelBindingActionInvoker.BoundActionArguments,
                       actionContext.RouteData,
                       actionContext.ModelState));
        }
        /// <summary>
        /// Resolves HTTP request to a route using the provided route context and the action selector and invoker services.
        /// </summary>
        /// <param name="services">Application services from which the route will be resolved.</param>
        /// <param name="router">IRouter to resolve route values.</param>
        /// <param name="routeContext">RouteContext to use for resolving the route values.</param>
        /// <param name="fullExecution">Indicates whether the full MVC pipeline will be executed or not.</param>
        /// <returns>Resolved route information.</returns>
        public static ResolvedRouteContext Resolve(
            IServiceProvider services,
            IRouter router,
            RouteContext routeContext,
            bool fullExecution = false)
        {
            try
            {
                RouteDataResolver.ResolveRouteData(router, routeContext);
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to resolve route data: '{ex.Unwrap().Message}'"));
            }

            var actionSelector = services.GetRequiredService <IActionSelector>();

            ActionDescriptor actionDescriptor;

            try
            {
                var actions = routeContext
                              .RouteData
                              .Routers
                              .FirstOrDefault(r => r.GetType() == WebFramework.Internals.MvcAttributeRouteHandler)
                              ?.Exposed()
                              .Actions
                              ?? actionSelector.SelectCandidates(routeContext);

                actionDescriptor = actionSelector.SelectBestCandidate(
                    routeContext,
                    actions);
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to select an action: '{ex.Unwrap().Message}'"));
            }

            if (actionDescriptor == null)
            {
                return(new ResolvedRouteContext("action could not be matched"));
            }

            routeContext.HttpContext.Features.Set(new RouteTestingFeature(fullExecution));

            var actionContext = new ActionContext(routeContext.HttpContext, routeContext.RouteData, actionDescriptor);

            if (!(actionDescriptor is ControllerActionDescriptor controllerActionDescriptor))
            {
                throw new InvalidOperationException("Only controller actions are supported by the route testing.");
            }

            var actionInvokerFactory = services.GetRequiredService <IActionInvokerFactory>();

            var invoker = actionInvokerFactory.CreateInvoker(actionContext);

            if (!(invoker is IModelBindingActionInvoker modelBindingActionInvoker))
            {
                throw new InvalidOperationException($"Route testing requires the selected {nameof(IActionInvoker)} by the {nameof(IActionInvokerFactory)} to implement {nameof(IModelBindingActionInvoker)}.");
            }

            try
            {
                AsyncHelper.RunSync(() => modelBindingActionInvoker.InvokeAsync());
            }
            catch (Exception ex)
            {
                return(new ResolvedRouteContext($"exception was thrown when trying to invoke the pipeline: '{ex.Unwrap().Message}'"));
            }

            if (modelBindingActionInvoker.BoundActionArguments == null)
            {
                var filters = actionDescriptor
                              .FilterDescriptors
                              .OrderByDescending(f => f.Order)
                              .Select(f => $"{f.Filter.GetName()} ({f.Scope.ToFilterScopeName()})")
                              .ToArray();

                var filtersMessage = filters.Any()
                    ? $"filters - {string.Join(", ", filters)}"
                    : "filters";

                return(new ResolvedRouteContext($"action could not be invoked because of the declared {filtersMessage}. Either a filter is setting the response result before the action itself, or you must set the request properties so that they will pass through the pipeline"));
            }

            return(new ResolvedRouteContext(
                       controllerActionDescriptor.ControllerTypeInfo,
                       controllerActionDescriptor.ControllerName,
                       controllerActionDescriptor.ActionName,
                       modelBindingActionInvoker.BoundActionArguments,
                       actionContext.RouteData,
                       actionContext.ModelState));
        }