using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.Infrastructure; | |
using Microsoft.AspNetCore.Mvc.Internal; | |
using Microsoft.AspNetCore.Routing; | |
using Microsoft.Extensions.Logging; | |
using System; | |
using System.Diagnostics; | |
using System.Threading.Tasks; | |
namespace ControllerlessDemo | |
{ | |
// "controllerless" router - if no controller/action found, route to a default controller that simply renders the view, | |
// otherwise invoke controller/action as normal - replaces MvcRouteHandler | |
public class ControllerlessMvcRouteHandler : IRouter | |
{ | |
private IActionContextAccessor _actionContextAccessor; | |
private IActionInvokerFactory _actionInvokerFactory; | |
private IActionSelector _actionSelector; | |
private ILogger _logger; | |
private DiagnosticSource _diagnosticSource; | |
public ControllerlessMvcRouteHandler( | |
IActionInvokerFactory actionInvokerFactory, | |
IActionSelector actionSelector, | |
DiagnosticSource diagnosticSource, | |
ILoggerFactory loggerFactory) | |
: this(actionInvokerFactory, actionSelector, diagnosticSource, loggerFactory, actionContextAccessor: null) | |
{ | |
} | |
public ControllerlessMvcRouteHandler( | |
IActionInvokerFactory actionInvokerFactory, | |
IActionSelector actionSelector, | |
DiagnosticSource diagnosticSource, | |
ILoggerFactory loggerFactory, | |
IActionContextAccessor actionContextAccessor) | |
{ | |
// The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext | |
// if possible. | |
_actionContextAccessor = actionContextAccessor; | |
_actionInvokerFactory = actionInvokerFactory; | |
_actionSelector = actionSelector; | |
_diagnosticSource = diagnosticSource; | |
_logger = loggerFactory.CreateLogger<ControllerlessMvcRouteHandler>(); | |
} | |
public VirtualPathData GetVirtualPath(VirtualPathContext context) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
// We return null here because we're not responsible for generating the url, the route is. | |
return null; | |
} | |
public Task RouteAsync(RouteContext context) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
var candidates = _actionSelector.SelectCandidates(context); | |
// no controller/action available to execute route - run it controllerless | |
if (candidates == null || candidates.Count == 0) | |
{ | |
MakeRouteControllerless(context); | |
candidates = _actionSelector.SelectCandidates(context); | |
} | |
var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); | |
if (actionDescriptor == null) | |
{ | |
_logger.LogDebug(3, "No actions matched the current request"); | |
return TaskCache.CompletedTask; | |
} | |
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($"An action invoker could not be created for action '{actionDescriptor.DisplayName}'."); | |
} | |
return invoker.InvokeAsync(); | |
}; | |
return TaskCache.CompletedTask; | |
} | |
private void MakeRouteControllerless(RouteContext context) | |
{ | |
var controller = context.RouteData.Values["controller"].ToString(); | |
var action = context.RouteData.Values["action"].ToString(); | |
context.RouteData.Values["x-old-controller"] = controller; | |
context.RouteData.Values["x-old-action"] = action; | |
context.RouteData.Values["controller"] = "default"; | |
context.RouteData.Values["action"] = "default"; | |
context.RouteData.Values["viewName"] = $"~/views/{controller}/{action}.cshtml"; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment