-
-
Save paddo/9d9d5e9742b1b50c086f58275c887973 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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