Enable request to reach base controller class's action

Fix #378
This commit is contained in:
Tian Pan 2014-06-11 10:31:06 -07:00
parent 8ce8447c08
commit 60443101d5
6 changed files with 244 additions and 5 deletions

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Mvc
{ {
private DynamicViewData _viewBag; private DynamicViewData _viewBag;
[NonAction]
public void Initialize(IActionResultHelper actionResultHelper) public void Initialize(IActionResultHelper actionResultHelper)
{ {
Result = actionResultHelper; Result = actionResultHelper;
@ -42,6 +43,7 @@ namespace Microsoft.AspNet.Mvc
public IActionResultHelper Result { get; private set; } public IActionResultHelper Result { get; private set; }
public IUrlHelper Url { get; set; } public IUrlHelper Url { get; set; }
public IPrincipal User public IPrincipal User
{ {
get get
@ -70,22 +72,26 @@ namespace Microsoft.AspNet.Mvc
} }
} }
[NonAction]
public ViewResult View() public ViewResult View()
{ {
return View(view: null); return View(view: null);
} }
[NonAction]
public ViewResult View(string view) public ViewResult View(string view)
{ {
return View(view, model: null); return View(view, model: null);
} }
[NonAction]
// TODO #110: May need <TModel> here and in the overload below. // TODO #110: May need <TModel> here and in the overload below.
public ViewResult View(object model) public ViewResult View(object model)
{ {
return View(view: null, model: model); return View(view: null, model: model);
} }
[NonAction]
public ViewResult View(string view, object model) public ViewResult View(string view, object model)
{ {
// Do not override ViewData.Model unless passed a non-null value. // Do not override ViewData.Model unless passed a non-null value.
@ -97,26 +103,31 @@ namespace Microsoft.AspNet.Mvc
return Result.View(view, ViewData); return Result.View(view, ViewData);
} }
[NonAction]
public ContentResult Content(string content) public ContentResult Content(string content)
{ {
return Content(content, contentType: null); return Content(content, contentType: null);
} }
[NonAction]
public ContentResult Content(string content, string contentType) public ContentResult Content(string content, string contentType)
{ {
return Content(content, contentType, contentEncoding: null); return Content(content, contentType, contentEncoding: null);
} }
[NonAction]
public ContentResult Content(string content, string contentType, Encoding contentEncoding) public ContentResult Content(string content, string contentType, Encoding contentEncoding)
{ {
return Result.Content(content, contentType, contentEncoding); return Result.Content(content, contentType, contentEncoding);
} }
[NonAction]
public JsonResult Json(object value) public JsonResult Json(object value)
{ {
return Result.Json(value); return Result.Json(value);
} }
[NonAction]
public virtual RedirectResult Redirect(string url) public virtual RedirectResult Redirect(string url)
{ {
if (string.IsNullOrEmpty(url)) if (string.IsNullOrEmpty(url))
@ -127,6 +138,7 @@ namespace Microsoft.AspNet.Mvc
return new RedirectResult(url); return new RedirectResult(url);
} }
[NonAction]
public virtual RedirectResult RedirectPermanent(string url) public virtual RedirectResult RedirectPermanent(string url)
{ {
if (string.IsNullOrEmpty(url)) if (string.IsNullOrEmpty(url))
@ -137,21 +149,25 @@ namespace Microsoft.AspNet.Mvc
return new RedirectResult(url, permanent: true); return new RedirectResult(url, permanent: true);
} }
[NonAction]
public RedirectToActionResult RedirectToAction(string actionName) public RedirectToActionResult RedirectToAction(string actionName)
{ {
return RedirectToAction(actionName, routeValues: null); return RedirectToAction(actionName, routeValues: null);
} }
[NonAction]
public RedirectToActionResult RedirectToAction(string actionName, object routeValues) public RedirectToActionResult RedirectToAction(string actionName, object routeValues)
{ {
return RedirectToAction(actionName, controllerName: null, routeValues: routeValues); return RedirectToAction(actionName, controllerName: null, routeValues: routeValues);
} }
[NonAction]
public RedirectToActionResult RedirectToAction(string actionName, string controllerName) public RedirectToActionResult RedirectToAction(string actionName, string controllerName)
{ {
return RedirectToAction(actionName, controllerName, routeValues: null); return RedirectToAction(actionName, controllerName, routeValues: null);
} }
[NonAction]
public RedirectToActionResult RedirectToAction(string actionName, string controllerName, public RedirectToActionResult RedirectToAction(string actionName, string controllerName,
object routeValues) object routeValues)
{ {
@ -159,21 +175,25 @@ namespace Microsoft.AspNet.Mvc
TypeHelper.ObjectToDictionary(routeValues)); TypeHelper.ObjectToDictionary(routeValues));
} }
[NonAction]
public RedirectToActionResult RedirectToActionPermanent(string actionName) public RedirectToActionResult RedirectToActionPermanent(string actionName)
{ {
return RedirectToActionPermanent(actionName, routeValues: null); return RedirectToActionPermanent(actionName, routeValues: null);
} }
[NonAction]
public RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues) public RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues)
{ {
return RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues); return RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues);
} }
[NonAction]
public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName) public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName)
{ {
return RedirectToActionPermanent(actionName, controllerName, routeValues: null); return RedirectToActionPermanent(actionName, controllerName, routeValues: null);
} }
[NonAction]
public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName, public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName,
object routeValues) object routeValues)
{ {
@ -181,46 +201,55 @@ namespace Microsoft.AspNet.Mvc
TypeHelper.ObjectToDictionary(routeValues), permanent: true); TypeHelper.ObjectToDictionary(routeValues), permanent: true);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoute(string routeName) public RedirectToRouteResult RedirectToRoute(string routeName)
{ {
return RedirectToRoute(routeName, routeValues: null); return RedirectToRoute(routeName, routeValues: null);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoute(object routeValues) public RedirectToRouteResult RedirectToRoute(object routeValues)
{ {
return RedirectToRoute(routeName: null, routeValues: routeValues); return RedirectToRoute(routeName: null, routeValues: routeValues);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) public RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
{ {
return new RedirectToRouteResult(Url, routeName, routeValues); return new RedirectToRouteResult(Url, routeName, routeValues);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoutePermanent(string routeName) public RedirectToRouteResult RedirectToRoutePermanent(string routeName)
{ {
return RedirectToRoutePermanent(routeName, routeValues: null); return RedirectToRoutePermanent(routeName, routeValues: null);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoutePermanent(object routeValues) public RedirectToRouteResult RedirectToRoutePermanent(object routeValues)
{ {
return RedirectToRoutePermanent(routeName: null, routeValues: routeValues); return RedirectToRoutePermanent(routeName: null, routeValues: routeValues);
} }
[NonAction]
public RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) public RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues)
{ {
return new RedirectToRouteResult(Url, routeName, routeValues, permanent: true); return new RedirectToRouteResult(Url, routeName, routeValues, permanent: true);
} }
[NonAction]
public virtual void OnActionExecuting([NotNull] ActionExecutingContext context) public virtual void OnActionExecuting([NotNull] ActionExecutingContext context)
{ {
} }
[NonAction]
public virtual void OnActionExecuted([NotNull] ActionExecutedContext context) public virtual void OnActionExecuted([NotNull] ActionExecutedContext context)
{ {
} }
[NonAction]
public virtual async Task OnActionExecutionAsync( public virtual async Task OnActionExecutionAsync(
[NotNull] ActionExecutingContext context, [NotNull] ActionExecutingContext context,
[NotNull] ActionExecutionDelegate next) [NotNull] ActionExecutionDelegate next)
{ {
OnActionExecuting(context); OnActionExecuting(context);

View File

@ -90,7 +90,10 @@ namespace Microsoft.AspNet.Mvc
// The SpecialName bit is set to flag members that are treated in a special way by some compilers // The SpecialName bit is set to flag members that are treated in a special way by some compilers
// (such as property accessors and operator overloading methods). // (such as property accessors and operator overloading methods).
!method.IsSpecialName && !method.IsSpecialName &&
!method.IsDefined(typeof(NonActionAttribute)); !method.IsDefined(typeof(NonActionAttribute)) &&
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
method.GetBaseDefinition().DeclaringType != typeof(object);
} }
public virtual IEnumerable<string> GetSupportedHttpMethods(MethodInfo methodInfo) public virtual IEnumerable<string> GetSupportedHttpMethods(MethodInfo methodInfo)

View File

@ -53,6 +53,12 @@ namespace Microsoft.AspNet.Mvc
.Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t)) .Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t))
.ToArray(); .ToArray();
return GetDescriptors(controllerDescriptors);
}
// Internal for unit testing.
internal IEnumerable<ActionDescriptor> GetDescriptors(IEnumerable<ControllerDescriptor> controllerDescriptors)
{
foreach (var cd in controllerDescriptors) foreach (var cd in controllerDescriptors)
{ {
var controllerAttributes = cd.ControllerTypeInfo.GetCustomAttributes(inherit: true).ToArray(); var controllerAttributes = cd.ControllerTypeInfo.GetCustomAttributes(inherit: true).ToArray();
@ -63,7 +69,7 @@ namespace Microsoft.AspNet.Mvc
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToArray(); .ToArray();
foreach (var methodInfo in cd.ControllerTypeInfo.DeclaredMethods) foreach (var methodInfo in cd.ControllerTypeInfo.AsType().GetMethods())
{ {
var actionInfos = _conventions.GetActions(methodInfo, cd.ControllerTypeInfo); var actionInfos = _conventions.GetActions(methodInfo, cd.ControllerTypeInfo);
if (actionInfos == null) if (actionInfos == null)

View File

@ -2,8 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Xunit; using Xunit;
@ -12,6 +13,17 @@ namespace Microsoft.AspNet.Mvc.Core
{ {
public class ControllerTests public class ControllerTests
{ {
public static IEnumerable<object[]> PublicNormalMethodsFromController
{
get
{
return typeof(Controller).GetTypeInfo()
.DeclaredMethods
.Where(method => method.IsPublic && !method.IsSpecialName)
.Select(method => new [] { method });
}
}
[Fact] [Fact]
public void SettingViewData_AlsoUpdatesViewBag() public void SettingViewData_AlsoUpdatesViewBag()
{ {
@ -248,6 +260,14 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultPermanent.RouteValues); Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultPermanent.RouteValues);
} }
[Theory]
[MemberData("PublicNormalMethodsFromController")]
public void NonActionAttribute_IsOnEveryPublicNormalMethodFromController(MethodInfo method)
{
// Arrange & Act & Assert
Assert.True(method.IsDefined(typeof(NonActionAttribute)));
}
public static IEnumerable<object[]> RedirectTestData public static IEnumerable<object[]> RedirectTestData
{ {
get get

View File

@ -49,6 +49,7 @@
<Compile Include="JsonResultTest.cs" /> <Compile Include="JsonResultTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PropertyHelperTest.cs" /> <Compile Include="PropertyHelperTest.cs" />
<Compile Include="ReflectedActionDescriptorProviderTests.cs" />
<Compile Include="ReflectedActionInvokerTest.cs" /> <Compile Include="ReflectedActionInvokerTest.cs" />
<Compile Include="Rendering\HtmlAttributePropertyHelperTest.cs" /> <Compile Include="Rendering\HtmlAttributePropertyHelperTest.cs" />
<Compile Include="Rendering\ViewContextTests.cs" /> <Compile Include="Rendering\ViewContextTests.cs" />
@ -60,4 +61,4 @@
<Compile Include="ViewComponentTests.cs" /> <Compile Include="ViewComponentTests.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -0,0 +1,180 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ReflectedActionDescriptorProviderTests
{
private DefaultActionDiscoveryConventions _actionDiscoveryConventions =
new DefaultActionDiscoveryConventions();
private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory();
private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory();
[Fact]
public void GetDescriptors_GetsDescriptorsOnlyForValidActionsInBaseAndDerivedController()
{
// Arrange & Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
// "NewMethod" is a public method declared with keyword "new".
Assert.Equal(new[] { "GetFromDerived", "NewMethod", "GetFromBase" }, actionNames);
}
[Fact]
public void GetDescriptors_Ignores_OverridenRedirect_FromControllerClass()
{
// Arrange & Act
var actionNames = GetDescriptors(typeof(BaseController).GetTypeInfo()).Select(a => a.Name);
// Assert
Assert.False(actionNames.Contains("Redirect"));
}
[Fact]
public void GetDescriptors_Ignores_PrivateMethod_FromUserDefinedController()
{
// Arrange & Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
Assert.False(actionNames.Contains("PrivateMethod"));
}
[Fact]
public void GetDescriptors_Ignores_Constructor_FromUserDefinedController()
{
// Arrange & Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
Assert.False(actionNames.Contains("DerivedController"));
}
[Fact]
public void GetDescriptors_Ignores_OperatorOverloadingMethod_FromOperatorOverloadingController()
{
// Arrange & Act
var actionDescriptors = GetDescriptors(typeof(OperatorOverloadingController).GetTypeInfo());
// Assert
Assert.Empty(actionDescriptors);
}
[Fact]
public void GetDescriptors_Ignores_GenericMethod_FromUserDefinedController()
{
// Arrange & Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
Assert.False(actionNames.Contains("GenericMethod"));
}
[Fact]
public void GetDescriptors_Ignores_OverridenNonActionMethod_FromDerivedController()
{
// Arrange & Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
Assert.False(actionNames.Contains("OverridenNonActionMethod"));
}
[Fact]
public void GetDescriptors_Ignores_MethodsFromObjectClass_FromUserDefinedController()
{
// Arrange
var methodsFromObjectClass = typeof(object).GetMethods().Select(m => m.Name);
// Act
var actionNames = GetActionNamesFromDerivedController();
// Assert
Assert.Empty(methodsFromObjectClass.Intersect(actionNames));
}
private IEnumerable<string> GetActionNamesFromDerivedController()
{
return GetDescriptors(typeof(DerivedController).GetTypeInfo()).Select(a => a.Name);
}
private IEnumerable<ActionDescriptor> GetDescriptors(TypeInfo controllerTypeInfo)
{
var provider = new ReflectedActionDescriptorProvider(null,
_actionDiscoveryConventions,
_controllerDescriptorFactory,
_parameterDescriptorFactory,
null);
var testControllers = new TypeInfo[]
{
controllerTypeInfo,
};
var controllerDescriptors = testControllers
.Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t));
return provider.GetDescriptors(controllerDescriptors);
}
private class DerivedController : BaseController
{
public void GetFromDerived() // Valid action method.
{
}
[HttpGet]
public override void OverridenNonActionMethod()
{
}
public new void NewMethod() // Valid action method.
{
}
public void GenericMethod<T>()
{
}
private void PrivateMethod()
{
}
}
private class OperatorOverloadingController : Controller
{
public static OperatorOverloadingController operator +(
OperatorOverloadingController c1,
OperatorOverloadingController c2)
{
return new OperatorOverloadingController();
}
}
private class BaseController : Controller
{
public void GetFromBase() // Valid action method.
{
}
[NonAction]
public virtual void OverridenNonActionMethod()
{
}
[NonAction]
public virtual void NewMethod()
{
}
public override RedirectResult Redirect(string url)
{
return base.Redirect(url + "#RedirectOverride");
}
}
}
}