From 307d2ea198b2dd07d46b9f9fb26c6cf93c7fb42a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 12 Dec 2013 21:40:56 -0800 Subject: [PATCH] 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. --- Microsoft.AspNet.Mvc/ActionDescriptor.cs | 7 ++ .../ActionDescriptorProvider.cs | 18 +++++ Microsoft.AspNet.Mvc/ActionInvokerFactory.cs | 29 ++++++++ Microsoft.AspNet.Mvc/ActionInvokerProvider.cs | 33 +++++++++ Microsoft.AspNet.Mvc/ContentResult.cs | 2 +- .../ControllerActionInvoker.cs | 69 ++++++++++++++++--- .../ControllerActionInvokerFactory.cs | 18 ----- .../ControllerBasedActionDescriptor.cs | 10 +++ Microsoft.AspNet.Mvc/EmptyResult.cs | 2 +- .../HttpResponseMessageActionResult.cs | 2 +- Microsoft.AspNet.Mvc/HttpStatusCodeResult.cs | 2 +- .../IActionDescriptorProvider.cs | 10 +++ Microsoft.AspNet.Mvc/IActionInvoker.cs | 2 +- Microsoft.AspNet.Mvc/IActionInvokerFactory.cs | 5 +- .../IActionInvokerProvider.cs | 8 +++ Microsoft.AspNet.Mvc/IActionResult.cs | 2 +- .../Microsoft.AspNet.Mvc.csproj | 11 ++- Microsoft.AspNet.Mvc/MvcHandler.cs | 62 ++--------------- Microsoft.AspNet.Mvc/MvcServices.cs | 6 +- ...ControllerContext.cs => RequestContext.cs} | 13 ++-- Microsoft.AspNet.Mvc/Routing/IRouteData.cs | 40 +++++++++++ 21 files changed, 251 insertions(+), 100 deletions(-) create mode 100644 Microsoft.AspNet.Mvc/ActionDescriptor.cs create mode 100644 Microsoft.AspNet.Mvc/ActionDescriptorProvider.cs create mode 100644 Microsoft.AspNet.Mvc/ActionInvokerFactory.cs create mode 100644 Microsoft.AspNet.Mvc/ActionInvokerProvider.cs delete mode 100644 Microsoft.AspNet.Mvc/ControllerActionInvokerFactory.cs create mode 100644 Microsoft.AspNet.Mvc/ControllerBasedActionDescriptor.cs create mode 100644 Microsoft.AspNet.Mvc/IActionDescriptorProvider.cs create mode 100644 Microsoft.AspNet.Mvc/IActionInvokerProvider.cs rename Microsoft.AspNet.Mvc/{ControllerContext.cs => RequestContext.cs} (51%) create mode 100644 Microsoft.AspNet.Mvc/Routing/IRouteData.cs diff --git a/Microsoft.AspNet.Mvc/ActionDescriptor.cs b/Microsoft.AspNet.Mvc/ActionDescriptor.cs new file mode 100644 index 0000000000..9ac4689922 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionDescriptor.cs @@ -0,0 +1,7 @@ + +namespace Microsoft.AspNet.Mvc +{ + public class ActionDescriptor + { + } +} diff --git a/Microsoft.AspNet.Mvc/ActionDescriptorProvider.cs b/Microsoft.AspNet.Mvc/ActionDescriptorProvider.cs new file mode 100644 index 0000000000..06dc0c3449 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionDescriptorProvider.cs @@ -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 + }; + } + } +} diff --git a/Microsoft.AspNet.Mvc/ActionInvokerFactory.cs b/Microsoft.AspNet.Mvc/ActionInvokerFactory.cs new file mode 100644 index 0000000000..afd8d14a84 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionInvokerFactory.cs @@ -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); + } + } +} diff --git a/Microsoft.AspNet.Mvc/ActionInvokerProvider.cs b/Microsoft.AspNet.Mvc/ActionInvokerProvider.cs new file mode 100644 index 0000000000..bdc6e6b2f1 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionInvokerProvider.cs @@ -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; + } + } +} diff --git a/Microsoft.AspNet.Mvc/ContentResult.cs b/Microsoft.AspNet.Mvc/ContentResult.cs index 825fdc219e..8b69b5a0e8 100644 --- a/Microsoft.AspNet.Mvc/ContentResult.cs +++ b/Microsoft.AspNet.Mvc/ContentResult.cs @@ -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) { diff --git a/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs b/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs index 5482b02ee1..a1779250f5 100644 --- a/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs +++ b/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs @@ -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(); + 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)) + { + 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); } } } diff --git a/Microsoft.AspNet.Mvc/ControllerActionInvokerFactory.cs b/Microsoft.AspNet.Mvc/ControllerActionInvokerFactory.cs deleted file mode 100644 index 6d40b2a756..0000000000 --- a/Microsoft.AspNet.Mvc/ControllerActionInvokerFactory.cs +++ /dev/null @@ -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); - } - } -} diff --git a/Microsoft.AspNet.Mvc/ControllerBasedActionDescriptor.cs b/Microsoft.AspNet.Mvc/ControllerBasedActionDescriptor.cs new file mode 100644 index 0000000000..71baa1dd20 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ControllerBasedActionDescriptor.cs @@ -0,0 +1,10 @@ + +namespace Microsoft.AspNet.Mvc +{ + public class ControllerBasedActionDescriptor : ActionDescriptor + { + public string ControllerName { get; set; } + + public string ActionName { get; set; } + } +} diff --git a/Microsoft.AspNet.Mvc/EmptyResult.cs b/Microsoft.AspNet.Mvc/EmptyResult.cs index 8fb3b3c890..94b5e4d91c 100644 --- a/Microsoft.AspNet.Mvc/EmptyResult.cs +++ b/Microsoft.AspNet.Mvc/EmptyResult.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc get { return _singleton; } } - public async Task ExecuteResultAsync(ControllerContext context) + public async Task ExecuteResultAsync(RequestContext context) { } } diff --git a/Microsoft.AspNet.Mvc/HttpResponseMessageActionResult.cs b/Microsoft.AspNet.Mvc/HttpResponseMessageActionResult.cs index 0eac692479..2e715047b2 100644 --- a/Microsoft.AspNet.Mvc/HttpResponseMessageActionResult.cs +++ b/Microsoft.AspNet.Mvc/HttpResponseMessageActionResult.cs @@ -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; diff --git a/Microsoft.AspNet.Mvc/HttpStatusCodeResult.cs b/Microsoft.AspNet.Mvc/HttpStatusCodeResult.cs index 2e0b21f657..bc60749739 100644 --- a/Microsoft.AspNet.Mvc/HttpStatusCodeResult.cs +++ b/Microsoft.AspNet.Mvc/HttpStatusCodeResult.cs @@ -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; } diff --git a/Microsoft.AspNet.Mvc/IActionDescriptorProvider.cs b/Microsoft.AspNet.Mvc/IActionDescriptorProvider.cs new file mode 100644 index 0000000000..e47e919e48 --- /dev/null +++ b/Microsoft.AspNet.Mvc/IActionDescriptorProvider.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNet.Mvc.Routing; +using Microsoft.Owin; + +namespace Microsoft.AspNet.Mvc +{ + public interface IActionDescriptorProvider + { + ActionDescriptor CreateDescriptor(RequestContext requestContext); + } +} diff --git a/Microsoft.AspNet.Mvc/IActionInvoker.cs b/Microsoft.AspNet.Mvc/IActionInvoker.cs index 0ff839ad07..2ff2671788 100644 --- a/Microsoft.AspNet.Mvc/IActionInvoker.cs +++ b/Microsoft.AspNet.Mvc/IActionInvoker.cs @@ -4,6 +4,6 @@ namespace Microsoft.AspNet.Mvc { public interface IActionInvoker { - Task InvokeActionAsync(string actionName); + Task InvokeActionAsync(); } } diff --git a/Microsoft.AspNet.Mvc/IActionInvokerFactory.cs b/Microsoft.AspNet.Mvc/IActionInvokerFactory.cs index 5cb5bde9d5..9cb51657d1 100644 --- a/Microsoft.AspNet.Mvc/IActionInvokerFactory.cs +++ b/Microsoft.AspNet.Mvc/IActionInvokerFactory.cs @@ -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); } } diff --git a/Microsoft.AspNet.Mvc/IActionInvokerProvider.cs b/Microsoft.AspNet.Mvc/IActionInvokerProvider.cs new file mode 100644 index 0000000000..99abbd3d8f --- /dev/null +++ b/Microsoft.AspNet.Mvc/IActionInvokerProvider.cs @@ -0,0 +1,8 @@ + +namespace Microsoft.AspNet.Mvc +{ + public interface IActionInvokerProvider + { + IActionInvoker GetInvoker(RequestContext requestContext, ActionDescriptor descriptor); + } +} diff --git a/Microsoft.AspNet.Mvc/IActionResult.cs b/Microsoft.AspNet.Mvc/IActionResult.cs index ac6ef59247..4df054d7f3 100644 --- a/Microsoft.AspNet.Mvc/IActionResult.cs +++ b/Microsoft.AspNet.Mvc/IActionResult.cs @@ -4,6 +4,6 @@ namespace Microsoft.AspNet.Mvc { public interface IActionResult { - Task ExecuteResultAsync(ControllerContext context); + Task ExecuteResultAsync(RequestContext context); } } diff --git a/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj b/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj index 4c8aef6701..bb88882bcb 100644 --- a/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj +++ b/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj @@ -48,16 +48,22 @@ + + + + + + + - - + @@ -68,6 +74,7 @@ + diff --git a/Microsoft.AspNet.Mvc/MvcHandler.cs b/Microsoft.AspNet.Mvc/MvcHandler.cs index 7b59185261..e83dc28257 100644 --- a/Microsoft.AspNet.Mvc/MvcHandler.cs +++ b/Microsoft.AspNet.Mvc/MvcHandler.cs @@ -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(); - 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(); - 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)) - { - 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(); } } } diff --git a/Microsoft.AspNet.Mvc/MvcServices.cs b/Microsoft.AspNet.Mvc/MvcServices.cs index d213a4e9e2..1ca4a2d87f 100644 --- a/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/Microsoft.AspNet.Mvc/MvcServices.cs @@ -15,9 +15,13 @@ namespace Microsoft.AspNet.Mvc public static void DoCallback(Action 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)); } } } diff --git a/Microsoft.AspNet.Mvc/ControllerContext.cs b/Microsoft.AspNet.Mvc/RequestContext.cs similarity index 51% rename from Microsoft.AspNet.Mvc/ControllerContext.cs rename to Microsoft.AspNet.Mvc/RequestContext.cs index 57c15e459e..ec3429d9cc 100644 --- a/Microsoft.AspNet.Mvc/ControllerContext.cs +++ b/Microsoft.AspNet.Mvc/RequestContext.cs @@ -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; } } diff --git a/Microsoft.AspNet.Mvc/Routing/IRouteData.cs b/Microsoft.AspNet.Mvc/Routing/IRouteData.cs new file mode 100644 index 0000000000..5c315710c4 --- /dev/null +++ b/Microsoft.AspNet.Mvc/Routing/IRouteData.cs @@ -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; + } + } +}