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.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ViewComponents
{
/// <summary>
/// Represents the <see cref="IViewComponentActivator"/> that is registered by default.
/// A default implementation of <see cref="IViewComponentActivator"/>.
/// </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
{
private readonly Func<Type, PropertyActivator<ViewContext>[]> _getPropertiesToActivate;
private readonly IDictionary<Type, Func<ViewContext, object>> _valueAccessorLookup;
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]> _injectActions;
private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;
/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentActivator"/> class.
/// </summary>
public DefaultViewComponentActivator()
{
_valueAccessorLookup = CreateValueAccessorLookup();
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]>();
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
_getPropertiesToActivate = type =>
PropertyActivator<ViewContext>.GetPropertiesToActivate(
type, typeof(ActivateAttribute), CreateActivateInfo);
PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
type,
typeof(ViewComponentContextAttribute),
CreateActivateInfo);
}
/// <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(),
_getPropertiesToActivate);
var propertiesToActivate = _injectActions.GetOrAdd(
viewComponent.GetType(),
_getPropertiesToActivate);
for (var i = 0; i < propertiesToActivate.Length; i++)
{
@ -45,44 +48,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
}
}
/// <summary>
/// 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()
private PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
{
return new Dictionary<Type, Func<ViewContext, object>>
{
{ 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);
return new PropertyActivator<ViewComponentContext>(property, context => context);
}
}
}

View File

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

View File

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

View File

@ -12,6 +12,19 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
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>
/// Creates a new <see cref="ViewComponentContext"/>.
/// </summary>
@ -29,32 +42,54 @@ namespace Microsoft.AspNet.Mvc
{
ViewComponentDescriptor = viewComponentDescriptor;
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>
/// Gets 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.
/// Gets or sets the View Component arguments.
/// </summary>
/// <remarks>
/// <see cref="IViewComponentHelper.Invoke(string, object[])"/> or a similar overload is used to invoke the
/// View Component, then <see cref="Writer"/> will be different than <see cref="ViewContext.Writer"/>.
/// The property setter is provided for unit test purposes only.
/// </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)
{
var viewEngine = ViewEngine ?? ResolveViewEngine(context);
var viewData = ViewData ?? context.ViewContext.ViewData;
var viewData = ViewData ?? context.ViewData;
string qualifiedViewName;
if (ViewName != null && ViewName.Length > 0 && ViewName[0] == '/')
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc
var childViewContext = new ViewContext(
context.ViewContext,
view,
ViewData ?? context.ViewContext.ViewData,
ViewData ?? context.ViewData,
context.Writer);
using (view as IDisposable)

View File

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

View File

@ -17,135 +17,28 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public class DefaultViewComponentActivatorTests
{
[Fact]
public void DefaultViewComponentActivatorSetsAllPropertiesMarkedAsActivate()
public void DefaultViewComponentActivator_ActivatesViewComponentContext()
{
// Arrange
var activator = new DefaultViewComponentActivator();
var context = new ViewComponentContext();
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
activator.Activate(instance, viewContext);
activator.Activate(instance, context);
// Assert
Assert.Same(helper, instance.Html);
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());
Assert.Same(context, instance.ViewComponentContext);
}
private class TestViewComponent : ViewComponent
{
[Activate]
public IHtmlHelper<object> Html { get; private set; }
public Task ExecuteAsync()
{
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

View File

@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task ViewComponentActivator_ActivatesProperties()
public async Task ViewComponentActivator_Activates()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -127,35 +127,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
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]
public async Task ViewComponentThatCannotBeActivated_ThrowsWhenAttemptedToBeInvoked()
@ -163,8 +134,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var expectedMessage = "No service for type 'ActivatorWebSite.CannotBeActivatedComponent+FakeType' " +
"has been registered.";
var expectedMessage =
"Unable to resolve service for type 'ActivatorWebSite.CannotBeActivatedComponent+FakeType' " +
"while attempting to activate 'ActivatorWebSite.CannotBeActivatedComponent'.";
// Act & Assert
var response = await client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent");

View File

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

View File

@ -8,8 +8,12 @@ namespace ActivatorWebSite
[ViewComponent(Name = "Number")]
public class NumberComponent : ViewComponent
{
[Activate]
public MyService MyTestService { get; set; }
public NumberComponent(MyService myTestService)
{
MyTestService = myTestService;
}
private MyService MyTestService { get; }
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();
}
public ViewResult ConsumeValueComponent()
{
return View();
}
public ViewResult ConsumeViewAndValueComponent()
{
return View();
}
public ViewResult ConsumeCannotBeActivatedComponent()
{
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
{
[Activate]
public ProductsService ProductsService { get; set; }
public ProductsViewComponent(ProductsService productsService, IMemoryCache cache)
{
ProductsService = productsService;
Cache = cache;
}
[Activate]
public IMemoryCache Cache { get; set; }
private ProductsService ProductsService { get; }
public IMemoryCache Cache { get; }
public IViewComponentResult Invoke(string category)
{

View File

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