Adding support for ActivateAttribute via IControllerActivator

This mechanism replaces code in Inject that activated properties
by looking them up by names.
This commit is contained in:
Pranav K 2014-06-10 16:13:45 -07:00
parent 6ae02b0321
commit cca78bb055
28 changed files with 734 additions and 51 deletions

13
Mvc.sln
View File

@ -37,6 +37,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Functi
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BasicWebSite", "test\WebSites\BasicWebSite\BasicWebSite.kproj", "{34DF1487-12C6-476C-BE0A-F31DF1939AE5}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActivatorWebSite", "test\WebSites\ActivatorWebSite\ActivatorWebSite.kproj", "{DB79BCBA-9538-4A53-87D9-77728E2BAA39}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InlineConstraintsWebSite", "test\WebSites\InlineConstraintsWebSite\InlineConstraintsWebSite.kproj", "{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}"
EndProject
Global
@ -179,6 +181,16 @@ Global
{34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|x86.ActiveCfg = Release|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|x86.ActiveCfg = Debug|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.Build.0 = Release|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|x86.ActiveCfg = Release|Any CPU
{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -208,6 +220,7 @@ Global
{16703B76-C9F7-4C75-AE6C-53D92E308E3C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{323D0C04-B518-4A8F-8A8E-3546AD153D34} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{34DF1487-12C6-476C-BE0A-F31DF1939AE5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{DB79BCBA-9538-4A53-87D9-77728E2BAA39} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{EA34877F-1AC1-42B7-B4E6-15A093F40CAE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -8,15 +8,12 @@ namespace MvcSample.Web.RandomNameSpace
{
private User _user = new User() { Name = "User Name", Address = "Home Address" };
public HttpContext Context
[Activate]
public HttpResponse Response
{
get
{
return ActionContext.HttpContext;
}
get; set;
}
// The property ActionContext gets injected by InitializeController from DefaultControllerFactory.
public ActionContext ActionContext { get; set; }
public string Index()
@ -42,7 +39,7 @@ namespace MvcSample.Web.RandomNameSpace
public void Raw()
{
Context.Response.WriteAsync("Hello World raw");
Response.WriteAsync("Hello World raw");
}
public ActionResult UserJson()

View File

@ -0,0 +1,16 @@
// 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;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Specifies that a property or parameter value should be initialized via the dependency injection
/// container for activated types.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class ActivateAttribute : Attribute
{
}
}

View File

@ -15,15 +15,6 @@ namespace Microsoft.AspNet.Mvc
public class Controller : IActionFilter, IAsyncActionFilter
{
private DynamicViewData _viewBag;
private IServiceProvider _serviceProvider;
private IViewEngine _viewEngine;
[NonAction]
public virtual void Initialize(IServiceProvider serviceProvider, IViewEngine viewEngine)
{
_serviceProvider = serviceProvider;
_viewEngine = viewEngine;
}
public HttpContext Context
{
@ -41,8 +32,16 @@ namespace Microsoft.AspNet.Mvc
}
}
[Activate]
public ActionContext ActionContext { get; set; }
[Activate]
public IServiceProvider ServiceProvider { get; set; }
[Activate]
public IViewEngine ViewEngine { get; set; }
[Activate]
public IUrlHelper Url { get; set; }
public IPrincipal User
@ -58,6 +57,7 @@ namespace Microsoft.AspNet.Mvc
}
}
[Activate]
public ViewDataDictionary ViewData { get; set; }
public dynamic ViewBag
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc
ViewData.Model = model;
}
return new ViewResult(_serviceProvider, _viewEngine)
return new ViewResult(ServiceProvider, ViewEngine)
{
ViewName = viewName,
ViewData = ViewData,

View File

@ -0,0 +1,131 @@
// 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Represents the <see cref="IControllerActivator"/> that is registered by default.
/// </summary>
public class DefaultControllerActivator : IControllerActivator
{
private readonly Func<Type, PropertyActivator[]> _getPropertiesToActivate;
private readonly Func<PropertyInfo, PropertyActivator> _createActivateInfo;
private readonly ReadOnlyDictionary<Type, Func<ActionContext, object>> _valueAccessorLookup;
private readonly ConcurrentDictionary<Type, PropertyActivator[]> _injectActions;
/// <summary>
/// Initializes a new instance of the DefaultControllerActivator class.
/// </summary>
public DefaultControllerActivator()
{
_valueAccessorLookup = CreateValueAccessorLookup();
_getPropertiesToActivate = GetPropertiesToActivate;
_createActivateInfo = CreateActivateInfo;
_injectActions = new ConcurrentDictionary<Type, PropertyActivator[]>();
}
/// <summary>
/// Activates the specified controller by using the specified action context.
/// </summary>
/// <param name="controller">The controller to activate.</param>
/// <param name="context">The context of the executing action.</param>
public void Activate([NotNull] object controller, [NotNull] ActionContext context)
{
var controllerType = controller.GetType();
var controllerTypeInfo = controllerType.GetTypeInfo();
if (controllerTypeInfo.IsValueType)
{
var message = Resources.FormatValueTypesCannotBeActivated(GetType().FullName);
throw new InvalidOperationException(message);
}
var propertiesToActivate = _injectActions.GetOrAdd(controllerType,
_getPropertiesToActivate);
for (var i = 0; i < propertiesToActivate.Length; i++)
{
var activateInfo = propertiesToActivate[i];
activateInfo.Activate(controller, context);
}
}
protected virtual ReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
{
var dictionary = new Dictionary<Type, Func<ActionContext, object>>
{
{ typeof(ActionContext), (context) => context },
{ typeof(HttpContext), (context) => context.HttpContext },
{ typeof(HttpRequest), (context) => context.HttpContext.Request },
{ typeof(HttpResponse), (context) => context.HttpContext.Response },
{
typeof(ViewDataDictionary),
(context) =>
{
var serviceProvider = context.HttpContext.RequestServices;
return new ViewDataDictionary(
serviceProvider.GetService<IModelMetadataProvider>(),
context.ModelState);
}
}
};
return new ReadOnlyDictionary<Type, Func<ActionContext, object>>(dictionary);
}
private PropertyActivator[] GetPropertiesToActivate(Type controllerType)
{
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
return controllerType.GetProperties(bindingFlags)
.Where(property => property.IsDefined(typeof(ActivateAttribute)) &&
property.GetSetMethod(nonPublic: true) != null)
.Select(_createActivateInfo)
.ToArray();
}
private PropertyActivator CreateActivateInfo(PropertyInfo property)
{
Func<ActionContext, object> valueAccessor;
if (!_valueAccessorLookup.TryGetValue(property.PropertyType, out valueAccessor))
{
valueAccessor = (actionContext) =>
{
var serviceProvider = actionContext.HttpContext.RequestServices;
return serviceProvider.GetService(property.PropertyType);
};
}
return new PropertyActivator(property,
valueAccessor);
}
private sealed class PropertyActivator
{
private readonly PropertyInfo _propertyInfo;
private readonly Func<ActionContext, object> _valueAccessor;
private readonly Action<object, object> _fastPropertySetter;
public PropertyActivator(PropertyInfo propertyInfo,
Func<ActionContext, object> valueAccessor)
{
_propertyInfo = propertyInfo;
_valueAccessor = valueAccessor;
_fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo);
}
public void Activate(object instance, ActionContext context)
{
var value = _valueAccessor(context);
_fastPropertySetter(instance, value);
}
}
}
}

View File

@ -3,20 +3,23 @@
using System;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class DefaultControllerFactory : IControllerFactory
{
private readonly ITypeActivator _activator;
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivator _typeActivator;
private readonly IControllerActivator _controllerActivator;
public DefaultControllerFactory(IServiceProvider serviceProvider, ITypeActivator activator)
public DefaultControllerFactory(IServiceProvider serviceProvider,
ITypeActivator typeActivator,
IControllerActivator controllerActivator)
{
_serviceProvider = serviceProvider;
_activator = activator;
_typeActivator = typeActivator;
_controllerActivator = controllerActivator;
}
public object CreateController(ActionContext actionContext)
@ -30,11 +33,12 @@ namespace Microsoft.AspNet.Mvc
"actionContext");
}
var controller = _activator.CreateInstance(
var controller = _typeActivator.CreateInstance(
_serviceProvider,
actionDescriptor.ControllerDescriptor.ControllerTypeInfo.AsType());
InitializeController(controller, actionContext);
actionContext.Controller = controller;
_controllerActivator.Activate(controller, actionContext);
return controller;
}
@ -48,20 +52,5 @@ namespace Microsoft.AspNet.Mvc
disposableController.Dispose();
}
}
private void InitializeController(object controller, ActionContext actionContext)
{
Injector.InjectProperty(controller, "ActionContext", actionContext);
var viewData = new ViewDataDictionary(
_serviceProvider.GetService<IModelMetadataProvider>(),
actionContext.ModelState);
Injector.InjectProperty(controller, "ViewData", viewData);
var urlHelper = _serviceProvider.GetService<IUrlHelper>();
Injector.InjectProperty(controller, "Url", urlHelper);
Injector.CallInitializer(controller, _serviceProvider);
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Provides methods to activate an instantiated controller.
/// </summary>
public interface IControllerActivator
{
/// <summary>
/// When implemented in a type, activates an instantiated controller.
/// </summary>
/// <param name="controller">The controller to activate.</param>
/// <param name="context">The <see cref="ActionContext"/> for the executing action.</param>
void Activate(object controller, ActionContext context);
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
@ -21,6 +20,9 @@ namespace Microsoft.AspNet.Mvc
private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod("CallPropertyGetterByReference");
private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod("CallPropertySetter");
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> ReflectionCache =
new ConcurrentDictionary<Type, PropertyHelper[]>();
@ -109,6 +111,45 @@ namespace Microsoft.AspNet.Mvc
return (Func<object, object>)callPropertyGetterDelegate;
}
/// <summary>
/// Creates a single fast property setter for reference types. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the setter for.</param>
/// <returns>a fast getter.</returns>
/// <remarks>
/// This method is more memory efficient than a dynamically compiled lambda, and about the
/// same speed. This only works for reference types.
/// </remarks>
public static Action<object, object> MakeFastPropertySetter(PropertyInfo propertyInfo)
{
Contract.Assert(propertyInfo != null);
Contract.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType);
var setMethod = propertyInfo.SetMethod;
Contract.Assert(setMethod != null);
Contract.Assert(!setMethod.IsStatic);
Contract.Assert(setMethod.ReturnType == typeof(void));
var parameters = setMethod.GetParameters();
Contract.Assert(parameters.Length == 1);
// Instance methods in the CLR can be turned into static methods where the first parameter
// is open over "target". This parameter is always passed by reference, so we have a code
// path for value types and a code path for reference types.
var typeInput = setMethod.DeclaringType;
var parameterType = parameters[0].ParameterType;
// Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; }
var propertySetterAsAction =
setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType));
var callPropertySetterClosedGenericMethod =
CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType);
var callPropertySetterDelegate =
callPropertySetterClosedGenericMethod.CreateDelegate(
typeof(Action<object, object>), propertySetterAsAction);
return (Action<object, object>)callPropertySetterDelegate;
}
private static PropertyHelper CreateInstance(PropertyInfo property)
{
return new PropertyHelper(property);
@ -131,6 +172,14 @@ namespace Microsoft.AspNet.Mvc
return getter(ref unboxed);
}
private static void CallPropertySetter<TDeclaringType, TValue>(
Action<TDeclaringType, TValue> setter,
object target,
object value)
{
setter((TDeclaringType)target, (TValue)value);
}
protected static PropertyHelper[] GetProperties(
object instance,
Func<PropertyInfo, PropertyHelper> createPropertyHelper,

View File

@ -47,6 +47,7 @@
<Compile Include="ActionResults\RedirectToActionResult.cs" />
<Compile Include="ActionResults\RedirectToRouteResult.cs" />
<Compile Include="ActionResults\ViewResult.cs" />
<Compile Include="ActivateAttribute.cs" />
<Compile Include="AntiForgery\AntiForgery.cs" />
<Compile Include="AntiForgery\AntiForgeryOptions.cs" />
<Compile Include="AntiForgery\AntiForgeryToken.cs" />
@ -130,7 +131,9 @@
<Compile Include="IActionInvokerProvider.cs" />
<Compile Include="IActionResult.cs" />
<Compile Include="IActionSelector.cs" />
<Compile Include="IControllerActivator.cs" />
<Compile Include="IControllerAssemblyProvider.cs" />
<Compile Include="DefaultControllerActivator.cs" />
<Compile Include="IControllerFactory.cs" />
<Compile Include="Injector.cs" />
<Compile Include="Internal\PropertyHelper.cs" />

View File

@ -1018,6 +1018,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ActionResult_ActionReturnValueCannotBeNull"), p0);
}
/// <summary>
/// Value types cannot be activated by '{0}'.
/// </summary>
internal static string ValueTypesCannotBeActivated
{
get { return GetString("ValueTypesCannotBeActivated"); }
}
/// <summary>
/// Value types cannot be activated by '{0}'.
/// </summary>
internal static string FormatValueTypesCannotBeActivated(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValueTypesCannotBeActivated"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -8,20 +8,17 @@ namespace Microsoft.AspNet.Mvc
{
public class ReflectedActionInvokerProvider : IActionInvokerProvider
{
private readonly IServiceProvider _serviceProvider;
private readonly IControllerFactory _controllerFactory;
private readonly IActionBindingContextProvider _bindingProvider;
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
public ReflectedActionInvokerProvider(IControllerFactory controllerFactory,
IActionBindingContextProvider bindingProvider,
INestedProviderManager<FilterProviderContext> filterProvider,
IServiceProvider serviceProvider)
INestedProviderManager<FilterProviderContext> filterProvider)
{
_controllerFactory = controllerFactory;
_bindingProvider = bindingProvider;
_filterProvider = filterProvider;
_serviceProvider = serviceProvider;
}
public int Order

View File

@ -306,4 +306,7 @@
<data name="ActionResult_ActionReturnValueCannotBeNull" xml:space="preserve">
<value>Cannot return null from an action method with a return type of '{0}'.</value>
</data>
<data name="ValueTypesCannotBeActivated" xml:space="preserve">
<value>Value types cannot be activated by '{0}'.</value>
</data>
</root>

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Mvc
var describe = new ServiceDescriber(configuration);
yield return describe.Transient<IControllerFactory, DefaultControllerFactory>();
yield return describe.Singleton<IControllerActivator, DefaultControllerActivator>();
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
yield return describe.Transient<IActionInvokerFactory, ActionInvokerFactory>();
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();

View File

@ -0,0 +1,151 @@
// 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.
#if NET45
using System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test
{
public class DefaultControllerActivatorTest
{
[Fact]
public void Activate_SetsPropertiesFromActionContextHierarchy()
{
// Arrange
var httpRequest = Mock.Of<HttpRequest>();
var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.Request)
.Returns(httpRequest);
httpContext.SetupGet(c => c.RequestServices)
.Returns(Mock.Of<IServiceProvider>());
var routeContext = new RouteContext(httpContext.Object);
var controller = new TestController();
var context = new ActionContext(routeContext, new ActionDescriptor())
{
Controller = controller
};
var activator = new DefaultControllerActivator();
// Act
activator.Activate(controller, context);
// Assert
Assert.Same(context, controller.ActionContext);
Assert.Same(httpContext.Object, controller.HttpContext);
Assert.Same(httpRequest, controller.GetHttpRequest());
}
[Fact]
public void Activate_SetsViewDatDictionary()
{
// Arrange
var service = new Mock<IServiceProvider>();
service.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(Mock.Of<IModelMetadataProvider>());
var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.RequestServices)
.Returns(service.Object);
var routeContext = new RouteContext(httpContext.Object);
var controller = new TestController();
var context = new ActionContext(routeContext, new ActionDescriptor())
{
Controller = controller
};
var activator = new DefaultControllerActivator();
// Act
activator.Activate(controller, context);
// Assert
Assert.NotNull(controller.GetViewData());
}
[Fact]
public void Activate_PopulatesServicesFromServiceContainer()
{
// Arrange
var urlHelper = Mock.Of<IUrlHelper>();
var service = new Mock<IServiceProvider>();
service.Setup(s => s.GetService(typeof(IUrlHelper)))
.Returns(urlHelper);
var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.RequestServices)
.Returns(service.Object);
var routeContext = new RouteContext(httpContext.Object);
var controller = new TestController();
var context = new ActionContext(routeContext, new ActionDescriptor())
{
Controller = controller
};
var activator = new DefaultControllerActivator();
// Act
activator.Activate(controller, context);
// Assert
Assert.Same(urlHelper, controller.Helper);
}
[Fact]
public void Activate_IgnoresPropertiesThatAreNotDecoratedWithActivateAttribute()
{
// Arrange
var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.Response)
.Returns(Mock.Of<HttpResponse>());
httpContext.SetupGet(c => c.RequestServices)
.Returns(Mock.Of<IServiceProvider>());
var routeContext = new RouteContext(httpContext.Object);
var controller = new TestController();
var context = new ActionContext(routeContext, new ActionDescriptor())
{
Controller = controller
};
var activator = new DefaultControllerActivator();
// Act
activator.Activate(controller, context);
// Assert
Assert.Null(controller.Response);
}
public class TestController
{
[Activate]
public ActionContext ActionContext { get; set; }
[Activate]
public HttpContext HttpContext { get; set; }
[Activate]
protected HttpRequest Request { get; set; }
[Activate]
private ViewDataDictionary ViewData { get; set; }
[Activate]
public IUrlHelper Helper { get; set; }
public HttpResponse Response { get; set; }
public ViewDataDictionary GetViewData()
{
return ViewData;
}
public HttpRequest GetHttpRequest()
{
return Request;
}
}
}
}
#endif

View File

@ -15,8 +15,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
// Arrange
var factory = new DefaultControllerFactory(
new Mock<IServiceProvider>().Object,
new Mock<ITypeActivator>().Object);
Mock.Of<IServiceProvider>(),
Mock.Of<ITypeActivator>(),
Mock.Of<IControllerActivator>());
var controller = new MyController();
@ -33,8 +34,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
// Arrange
var factory = new DefaultControllerFactory(
new Mock<IServiceProvider>().Object,
new Mock<ITypeActivator>().Object);
Mock.Of<IServiceProvider>(),
Mock.Of<ITypeActivator>(),
Mock.Of<IControllerActivator>());
var controller = new Object();

View File

@ -38,6 +38,7 @@
<Compile Include="AntiXsrf\AntiForgeryTokenSerializerTest.cs" />
<Compile Include="AntiXsrf\ITokenProvider.cs" />
<Compile Include="AntiXsrf\ValidateAntiForgeryTokenAttributeTest.cs" />
<Compile Include="DefaultControllerActivatorTest.cs" />
<Compile Include="Filters\ActionFilterAttributeTests.cs" />
<Compile Include="Filters\AuthorizeAttributeTests.cs" />
<Compile Include="Filters\AuthorizeAttributeTestsBase.cs" />

View File

@ -186,10 +186,62 @@ namespace Microsoft.AspNet.Mvc
var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA"));
var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB"));
Assert.Equal("Overriden", propAHelper.GetValue(derived));
Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived));
Assert.Equal("propBValue", propBHelper.GetValue(derived));
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties()
{
// Arrange
var instance = new BaseClass();
var typeInfo = instance.GetType().GetTypeInfo();
var publicProperty = typeInfo.GetDeclaredProperty("PropA");
var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected");
var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty);
var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty);
// Act
publicPropertySetter(instance, "TestPublic");
protectedPropertySetter(instance, "TestProtected");
// Assert
Assert.Equal("TestPublic", instance.PropA);
Assert.Equal("TestProtected", instance.GetPropProtected());
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties()
{
// Arrange
var instance = new DerivedClassWithOverride();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("PropA");
var propertySetter = PropertyHelper.MakeFastPropertySetter(property);
// Act
propertySetter(instance, "Test value");
// Assert
Assert.Equal("OverridenTest value", instance.PropA);
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties()
{
// Arrange
var instance = new DerivedClassWithNew();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("PropB");
var propertySetter = PropertyHelper.MakeFastPropertySetter(property);
// Act
propertySetter(instance, "Test value");
// Assert
Assert.Equal("NewedTest value", instance.PropB);
}
private class Static
{
public static int Prop2 { get; set; }
@ -220,6 +272,11 @@ namespace Microsoft.AspNet.Mvc
public string PropA { get; set; }
protected string PropProtected { get; set; }
public string GetPropProtected()
{
return PropProtected;
}
}
public class DerivedClass : BaseClass
@ -235,12 +292,24 @@ namespace Microsoft.AspNet.Mvc
public class DerivedClassWithNew : BaseClassWithVirtual
{
public new string PropB { get { return "Newed"; } }
private string _value = "Newed";
public new string PropB
{
get { return _value; }
set { _value = "Newed" + value; }
}
}
public class DerivedClassWithOverride : BaseClassWithVirtual
{
public override string PropA { get { return "Overriden"; } }
private string _value = "Overriden";
public override string PropA
{
get { return _value; }
set { _value = "Overriden" + value; }
}
}
}
}

View File

@ -1297,7 +1297,7 @@ namespace Microsoft.AspNet.Mvc
routeData: new RouteData(),
actionDescriptor: actionDescriptor);
var controllerFactory = new Mock<IControllerFactory>(MockBehavior.Strict);
var controllerFactory = new Mock<IControllerFactory>();
controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>())).Returns(this);
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>(MockBehavior.Strict);

View File

@ -0,0 +1,65 @@
// 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;
using System.Threading.Tasks;
using ActivatorWebSite;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class ActivatorTests
{
private readonly IServiceProvider _provider = TestHelper.CreateServices("ActivatorWebSite");
private readonly Action<IBuilder> _app = new Startup().Configure;
[Fact]
public async Task ControllerThatCannotBeActivated_ThrowsWhenAttemptedToBeInvoked()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.Handler;
var expectedMessage = "TODO: No service for type 'ActivatorWebSite.CannotBeActivatedController+FakeType' " +
"has been registered.";
// Act & Assert
var ex = await Assert.ThrowsAsync<Exception>(() => client.GetAsync("http://localhost/CannotBeActivated/Index"));
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
public async Task PropertiesForPocoControllersAreInitialized()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.Handler;
var expected = "4|some-text";
// Act
var result = await client.GetAsync("http://localhost/Plain?foo=some-text");
// Assert
Assert.Equal("Fake-Value", result.HttpContext.Response.Headers["X-Fake-Header"]);
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
Assert.Equal(expected, body);
}
[Fact]
public async Task PropertiesForTypesDerivingFromControllerAreInitialized()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.Handler;
var expected = "Hello world";
// Act
var result = await client.GetAsync("http://localhost/Regular");
// Assert
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
Assert.Equal(expected, body);
}
}
}

View File

@ -30,6 +30,7 @@
<Content Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="ActivatorTests.cs" />
<Compile Include="InlineConstraintTests.cs" />
<Compile Include="TestHelper.cs" />
<Compile Include="BasicTests.cs" />

View File

@ -4,6 +4,7 @@
},
"dependencies": {
"BasicWebSite": "",
"ActivatorWebSite": "",
"InlineConstraintsWebSite": "",
"Microsoft.AspNet.TestHost": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>db79bcba-9538-4a53-87d9-77728e2baa39</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controllers\CannotBeActivatedController.cs" />
<Compile Include="Controllers\PlainController.cs" />
<Compile Include="Controllers\RegularController.cs" />
<Compile Include="Services\MyService.cs" />
<Compile Include="Startup.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,22 @@
// 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 Microsoft.AspNet.Mvc;
namespace ActivatorWebSite
{
public class CannotBeActivatedController
{
[Activate]
private FakeType Service { get; set; }
public IActionResult Index()
{
return new NoContentResult();
}
private sealed class FakeType
{
}
}
}

View File

@ -0,0 +1,28 @@
// 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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
namespace ActivatorWebSite
{
public class PlainController
{
[Activate]
public MyService Service { get; set; }
[Activate]
public HttpRequest Request { get; set; }
[Activate]
public HttpResponse Response { get; set; }
public IActionResult Index()
{
Response.Headers["X-Fake-Header"] = "Fake-Value";
var value = Request.Query["foo"];
return new ContentResult { Content = Service.Random + "|" + value };
}
}
}

View File

@ -0,0 +1,19 @@
// 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 Microsoft.AspNet.Mvc;
namespace ActivatorWebSite
{
public class RegularController : Controller
{
public void Index()
{
// This verifies that ModelState and Context are activated.
if (ModelState.IsValid)
{
Context.Response.WriteAsync("Hello world").Wait();
}
}
}
}

View File

@ -0,0 +1,15 @@
// 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;
namespace ActivatorWebSite
{
public class MyService
{
public int Random
{
get { return 4; }
}
}
}

View File

@ -0,0 +1,30 @@
// 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 Microsoft.AspNet.Builder;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
namespace ActivatorWebSite
{
public class Startup
{
public void Configure(IBuilder app)
{
// Set up application services
app.UseServices(services =>
{
// Add MVC services to the services container
services.AddMvc();
services.AddInstance(new MyService());
});
// Add MVC to the request pipeline
app.UseMvc(routes =>
{
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
});
}
}
}

View File

@ -0,0 +1,10 @@
{
"dependencies": {
"Helios": "0.1-alpha-*",
"Microsoft.AspNet.Mvc": ""
},
"configurations": {
"net45": { },
"k10": { }
}
}