Reworked it so that it's fully action based.

- The action invoker factory is the entry point to an mvc application.
- The default implementation will use an IActionDescriptorProvider with
  a IActionInvokerProvider to resolve an IActionInvoker to invoke.
This commit is contained in:
David Fowler 2013-12-12 21:40:56 -08:00
parent b798736385
commit 307d2ea198
21 changed files with 251 additions and 100 deletions

View File

@ -0,0 +1,7 @@

namespace Microsoft.AspNet.Mvc
{
public class ActionDescriptor
{
}
}

View File

@ -0,0 +1,18 @@

namespace Microsoft.AspNet.Mvc
{
public class ActionDescriptorProvider : IActionDescriptorProvider
{
public ActionDescriptor CreateDescriptor(RequestContext requestContext)
{
string controllerName = requestContext.RouteData.GetRouteValue("controller");
string actionName = requestContext.RouteData.GetRouteValue("action");
return new ControllerBasedActionDescriptor
{
ControllerName = controllerName,
ActionName = actionName
};
}
}
}

View File

@ -0,0 +1,29 @@

using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
{
public class ActionInvokerFactory : IActionInvokerFactory
{
private readonly IActionResultFactory _actionResultFactory;
private readonly IActionDescriptorProvider _actionDescriptorProvider;
private readonly IActionInvokerProvider _actionInvokerProvider;
public ActionInvokerFactory(IActionResultFactory actionResultFactory,
IActionDescriptorProvider actionDescriptorProvider,
IActionInvokerProvider actionInvokerProvider)
{
_actionResultFactory = actionResultFactory;
_actionDescriptorProvider = actionDescriptorProvider;
_actionInvokerProvider = actionInvokerProvider;
}
public IActionInvoker CreateInvoker(RequestContext requestContext)
{
ActionDescriptor descriptor = _actionDescriptorProvider.CreateDescriptor(requestContext);
return _actionInvokerProvider.GetInvoker(requestContext, descriptor);
}
}
}

View File

@ -0,0 +1,33 @@
using System;
namespace Microsoft.AspNet.Mvc
{
public class ActionInvokerProvider : IActionInvokerProvider
{
private IActionResultFactory _actionResultFactory;
private IServiceProvider _serviceProvider;
public ActionInvokerProvider(IActionResultFactory actionResultFactory,
IServiceProvider serviceProvider)
{
_actionResultFactory = actionResultFactory;
_serviceProvider = serviceProvider;
}
public IActionInvoker GetInvoker(RequestContext requestContext, ActionDescriptor descriptor)
{
var controllerActionDescriptor = descriptor as ControllerBasedActionDescriptor;
if (controllerActionDescriptor != null)
{
return new ControllerActionInvoker(
requestContext,
controllerActionDescriptor,
_actionResultFactory,
_serviceProvider);
}
return null;
}
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc
public string ContentType { get; set; }
public async Task ExecuteResultAsync(ControllerContext context)
public async Task ExecuteResultAsync(RequestContext context)
{
if (context == null)
{

View File

@ -1,34 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.CoreServices;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
{
public class ControllerActionInvoker : IActionInvoker
{
private readonly ControllerContext _context;
private readonly RequestContext _requestContext;
private readonly ControllerBasedActionDescriptor _descriptor;
private readonly IActionResultFactory _actionResultFactory;
private readonly IServiceProvider _serviceProvider;
public ControllerActionInvoker(ControllerContext context, IActionResultFactory actionResultFactory)
public ControllerActionInvoker(RequestContext requestContext,
ControllerBasedActionDescriptor descriptor,
IActionResultFactory actionResultFactory,
IServiceProvider serviceProvider)
{
_context = context;
_requestContext = requestContext;
_descriptor = descriptor;
_actionResultFactory = actionResultFactory;
_serviceProvider = serviceProvider;
}
public Task InvokeActionAsync(string actionName)
public Task InvokeActionAsync()
{
var method = _context.Controller.GetType().GetMethod(actionName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
var factory = _serviceProvider.GetService<IControllerFactory>();
object controller = factory.CreateController(_requestContext.HttpContext, _descriptor.ControllerName);
if (controller == null)
{
throw new InvalidOperationException(String.Format("Couldn't find controller '{0}'.", _descriptor.ControllerName));
}
Initialize(controller, _requestContext);
var method = controller.GetType().GetMethod(_descriptor.ActionName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (method == null)
{
throw new InvalidOperationException(String.Format("Could not find action method '{0}'", actionName));
throw new InvalidOperationException(String.Format("Could not find action method '{0}'", _descriptor.ActionName));
}
object actionReturnValue = method.Invoke(_context.Controller, null);
object actionReturnValue = method.Invoke(controller, null);
IActionResult actionResult = _actionResultFactory.CreateActionResult(actionReturnValue);
return actionResult.ExecuteResultAsync(_context);
return actionResult.ExecuteResultAsync(_requestContext);
}
private void Initialize(object controller, RequestContext requestContext)
{
var controllerType = controller.GetType();
foreach (var prop in controllerType.GetProperties())
{
if (prop.Name == "Context")
{
if (prop.PropertyType == typeof(IOwinContext))
{
prop.SetValue(controller, requestContext.HttpContext);
}
else if (prop.PropertyType == typeof(IDictionary<string, object>))
{
prop.SetValue(controller, requestContext.HttpContext.Environment);
}
}
}
var method = controllerType.GetMethod("Initialize");
if (method == null)
{
return;
}
var args = method.GetParameters()
.Select(p => _serviceProvider.GetService(p.ParameterType)).ToArray();
method.Invoke(controller, args);
}
}
}

View File

@ -1,18 +0,0 @@

namespace Microsoft.AspNet.Mvc
{
public class ControllerActionInvokerFactory : IActionInvokerFactory
{
private readonly IActionResultFactory _actionResultFactory;
public ControllerActionInvokerFactory(IActionResultFactory actionResultFactory)
{
_actionResultFactory = actionResultFactory;
}
public IActionInvoker CreateInvoker(ControllerContext context)
{
return new ControllerActionInvoker(context, _actionResultFactory);
}
}
}

View File

@ -0,0 +1,10 @@

namespace Microsoft.AspNet.Mvc
{
public class ControllerBasedActionDescriptor : ActionDescriptor
{
public string ControllerName { get; set; }
public string ActionName { get; set; }
}
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc
get { return _singleton; }
}
public async Task ExecuteResultAsync(ControllerContext context)
public async Task ExecuteResultAsync(RequestContext context)
{
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc
ResponseMessage = responseMessage;
}
public async Task ExecuteResultAsync(ControllerContext context)
public async Task ExecuteResultAsync(RequestContext context)
{
var response = context.HttpContext.Response;
response.StatusCode = (int)ResponseMessage.StatusCode;

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc
_statusCode = statusCode;
}
public async Task ExecuteResultAsync(ControllerContext context)
public async Task ExecuteResultAsync(RequestContext context)
{
context.HttpContext.Response.StatusCode = _statusCode;
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
{
public interface IActionDescriptorProvider
{
ActionDescriptor CreateDescriptor(RequestContext requestContext);
}
}

View File

@ -4,6 +4,6 @@ namespace Microsoft.AspNet.Mvc
{
public interface IActionInvoker
{
Task InvokeActionAsync(string actionName);
Task InvokeActionAsync();
}
}

View File

@ -1,8 +1,11 @@

using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
{
public interface IActionInvokerFactory
{
IActionInvoker CreateInvoker(ControllerContext context);
IActionInvoker CreateInvoker(RequestContext requestContext);
}
}

View File

@ -0,0 +1,8 @@

namespace Microsoft.AspNet.Mvc
{
public interface IActionInvokerProvider
{
IActionInvoker GetInvoker(RequestContext requestContext, ActionDescriptor descriptor);
}
}

View File

@ -4,6 +4,6 @@ namespace Microsoft.AspNet.Mvc
{
public interface IActionResult
{
Task ExecuteResultAsync(ControllerContext context);
Task ExecuteResultAsync(RequestContext context);
}
}

View File

@ -48,16 +48,22 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ActionDescriptor.cs" />
<Compile Include="ActionDescriptorProvider.cs" />
<Compile Include="ActionInvokerProvider.cs" />
<Compile Include="ActionResultFactory.cs" />
<Compile Include="ActionResultHelper.cs" />
<Compile Include="ContentResult.cs" />
<Compile Include="ControllerBasedActionDescriptor.cs" />
<Compile Include="RequestContext.cs" />
<Compile Include="EmptyResult.cs" />
<Compile Include="HttpResponseMessageActionResult.cs" />
<Compile Include="IActionDescriptorProvider.cs" />
<Compile Include="IActionInvokerProvider.cs" />
<Compile Include="IActionResult.cs" />
<Compile Include="Controller.cs" />
<Compile Include="ControllerActionInvoker.cs" />
<Compile Include="ControllerActionInvokerFactory.cs" />
<Compile Include="ControllerContext.cs" />
<Compile Include="ActionInvokerFactory.cs" />
<Compile Include="DefaultControllerFactory.cs" />
<Compile Include="HttpStatusCodeResult.cs" />
<Compile Include="IActionInvoker.cs" />
@ -68,6 +74,7 @@
<Compile Include="MvcHandler.cs" />
<Compile Include="MvcServices.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Routing\IRouteData.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.CoreServices;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
@ -23,65 +22,12 @@ namespace Microsoft.AspNet.Mvc
public Task ExecuteAsync(IOwinContext context)
{
string[] parts = (context.Request.PathBase + context.Request.Path).Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// {controller}/{action}
string controllerName = GetPartOrDefault(parts, 0, "HomeController");
string actionName = GetPartOrDefault(parts, 1, "Index");
var factory = _serviceProvider.GetService<IControllerFactory>();
object controller = factory.CreateController(context, controllerName);
if (controller == null)
{
throw new InvalidOperationException(String.Format("Couldn't find controller '{0}'.", controllerName));
}
var controllerContext = new ControllerContext(context, controller);
Initialize(controller, controllerContext);
var routeData = new FakeRouteData(context);
IActionInvokerFactory invokerFactory = _serviceProvider.GetService<IActionInvokerFactory>();
var invoker = invokerFactory.CreateInvoker(controllerContext);
var invoker = invokerFactory.CreateInvoker(new RequestContext(context, routeData));
return invoker.InvokeActionAsync(actionName);
}
private void Initialize(object controller, ControllerContext controllerContext)
{
var controllerType = controller.GetType();
foreach (var prop in controllerType.GetProperties())
{
if (prop.Name == "Context")
{
if (prop.PropertyType == typeof(IOwinContext))
{
prop.SetValue(controller, controllerContext.HttpContext);
}
else if (prop.PropertyType == typeof(IDictionary<string, object>))
{
prop.SetValue(controller, controllerContext.HttpContext.Environment);
}
}
}
var method = controllerType.GetMethod("Initialize");
if (method == null)
{
return;
}
var args = method.GetParameters()
.Select(p => _serviceProvider.GetService(p.ParameterType)).ToArray();
method.Invoke(controller, args);
}
private static string GetPartOrDefault(string[] parts, int index, string defaultValue)
{
return index < parts.Length ? parts[index] : defaultValue;
return invoker.InvokeActionAsync();
}
}
}

View File

@ -15,9 +15,13 @@ namespace Microsoft.AspNet.Mvc
public static void DoCallback(Action<Type, Type> callback)
{
callback(typeof(IControllerFactory), typeof(DefaultControllerFactory));
callback(typeof(IActionInvokerFactory), typeof(ControllerActionInvokerFactory));
callback(typeof(IActionInvokerFactory), typeof(ActionInvokerFactory));
callback(typeof(IActionResultHelper), typeof(ActionResultHelper));
callback(typeof(IActionResultFactory), typeof(ActionResultFactory));
// TODO: Should be many
callback(typeof(IActionDescriptorProvider), typeof(ActionDescriptorProvider));
callback(typeof(IActionInvokerProvider), typeof(ActionInvokerProvider));
}
}
}

View File

@ -1,27 +1,28 @@
using System;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc
{
public class ControllerContext
public class RequestContext
{
public ControllerContext(IOwinContext context, object controller)
public RequestContext(IOwinContext context, IRouteData routeData)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (controller == null)
if (routeData == null)
{
throw new ArgumentNullException("controller");
throw new ArgumentNullException("routeData");
}
HttpContext = context;
Controller = controller;
RouteData = routeData;
}
public virtual object Controller { get; set; }
public virtual IRouteData RouteData { get; set; }
public virtual IOwinContext HttpContext { get; set; }
}

View File

@ -0,0 +1,40 @@
using System;
using Microsoft.Owin;
namespace Microsoft.AspNet.Mvc.Routing
{
// Move to routing middleware
public interface IRouteData
{
string GetRouteValue(string name);
}
public class FakeRouteData : IRouteData
{
private readonly string[] _parts;
public FakeRouteData(IOwinContext context)
{
_parts = (context.Request.PathBase + context.Request.Path).Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
}
public string GetRouteValue(string name)
{
if (name.Equals("controller", StringComparison.OrdinalIgnoreCase))
{
return GetPartOrDefault(0, "HomeController");
}
else if (name.Equals("action", StringComparison.OrdinalIgnoreCase))
{
return GetPartOrDefault(1, "Index");
}
return null;
}
private string GetPartOrDefault(int index, string defaultValue)
{
return index < _parts.Length ? _parts[index] : defaultValue;
}
}
}