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

View File

@ -53,6 +53,12 @@ namespace Microsoft.AspNet.Mvc
.Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t))
.ToArray();
return GetDescriptors(controllerDescriptors);
}
// Internal for unit testing.
internal IEnumerable<ActionDescriptor> GetDescriptors(IEnumerable<ControllerDescriptor> controllerDescriptors)
{
foreach (var cd in controllerDescriptors)
{
var controllerAttributes = cd.ControllerTypeInfo.GetCustomAttributes(inherit: true).ToArray();
@ -63,7 +69,7 @@ namespace Microsoft.AspNet.Mvc
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToArray();
foreach (var methodInfo in cd.ControllerTypeInfo.DeclaredMethods)
foreach (var methodInfo in cd.ControllerTypeInfo.AsType().GetMethods())
{
var actionInfos = _conventions.GetActions(methodInfo, cd.ControllerTypeInfo);
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.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing;
using Xunit;
@ -12,6 +13,17 @@ namespace Microsoft.AspNet.Mvc.Core
{
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]
public void SettingViewData_AlsoUpdatesViewBag()
{
@ -248,6 +260,14 @@ namespace Microsoft.AspNet.Mvc.Core
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
{
get

View File

@ -49,6 +49,7 @@
<Compile Include="JsonResultTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PropertyHelperTest.cs" />
<Compile Include="ReflectedActionDescriptorProviderTests.cs" />
<Compile Include="ReflectedActionInvokerTest.cs" />
<Compile Include="Rendering\HtmlAttributePropertyHelperTest.cs" />
<Compile Include="Rendering\ViewContextTests.cs" />
@ -60,4 +61,4 @@
<Compile Include="ViewComponentTests.cs" />
</ItemGroup>
<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");
}
}
}
}