Introducing RazorViewFactory for RazorView
This commit is contained in:
parent
7e8870cb9f
commit
e9c7a34cfc
|
|
@ -6,17 +6,16 @@ using Microsoft.AspNet.Mvc.Rendering;
|
|||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the contract for an <see cref="IView"/> that executes <see cref="IRazorPage"/> as part of its
|
||||
/// execution.
|
||||
/// Defines methods to create <see cref="RazorView"/> instances with a given <see cref="IRazorPage"/>.
|
||||
/// </summary>
|
||||
public interface IRazorView : IView
|
||||
public interface IRazorViewFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Contextualizes the current instance of the <see cref="IRazorView"/> providing it with the
|
||||
/// <see cref="IRazorPage"/> to execute.
|
||||
/// Creates a <see cref="RazorView"/> providing it with the <see cref="IRazorPage"/> to execute.
|
||||
/// </summary>
|
||||
/// <param name="razorPage">The <see cref="IRazorPage"/> instance to execute.</param>
|
||||
/// <param name="isPartial">Determines if the view is to be executed as a partial.</param>
|
||||
void Contextualize(IRazorPage razorPage, bool isPartial);
|
||||
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
|
||||
IView GetView([NotNull] IRazorPage page, bool isPartial);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation for <see cref="IRazorView"/> that executes one or more <see cref="RazorPage"/>
|
||||
/// Default implementation for <see cref="IView"/> that executes one or more <see cref="IRazorPage"/>
|
||||
/// instances as part of view rendering.
|
||||
/// </summary>
|
||||
public class RazorView : IRazorView
|
||||
public class RazorView : IView
|
||||
{
|
||||
private readonly IRazorPageFactory _pageFactory;
|
||||
private readonly IRazorPageActivator _pageActivator;
|
||||
|
|
@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Initializes a new instance of RazorView
|
||||
/// </summary>
|
||||
/// <param name="pageFactory">The view factory used to instantiate layout and _ViewStart pages.</param>
|
||||
/// <param name="pageFactory">The page factory used to instantiate layout and _ViewStart pages.</param>
|
||||
/// <param name="pageActivator">The <see cref="IRazorPageActivator"/> used to activate pages.</param>
|
||||
/// <param name="viewStartProvider">The <see cref="IViewStartProvider"/> used for discovery of _ViewStart
|
||||
/// pages</param>
|
||||
|
|
@ -42,7 +43,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
get { return _pageExecutionFeature != null; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Contextualizes the current instance of the <see cref="RazorView"/> providing it with the
|
||||
/// <see cref="IRazorPage"/> to execute.
|
||||
/// </summary>
|
||||
/// <param name="razorPage">The <see cref="IRazorPage"/> instance to execute.</param>
|
||||
/// <param name="isPartial">Determines if the view is to be executed as a partial.</param>
|
||||
public virtual void Contextualize([NotNull] IRazorPage razorPage,
|
||||
bool isPartial)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
};
|
||||
|
||||
private readonly IRazorPageFactory _pageFactory;
|
||||
private readonly IRazorViewFactory _pageViewFactory;
|
||||
private readonly IReadOnlyList<IViewLocationExpander> _viewLocationExpanders;
|
||||
private readonly IViewLocationCache _viewLocationCache;
|
||||
|
||||
|
|
@ -42,10 +43,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// </summary>
|
||||
/// <param name="pageFactory">The page factory used for creating <see cref="IRazorPage"/> instances.</param>
|
||||
public RazorViewEngine(IRazorPageFactory pageFactory,
|
||||
IRazorViewFactory pageViewFactory,
|
||||
IViewLocationExpanderProvider viewLocationExpanderProvider,
|
||||
IViewLocationCache viewLocationCache)
|
||||
{
|
||||
_pageFactory = pageFactory;
|
||||
_pageViewFactory = pageViewFactory;
|
||||
_viewLocationExpanders = viewLocationExpanderProvider.ViewLocationExpanders;
|
||||
_viewLocationCache = viewLocationCache;
|
||||
}
|
||||
|
|
@ -193,9 +196,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
// and might store state. We'll use the service container to create new instances as we require.
|
||||
|
||||
var services = actionContext.HttpContext.RequestServices;
|
||||
var view = services.GetRequiredService<IRazorView>();
|
||||
|
||||
view.Contextualize(page, partial);
|
||||
var view = _pageViewFactory.GetView(page, partial);
|
||||
|
||||
return ViewEngineResult.Found(viewName, view);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
// 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.Rendering;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorViewFactory : IRazorViewFactory
|
||||
{
|
||||
private readonly IRazorPageActivator _pageActivator;
|
||||
private readonly IRazorPageFactory _pageFactory;
|
||||
private readonly IViewStartProvider _viewStartProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of RazorViewFactory
|
||||
/// </summary>
|
||||
/// <param name="pageFactory">The page factory used to instantiate layout and _ViewStart pages.</param>
|
||||
/// <param name="pageActivator">The <see cref="IRazorPageActivator"/> used to activate pages.</param>
|
||||
/// <param name="viewStartProvider">The <see cref="IViewStartProvider"/> used for discovery of _ViewStart
|
||||
/// pages</param>
|
||||
public RazorViewFactory(IRazorPageFactory pageFactory,
|
||||
IRazorPageActivator pageActivator,
|
||||
IViewStartProvider viewStartProvider)
|
||||
{
|
||||
_pageFactory = pageFactory;
|
||||
_pageActivator = pageActivator;
|
||||
_viewStartProvider = viewStartProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IView GetView([NotNull] IRazorPage page, bool isPartial)
|
||||
{
|
||||
var razorView = new RazorView(_pageFactory, _pageActivator, _viewStartProvider);
|
||||
|
||||
razorView.Contextualize(page, isPartial);
|
||||
|
||||
return razorView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
// The ViewStartProvider needs to be able to consume scoped instances of IRazorPageFactory
|
||||
yield return describe.Scoped<IViewStartProvider, ViewStartProvider>();
|
||||
yield return describe.Transient<IRazorView, RazorView>();
|
||||
yield return describe.Transient<IRazorViewFactory, RazorViewFactory>();
|
||||
yield return describe.Singleton<IRazorPageActivator, RazorPageActivator>();
|
||||
// Virtual path view factory needs to stay scoped so views can get get scoped services.
|
||||
yield return describe.Scoped<IRazorPageFactory, VirtualPathRazorPageFactory>();
|
||||
|
|
|
|||
|
|
@ -210,10 +210,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var pageFactory = new Mock<IRazorPageFactory>();
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
var page = Mock.Of<IRazorPage>();
|
||||
|
||||
pageFactory.Setup(p => p.CreateInstance(It.IsAny<string>()))
|
||||
.Returns(Mock.Of<IRazorPage>());
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object);
|
||||
viewFactory.Setup(p => p.GetView(It.IsAny<IRazorPage>(), It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<IView>());
|
||||
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object, viewFactory.Object);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
||||
// Act
|
||||
|
|
@ -221,7 +226,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.IsAssignableFrom<IRazorView>(result.View);
|
||||
Assert.IsAssignableFrom<IView>(result.View);
|
||||
Assert.Equal("/Views/bar/test-view.cshtml", result.ViewName);
|
||||
}
|
||||
|
||||
|
|
@ -230,11 +235,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var pageFactory = new Mock<IRazorPageFactory>();
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
var page = Mock.Of<IRazorPage>();
|
||||
pageFactory.Setup(p => p.CreateInstance("fake-path1/bar/test-view.rzr"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
||||
viewFactory.Object,
|
||||
GetViewLocationExpanders(),
|
||||
GetViewLocationCache());
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
|
@ -251,11 +258,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var pageFactory = new Mock<IRazorPageFactory>();
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
var page = Mock.Of<IRazorPage>();
|
||||
pageFactory.Setup(p => p.CreateInstance("fake-area-path/foo/bar/test-view2.rzr"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
||||
viewFactory.Object,
|
||||
GetViewLocationExpanders(),
|
||||
GetViewLocationCache());
|
||||
var context = GetActionContext(_areaTestContext);
|
||||
|
|
@ -293,7 +302,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[Theory]
|
||||
[MemberData(nameof(FindView_UsesViewLocationExpandersToLocateViewsData))]
|
||||
public void FindView_UsesViewLocationExpandersToLocateViews(IDictionary<string, object> routeValues,
|
||||
|
|
@ -304,6 +313,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
pageFactory.Setup(p => p.CreateInstance("test-string/bar.cshtml"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
|
||||
var expander1Result = new[] { "some-seed" };
|
||||
var expander1 = new Mock<IViewLocationExpander>();
|
||||
expander1.Setup(e => e.PopulateValues(It.IsAny<ViewLocationExpanderContext>()))
|
||||
|
|
@ -347,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
expander1.Verify();
|
||||
expander2.Verify();
|
||||
}
|
||||
|
||||
*/
|
||||
[Fact]
|
||||
public void FindView_CachesValuesIfViewWasFound()
|
||||
{
|
||||
|
|
@ -358,14 +368,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
pageFactory.Setup(p => p.CreateInstance("/Views/Shared/baz.cshtml"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
viewFactory.Setup(p => p.GetView(It.IsAny<IRazorPage>(), It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<IView>());
|
||||
|
||||
var cache = GetViewLocationCache();
|
||||
var cacheMock = Mock.Get<IViewLocationCache>(cache);
|
||||
|
||||
cacheMock.Setup(c => c.Set(It.IsAny<ViewLocationExpanderContext>(), "/Views/Shared/baz.cshtml"))
|
||||
.Verifiable();
|
||||
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object, cache: cache);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object, viewFactory.Object, cache: cache);
|
||||
var context = GetActionContext(_controllerTestContext, viewFactory.Object);
|
||||
|
||||
// Act
|
||||
var result = viewEngine.FindView(context, "baz");
|
||||
|
|
@ -384,6 +399,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
pageFactory.Setup(p => p.CreateInstance("some-view-location"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
viewFactory.Setup(p => p.GetView(It.IsAny<IRazorPage>(), It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<IView>());
|
||||
|
||||
var expander = new Mock<IViewLocationExpander>(MockBehavior.Strict);
|
||||
expander.Setup(v => v.PopulateValues(It.IsAny<ViewLocationExpanderContext>()))
|
||||
.Verifiable();
|
||||
|
|
@ -392,7 +412,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
.Returns("some-view-location")
|
||||
.Verifiable();
|
||||
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object,
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object,
|
||||
viewFactory.Object,
|
||||
new[] { expander.Object },
|
||||
cacheMock.Object);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
|
@ -418,6 +439,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
pageFactory.Setup(p => p.CreateInstance("some-view-location"))
|
||||
.Returns(Mock.Of<IRazorPage>())
|
||||
.Verifiable();
|
||||
|
||||
var viewFactory = new Mock<IRazorViewFactory>();
|
||||
viewFactory.Setup(p => p.GetView(It.IsAny<IRazorPage>(), It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<IView>());
|
||||
|
||||
var cacheMock = new Mock<IViewLocationCache>();
|
||||
cacheMock.Setup(c => c.Get(It.IsAny<ViewLocationExpanderContext>()))
|
||||
.Returns("expired-location");
|
||||
|
|
@ -432,6 +458,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
.Verifiable();
|
||||
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object,
|
||||
viewFactory.Object,
|
||||
new[] { expander.Object },
|
||||
cacheMock.Object);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
|
@ -447,14 +474,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
private IViewEngine CreateViewEngine(IRazorPageFactory pageFactory = null,
|
||||
IRazorViewFactory viewFactory = null,
|
||||
IEnumerable<IViewLocationExpander> expanders = null,
|
||||
IViewLocationCache cache = null)
|
||||
{
|
||||
pageFactory = pageFactory ?? Mock.Of<IRazorPageFactory>();
|
||||
viewFactory = viewFactory ?? Mock.Of<IRazorViewFactory>();
|
||||
|
||||
cache = cache ?? GetViewLocationCache();
|
||||
var viewLocationExpanderProvider = GetViewLocationExpanders(expanders);
|
||||
|
||||
var viewEngine = new RazorViewEngine(pageFactory,
|
||||
viewFactory,
|
||||
viewLocationExpanderProvider,
|
||||
cache);
|
||||
|
||||
|
|
@ -481,12 +512,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
private static ActionContext GetActionContext(IDictionary<string, object> routeValues,
|
||||
IRazorView razorView = null)
|
||||
IRazorViewFactory razorViewFactory = null)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
serviceProvider.Setup(p => p.GetService(typeof(IRazorView)))
|
||||
.Returns(razorView ?? Mock.Of<IRazorView>());
|
||||
serviceProvider.Setup(p => p.GetService(typeof(IRazorViewFactory)))
|
||||
.Returns(razorViewFactory ?? Mock.Of<IRazorViewFactory>());
|
||||
|
||||
httpContext.RequestServices = serviceProvider.Object;
|
||||
|
||||
|
|
@ -502,9 +533,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
private class OverloadedLocationViewEngine : RazorViewEngine
|
||||
{
|
||||
public OverloadedLocationViewEngine(IRazorPageFactory pageFactory,
|
||||
IRazorViewFactory viewFactory,
|
||||
IViewLocationExpanderProvider expanderProvider,
|
||||
IViewLocationCache cache)
|
||||
: base(pageFactory, expanderProvider, cache)
|
||||
: base(pageFactory, viewFactory, expanderProvider, cache)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||
{
|
||||
public class RazorViewFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetView_ReturnsRazorView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new RazorViewFactory(
|
||||
Mock.Of<IRazorPageFactory>(),
|
||||
Mock.Of<IRazorPageActivator>(),
|
||||
Mock.Of<IViewStartProvider>());
|
||||
|
||||
// Act
|
||||
var view = factory.GetView(Mock.Of<IRazorPage>(), true);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<RazorView>(view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenderAsync_ContextualizeMustBeInvoked()
|
||||
{
|
||||
// Arrange
|
||||
var page = new TestableRazorPage(v => { });
|
||||
|
||||
var factory = new RazorViewFactory(
|
||||
Mock.Of<IRazorPageFactory>(),
|
||||
Mock.Of<IRazorPageActivator>(),
|
||||
CreateViewStartProvider());
|
||||
|
||||
// Act
|
||||
var view = factory.GetView(page, true);
|
||||
|
||||
// Assert
|
||||
var viewContext = CreateViewContext(view);
|
||||
await Assert.DoesNotThrowAsync(() => view.RenderAsync(viewContext));
|
||||
}
|
||||
|
||||
private static IViewStartProvider CreateViewStartProvider()
|
||||
{
|
||||
var viewStartPages = new IRazorPage[0];
|
||||
var viewStartProvider = new Mock<IViewStartProvider>();
|
||||
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
|
||||
.Returns(viewStartPages);
|
||||
|
||||
return viewStartProvider.Object;
|
||||
}
|
||||
|
||||
private static ViewContext CreateViewContext(IView view)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var actionContext = new ActionContext(httpContext, routeData: null, actionDescriptor: null);
|
||||
return new ViewContext(
|
||||
actionContext,
|
||||
view,
|
||||
new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
new StringWriter());
|
||||
}
|
||||
|
||||
private class TestableRazorPage : RazorPage
|
||||
{
|
||||
private readonly Action<TestableRazorPage> _executeAction;
|
||||
|
||||
public TestableRazorPage(Action<TestableRazorPage> executeAction)
|
||||
{
|
||||
_executeAction = executeAction;
|
||||
}
|
||||
|
||||
public void RenderBodyPublic()
|
||||
{
|
||||
Write(RenderBody());
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync()
|
||||
{
|
||||
_executeAction(this);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue