Skip to content

Instantly share code, notes, and snippets.

@paddo
Created August 18, 2016 10:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paddo/9d9d5e9742b1b50c086f58275c887973 to your computer and use it in GitHub Desktop.
Save paddo/9d9d5e9742b1b50c086f58275c887973 to your computer and use it in GitHub Desktop.
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