Part of #2151 - Remove [Activate] from ViewComponents

This change removes [Activate] from ViewComponents. Accessing context
should be done through [ViewComponentContext]. Accessing services should
be done though constructor injection.
This commit is contained in:
Ryan Nowak 2015-05-20 20:19:45 -07:00
parent 92dbd8923b
commit b393191cff
19 changed files with 207 additions and 342 deletions

View File

@ -3,40 +3,43 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary> /// <summary>
/// Represents the <see cref="IViewComponentActivator"/> that is registered by default. /// A default implementation of <see cref="IViewComponentActivator"/>.
/// </summary> /// </summary>
/// <remarks>
/// The <see cref="DefaultViewComponentActivator"/> can provide the current instance of
/// <see cref="ViewComponentContext"/> to a public property of a view component marked
/// with <see cref="ViewComponentContextAttribute"/>.
/// </remarks>
public class DefaultViewComponentActivator : IViewComponentActivator public class DefaultViewComponentActivator : IViewComponentActivator
{ {
private readonly Func<Type, PropertyActivator<ViewContext>[]> _getPropertiesToActivate; private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
private readonly IDictionary<Type, Func<ViewContext, object>> _valueAccessorLookup; private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]> _injectActions;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentActivator"/> class. /// Initializes a new instance of <see cref="DefaultViewComponentActivator"/> class.
/// </summary> /// </summary>
public DefaultViewComponentActivator() public DefaultViewComponentActivator()
{ {
_valueAccessorLookup = CreateValueAccessorLookup(); _injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]>();
_getPropertiesToActivate = type => _getPropertiesToActivate = type =>
PropertyActivator<ViewContext>.GetPropertiesToActivate( PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
type, typeof(ActivateAttribute), CreateActivateInfo); type,
typeof(ViewComponentContextAttribute),
CreateActivateInfo);
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual void Activate([NotNull] object viewComponent, [NotNull] ViewContext context) public virtual void Activate([NotNull] object viewComponent, [NotNull] ViewComponentContext context)
{ {
var propertiesToActivate = _injectActions.GetOrAdd(viewComponent.GetType(), var propertiesToActivate = _injectActions.GetOrAdd(
_getPropertiesToActivate); viewComponent.GetType(),
_getPropertiesToActivate);
for (var i = 0; i < propertiesToActivate.Length; i++) for (var i = 0; i < propertiesToActivate.Length; i++)
{ {
@ -45,44 +48,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
} }
} }
/// <summary> private PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
/// Creates a lookup dictionary for the values to be activated.
/// </summary>
/// <returns>Returns a readonly dictionary of the values corresponding to the types.</returns>
protected virtual IDictionary<Type, Func<ViewContext, object>> CreateValueAccessorLookup()
{ {
return new Dictionary<Type, Func<ViewContext, object>> return new PropertyActivator<ViewComponentContext>(property, context => context);
{
{ typeof(ViewContext), (context) => context },
{
typeof(ViewDataDictionary),
(context) =>
{
return new ViewDataDictionary(context.ViewData);
}
}
};
}
private PropertyActivator<ViewContext> CreateActivateInfo(PropertyInfo property)
{
Func<ViewContext, object> valueAccessor;
if (!_valueAccessorLookup.TryGetValue(property.PropertyType, out valueAccessor))
{
valueAccessor = (viewContext) =>
{
var serviceProvider = viewContext.HttpContext.RequestServices;
var service = serviceProvider.GetRequiredService(property.PropertyType);
if (typeof(ICanHasViewContext).IsAssignableFrom(property.PropertyType))
{
((ICanHasViewContext)service).Contextualize(viewContext);
}
return service;
};
}
return new PropertyActivator<ViewContext>(property, valueAccessor);
} }
} }
} }

View File

@ -80,7 +80,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var component = _typeActivatorCache.CreateInstance<object>( var component = _typeActivatorCache.CreateInstance<object>(
_serviceProvider, _serviceProvider,
context.ViewComponentDescriptor.Type); context.ViewComponentDescriptor.Type);
_viewComponentActivator.Activate(component, context.ViewContext); _viewComponentActivator.Activate(component, context);
return component; return component;
} }

View File

@ -12,7 +12,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
/// When implemented in a type, activates an instantiated ViewComponent. /// When implemented in a type, activates an instantiated ViewComponent.
/// </summary> /// </summary>
/// <param name="viewComponent">The ViewComponent to activate.</param> /// <param name="viewComponent">The ViewComponent to activate.</param>
/// <param name="context">The <see cref="ViewContext"/> for the executing <see cref="ViewComponent"/>.</param> /// <param name="context">
void Activate(object viewComponent, ViewContext context); /// The <see cref="ViewComponentContext"/> for the executing <see cref="ViewComponent"/>.
/// </param>
void Activate(object viewComponent, ViewComponentContext context);
} }
} }

View File

@ -6,7 +6,9 @@ using System.Security.Principal;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewComponents;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -18,9 +20,10 @@ namespace Microsoft.AspNet.Mvc
[ViewComponent] [ViewComponent]
public abstract class ViewComponent public abstract class ViewComponent
{ {
private IUrlHelper _url;
private dynamic _viewBag; private dynamic _viewBag;
private ViewContext _viewContext; private ViewComponentContext _viewComponentContext;
private ViewDataDictionary _viewData; private ICompositeViewEngine _viewEngine;
/// <summary> /// <summary>
/// Gets the <see cref="HttpContext"/>. /// Gets the <see cref="HttpContext"/>.
@ -96,62 +99,93 @@ namespace Microsoft.AspNet.Mvc
/// <summary> /// <summary>
/// Gets or sets the <see cref="IUrlHelper"/>. /// Gets or sets the <see cref="IUrlHelper"/>.
/// </summary> /// </summary>
[Activate] public IUrlHelper Url
public IUrlHelper Url { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewContext"/>.
/// </summary>
[Activate]
public ViewContext ViewContext
{ {
get get
{ {
if (_viewContext == null) if (_url == null)
{ {
// This should run only for the ViewComponent unit test scenarios // May be null in unit-testing scenarios.
_viewContext = new ViewContext(); var services = ViewComponentContext.ViewContext?.HttpContext?.RequestServices;
_url = services?.GetRequiredService<IUrlHelper>();
} }
return _viewContext; return _url;
} }
[param: NotNull] [param: NotNull]
set set
{ {
_viewContext = value; _url = value;
}
}
[ViewComponentContext]
public ViewComponentContext ViewComponentContext
{
get
{
// This should run only for the ViewComponent unit test scenarios.
if (_viewComponentContext == null)
{
_viewComponentContext = new ViewComponentContext();
}
return _viewComponentContext;
}
[param: NotNull]
set
{
_viewComponentContext = value;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ViewDataDictionary"/>. /// Gets the <see cref="ViewContext"/>.
/// </summary>
public ViewContext ViewContext
{
get
{
return ViewComponentContext.ViewContext;
}
}
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary> /// </summary>
[Activate]
public ViewDataDictionary ViewData public ViewDataDictionary ViewData
{ {
get get
{ {
if (_viewData == null) return ViewComponentContext.ViewData;
{
// This should run only for the ViewComponent unit test scenarios
_viewData = new ViewDataDictionary(ViewContext.ViewData);
}
return _viewData;
}
[param: NotNull]
set
{
_viewData = value;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ICompositeViewEngine"/>. /// Gets or sets the <see cref="ICompositeViewEngine"/>.
/// </summary> /// </summary>
[Activate] public ICompositeViewEngine ViewEngine
public ICompositeViewEngine ViewEngine { get; set; } {
get
{
if (_viewEngine == null)
{
// May be null in unit-testing scenarios.
var services = ViewComponentContext.ViewContext?.HttpContext?.RequestServices;
_viewEngine = services?.GetRequiredService<ICompositeViewEngine>();
}
return _viewEngine;
}
[param: NotNull]
set
{
_viewEngine = value;
}
}
/// <summary> /// <summary>
/// Returns a result which will render HTML encoded text. /// Returns a result which will render HTML encoded text.

View File

@ -12,6 +12,19 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public class ViewComponentContext public class ViewComponentContext
{ {
/// <summary>
/// Creates a new <see cref="ViewComponentContext"/>.
/// </summary>
/// <remarks>
/// The default constructor is provided for unit test purposes only.
/// </remarks>
public ViewComponentContext()
{
ViewComponentDescriptor = new ViewComponentDescriptor();
Arguments = new object[0];
ViewContext = new ViewContext();
}
/// <summary> /// <summary>
/// Creates a new <see cref="ViewComponentContext"/>. /// Creates a new <see cref="ViewComponentContext"/>.
/// </summary> /// </summary>
@ -29,32 +42,54 @@ namespace Microsoft.AspNet.Mvc
{ {
ViewComponentDescriptor = viewComponentDescriptor; ViewComponentDescriptor = viewComponentDescriptor;
Arguments = arguments; Arguments = arguments;
ViewContext = viewContext;
Writer = writer; // We want to create a defensive copy of the VDD here so that changes done in the VC
// aren't visible in the calling view.
ViewContext = new ViewContext(
viewContext,
viewContext.View,
new ViewDataDictionary(viewContext.ViewData),
writer);
} }
/// <summary> /// <summary>
/// Gets the View Component arguments. /// Gets or sets the View Component arguments.
/// </summary>
public object[] Arguments { get; }
/// <summary>
/// Gets the <see cref="ViewComponentDescriptor"/> for the View Component being invoked.
/// </summary>
public ViewComponentDescriptor ViewComponentDescriptor { get; }
/// <summary>
/// Gets the <see cref="ViewContext"/>.
/// </summary>
public ViewContext ViewContext { get; }
/// <summary>
/// Gets the <see cref="TextWriter"/> for writing output.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <see cref="IViewComponentHelper.Invoke(string, object[])"/> or a similar overload is used to invoke the /// The property setter is provided for unit test purposes only.
/// View Component, then <see cref="Writer"/> will be different than <see cref="ViewContext.Writer"/>.
/// </remarks> /// </remarks>
public TextWriter Writer { get; } public object[] Arguments { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewComponentDescriptor"/> for the View Component being invoked.
/// </summary>
/// <remarks>
/// The property setter is provided for unit test purposes only.
/// </remarks>
public ViewComponentDescriptor ViewComponentDescriptor { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewContext"/>.
/// </summary>
/// <remarks>
/// The property setter is provided for unit test purposes only.
/// </remarks>
public ViewContext ViewContext { get; set; }
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary>
/// <remarks>
/// This is an alias for <c>ViewContext.ViewData</c>.
/// </remarks>
public ViewDataDictionary ViewData => ViewContext.ViewData;
/// <summary>
/// Gets the <see cref="TextWriter"/> for output.
/// </summary>
/// <remarks>
/// This is an alias for <c>ViewContext.Writer</c>.
/// </remarks>
public TextWriter Writer => ViewContext.Writer;
} }
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. 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.ViewComponents
{
/// <summary>
/// Specifies that a controller property should be set with the current
/// <see cref="ViewComponentContext"/> when creating the view component. The property must have a public
/// set method.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ViewComponentContextAttribute : Attribute
{
}
}

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
public async Task ExecuteAsync([NotNull] ViewComponentContext context) public async Task ExecuteAsync([NotNull] ViewComponentContext context)
{ {
var viewEngine = ViewEngine ?? ResolveViewEngine(context); var viewEngine = ViewEngine ?? ResolveViewEngine(context);
var viewData = ViewData ?? context.ViewContext.ViewData; var viewData = ViewData ?? context.ViewData;
string qualifiedViewName; string qualifiedViewName;
if (ViewName != null && ViewName.Length > 0 && ViewName[0] == '/') if (ViewName != null && ViewName.Length > 0 && ViewName[0] == '/')
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc
var childViewContext = new ViewContext( var childViewContext = new ViewContext(
context.ViewContext, context.ViewContext,
view, view,
ViewData ?? context.ViewContext.ViewData, ViewData ?? context.ViewData,
context.Writer); context.Writer);
using (view as IDisposable) using (view as IDisposable)

View File

@ -14,10 +14,7 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_ViewBag_UsesViewData() public void ViewComponent_ViewBag_UsesViewData()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider()),
};
// Act // Act
viewComponent.ViewBag.A = "Alice"; viewComponent.ViewBag.A = "Alice";
@ -33,10 +30,7 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_ViewData_StoresDataForViewBag() public void ViewComponent_ViewData_StoresDataForViewBag()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider()),
};
// Act // Act
viewComponent.ViewData["A"] = "Alice"; viewComponent.ViewData["A"] = "Alice";
@ -84,10 +78,7 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_View_WithEmptyParameter_SetsResultViewWithDefaultViewName() public void ViewComponent_View_WithEmptyParameter_SetsResultViewWithDefaultViewName()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
// Act // Act
var actualResult = viewComponent.View(); var actualResult = viewComponent.View();
@ -104,10 +95,7 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_View_WithViewNameParameter_SetsResultViewWithCustomViewName() public void ViewComponent_View_WithViewNameParameter_SetsResultViewWithCustomViewName()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
// Act // Act
var actualResult = viewComponent.View("CustomViewName"); var actualResult = viewComponent.View("CustomViewName");
@ -125,10 +113,8 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_View_WithModelParameter_SetsResultViewWithDefaultViewNameAndModel() public void ViewComponent_View_WithModelParameter_SetsResultViewWithDefaultViewNameAndModel()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
var model = new object(); var model = new object();
// Act // Act
@ -147,10 +133,8 @@ namespace Microsoft.AspNet.Mvc
public void ViewComponent_View_WithViewNameAndModelParameters_SetsResultViewWithCustomViewNameAndModel() public void ViewComponent_View_WithViewNameAndModelParameters_SetsResultViewWithCustomViewNameAndModel()
{ {
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent();
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
var model = new object(); var model = new object();
// Act // Act
@ -180,7 +164,7 @@ namespace Microsoft.AspNet.Mvc
Assert.Empty(viewComponent.ViewContext.ViewData); Assert.Empty(viewComponent.ViewContext.ViewData);
Assert.NotNull(viewComponent.ViewData); Assert.NotNull(viewComponent.ViewData);
Assert.Empty(viewComponent.ViewData); Assert.Empty(viewComponent.ViewData);
Assert.NotSame(viewComponent.ViewData, viewComponent.ViewContext.ViewData); Assert.Same(viewComponent.ViewData, viewComponent.ViewContext.ViewData);
} }
private class TestViewComponent : ViewComponent private class TestViewComponent : ViewComponent

View File

@ -17,135 +17,28 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public class DefaultViewComponentActivatorTests public class DefaultViewComponentActivatorTests
{ {
[Fact] [Fact]
public void DefaultViewComponentActivatorSetsAllPropertiesMarkedAsActivate() public void DefaultViewComponentActivator_ActivatesViewComponentContext()
{ {
// Arrange // Arrange
var activator = new DefaultViewComponentActivator(); var activator = new DefaultViewComponentActivator();
var context = new ViewComponentContext();
var instance = new TestViewComponent(); var instance = new TestViewComponent();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper<object>))).Returns(helper);
serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of<ICompositeViewEngine>());
serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of<IUrlHelper>());
var viewContext = GetViewContext(serviceProvider.Object);
// Act // Act
activator.Activate(instance, viewContext); activator.Activate(instance, context);
// Assert // Assert
Assert.Same(helper, instance.Html); Assert.Same(context, instance.ViewComponentContext);
Assert.Same(viewContext, instance.ViewContext);
Assert.IsType<ViewDataDictionary>(instance.ViewData);
}
[Fact]
public void DefaultViewComponentActivatorSetsModelAsNull()
{
// Arrange
var activator = new DefaultViewComponentActivator();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper<object>))).Returns(helper);
serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of<ICompositeViewEngine>());
serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of<IUrlHelper>());
var viewContext = GetViewContext(serviceProvider.Object);
// Act
activator.Activate(new TestViewComponent(), viewContext);
// Assert
Assert.Null(viewContext.ViewData.Model);
}
[Fact]
public void DefaultViewComponentActivatorActivatesNonBuiltInTypes()
{
// Arrange
var activator = new DefaultViewComponentActivator();
var helper = Mock.Of<IHtmlHelper<object>>();
var myTestService = new MyService();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper<object>))).Returns(helper);
serviceProvider.Setup(p => p.GetService(typeof(MyService))).Returns(myTestService);
serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of<ICompositeViewEngine>());
serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of<IUrlHelper>());
var viewContext = GetViewContext(serviceProvider.Object);
var instance = new TestViewComponentWithCustomDataType();
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.Equal(myTestService, instance.TestMyServiceObject);
}
[Fact]
public void DefaulViewComponentActivatorContextualizesService()
{
// Arrange
var activator = new DefaultViewComponentActivator();
var instance = new TestClassUsingMyService();
var myTestService = new MyService();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(p => p.GetService(typeof(MyService))).Returns(myTestService);
var viewContext = GetViewContext(serviceProvider.Object);
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.Same(myTestService, instance.MyTestService);
Assert.Same(viewContext, instance.MyTestService.ViewContext);
}
private ViewContext GetViewContext(IServiceProvider serviceProvider)
{
var httpContext = new Mock<DefaultHttpContext>();
httpContext.SetupGet(c => c.RequestServices)
.Returns(serviceProvider);
var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
return new ViewContext(actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
null,
TextWriter.Null,
new HtmlHelperOptions());
} }
private class TestViewComponent : ViewComponent private class TestViewComponent : ViewComponent
{ {
[Activate]
public IHtmlHelper<object> Html { get; private set; }
public Task ExecuteAsync() public Task ExecuteAsync()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
private class TestViewComponentWithCustomDataType : TestViewComponent
{
[Activate]
public MyService TestMyServiceObject { get; set; }
}
private class MyService : ICanHasViewContext
{
public ViewContext ViewContext { get; private set; }
public void Contextualize(ViewContext viewContext)
{
ViewContext = viewContext;
}
}
private class TestClassUsingMyService
{
[Activate]
public MyService MyTestService { get; set; }
}
} }
} }
#endif #endif

View File

@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task ViewComponentActivator_ActivatesProperties() public async Task ViewComponentActivator_Activates()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -127,35 +127,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(expected, body.Trim()); Assert.Equal(expected, body.Trim());
} }
[Fact]
public async Task ViewComponentActivator_ActivatesPropertiesAndContextualizesThem()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var expected = "test-value";
// Act
var body = await client.GetStringAsync("http://localhost/View/ConsumeValueComponent?test=test-value");
// Assert
Assert.Equal(expected, body.Trim());
}
[Fact]
public async Task ViewComponentActivator_ActivatesPropertiesAndContextualizesThem_WhenMultiplePropertiesArePresent()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var expected = "Random Number:4 test-value";
// Act
var body = await client.GetStringAsync("http://localhost/View/ConsumeViewAndValueComponent?test=test-value");
// Assert
Assert.Equal(expected, body.Trim());
}
[Fact] [Fact]
public async Task ViewComponentThatCannotBeActivated_ThrowsWhenAttemptedToBeInvoked() public async Task ViewComponentThatCannotBeActivated_ThrowsWhenAttemptedToBeInvoked()
@ -163,8 +134,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient(); var client = server.CreateClient();
var expectedMessage = "No service for type 'ActivatorWebSite.CannotBeActivatedComponent+FakeType' " + var expectedMessage =
"has been registered."; "Unable to resolve service for type 'ActivatorWebSite.CannotBeActivatedComponent+FakeType' " +
"while attempting to activate 'ActivatorWebSite.CannotBeActivatedComponent'.";
// Act & Assert // Act & Assert
var response = await client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent"); var response = await client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent");

View File

@ -8,15 +8,16 @@ namespace ActivatorWebSite
[ViewComponent(Name = "CannotBeActivated")] [ViewComponent(Name = "CannotBeActivated")]
public class CannotBeActivatedComponent : ViewComponent public class CannotBeActivatedComponent : ViewComponent
{ {
[Activate] public CannotBeActivatedComponent(FakeType fakeType)
private FakeType Service { get; set; } {
}
public IViewComponentResult Invoke() public IViewComponentResult Invoke()
{ {
return Content("Test"); return Content("Test");
} }
private sealed class FakeType public sealed class FakeType
{ {
} }
} }

View File

@ -8,8 +8,12 @@ namespace ActivatorWebSite
[ViewComponent(Name = "Number")] [ViewComponent(Name = "Number")]
public class NumberComponent : ViewComponent public class NumberComponent : ViewComponent
{ {
[Activate] public NumberComponent(MyService myTestService)
public MyService MyTestService { get; set; } {
MyTestService = myTestService;
}
private MyService MyTestService { get; }
public IViewComponentResult Invoke(string content) public IViewComponentResult Invoke(string content)
{ {

View File

@ -1,19 +0,0 @@
// Copyright (c) .NET Foundation. 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
{
[ViewComponent(Name = "Value")]
public class ValueComponent : ViewComponent
{
[Activate]
public ViewService MyViewService { get; set; }
public IViewComponentResult Invoke()
{
return Content(MyViewService.GetValue());
}
}
}

View File

@ -1,22 +0,0 @@
// Copyright (c) .NET Foundation. 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
{
[ViewComponent(Name = "ViewAndValue")]
public class ViewAndValueComponent : ViewComponent
{
[Activate]
public MyService MySampleService { get; set; }
[Activate]
public ViewService MyViewService { get; set; }
public IViewComponentResult Invoke(string content)
{
return Content(content + ":" + MySampleService.Random + " " + MyViewService.GetValue());
}
}
}

View File

@ -31,16 +31,6 @@ namespace ActivatorWebSite
return View(); return View();
} }
public ViewResult ConsumeValueComponent()
{
return View();
}
public ViewResult ConsumeViewAndValueComponent()
{
return View();
}
public ViewResult ConsumeCannotBeActivatedComponent() public ViewResult ConsumeCannotBeActivatedComponent()
{ {
return View(); return View();

View File

@ -1 +0,0 @@
@Component.Invoke("Value")

View File

@ -1 +0,0 @@
@Component.Invoke("ViewAndValue", "Random Number")

View File

@ -9,11 +9,15 @@ namespace MvcTagHelpersWebSite.Components
{ {
public class ProductsViewComponent : ViewComponent public class ProductsViewComponent : ViewComponent
{ {
[Activate] public ProductsViewComponent(ProductsService productsService, IMemoryCache cache)
public ProductsService ProductsService { get; set; } {
ProductsService = productsService;
Cache = cache;
}
[Activate] private ProductsService ProductsService { get; }
public IMemoryCache Cache { get; set; }
public IMemoryCache Cache { get; }
public IViewComponentResult Invoke(string category) public IViewComponentResult Invoke(string category)
{ {

View File

@ -7,8 +7,12 @@ namespace RequestServicesWebSite
{ {
public class RequestIdViewComponent : ViewComponent public class RequestIdViewComponent : ViewComponent
{ {
[Activate] public RequestIdViewComponent(RequestIdService requestIdService)
public RequestIdService RequestIdService { get; set; } {
RequestIdService = requestIdService;
}
private RequestIdService RequestIdService { get; }
public IViewComponentResult Invoke() public IViewComponentResult Invoke()
{ {