ViewStarts need to be executed as part of View execution

Fixes #834
This commit is contained in:
Pranav K 2014-07-21 09:27:24 -07:00
parent b8ab5c5063
commit e28adbfb3d
19 changed files with 362 additions and 23 deletions

View File

@ -39,6 +39,7 @@
<Content Include="Views\Shared\HelloWorldPartial.cshtml" /> <Content Include="Views\Shared\HelloWorldPartial.cshtml" />
<Content Include="Views\Shared\MyView.cshtml" /> <Content Include="Views\Shared\MyView.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" /> <Content Include="Views\Shared\_Layout.cshtml" />
<Content Include="Views\_ViewStart.cshtml" />
<Content Include="web.config" /> <Content Include="web.config" />
<Content Include="web.Debug.config" /> <Content Include="web.Debug.config" />
<Content Include="web.Release.config" /> <Content Include="web.Release.config" />

View File

@ -1,7 +1,6 @@
@using MvcSample.Web.Models @using MvcSample.Web.Models
@model User @model User
@{ @{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "Home Page"; ViewBag.Title = "Home Page";
string nullValue = null; string nullValue = null;

View File

@ -27,12 +27,6 @@
<div class="container body-content"> <div class="container body-content">
@RenderBody() @RenderBody()
<hr /> <hr />
<address>
@if (@Model != null)
{
@Model.Address
}
</address>
<footer> <footer>
<p>&copy; @DateTime.Now.Year - My ASP.NET Application</p> <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
</footer> </footer>

View File

@ -0,0 +1,3 @@
@{
Layout = "/Views/Shared/_Layout.cshtml";
}

View File

@ -16,6 +16,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary> /// </summary>
ViewContext ViewContext { get; set; } ViewContext ViewContext { get; set; }
/// <summary>
/// Gets the path to the page.
/// </summary>
string Path { get; set; }
string BodyContent { get; set; } string BodyContent { get; set; }
/// <summary> /// <summary>

View File

@ -11,8 +11,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <summary> /// <summary>
/// Creates a <see cref="IRazorPage"/> for the specified path. /// Creates a <see cref="IRazorPage"/> for the specified path.
/// </summary> /// </summary>
/// <param name="viewPath">The path to locate the RazorPage.</param> /// <param name="path">The path to locate the page.</param>
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns> /// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
IRazorPage CreateInstance(string viewPath); IRazorPage CreateInstance(string path);
} }
} }

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Defines methods for locating ViewStart pages that are applicable to a page.
/// </summary>
public interface IViewStartProvider
{
/// <summary>
/// Given a view path, returns a sequence of ViewStart instances
/// that are applicable to the specified view.
/// </summary>
/// <param name="path">The path of the page to locate ViewStart files for.</param>
/// <returns>A sequence of <see cref="IRazorPage"/> that represent ViewStart.</returns>
IEnumerable<IRazorPage> GetViewStartPages(string path);
}
}

View File

@ -30,6 +30,9 @@
<Compile Include="Compilation\RoslynCompilationService.cs" /> <Compile Include="Compilation\RoslynCompilationService.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" /> <Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="IRazorPage.cs" /> <Compile Include="IRazorPage.cs" />
<Compile Include="IViewStartProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewStartProvider.cs" />
<Compile Include="VirtualPathRazorPageFactory.cs" /> <Compile Include="VirtualPathRazorPageFactory.cs" />
<Compile Include="HelperResult.cs" /> <Compile Include="HelperResult.cs" />
<Compile Include="IRazorPageActivator.cs" /> <Compile Include="IRazorPageActivator.cs" />

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Test")]

View File

@ -42,6 +42,9 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
/// <inheritdoc />
public string Path { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public ViewContext ViewContext { get; set; } public ViewContext ViewContext { get; set; }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
@ -17,6 +18,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
private readonly IRazorPageFactory _pageFactory; private readonly IRazorPageFactory _pageFactory;
private readonly IRazorPageActivator _pageActivator; private readonly IRazorPageActivator _pageActivator;
private readonly IViewStartProvider _viewStartProvider;
private readonly IRazorPage _page; private readonly IRazorPage _page;
private readonly bool _executeViewHierarchy; private readonly bool _executeViewHierarchy;
@ -30,11 +32,13 @@ namespace Microsoft.AspNet.Mvc.Razor
/// view start and layout pages are executed as part of the executing the page.</param> /// view start and layout pages are executed as part of the executing the page.</param>
public RazorView([NotNull] IRazorPageFactory pageFactory, public RazorView([NotNull] IRazorPageFactory pageFactory,
[NotNull] IRazorPageActivator pageActivator, [NotNull] IRazorPageActivator pageActivator,
[NotNull] IViewStartProvider viewStartProvider,
[NotNull] IRazorPage page, [NotNull] IRazorPage page,
bool executeViewHierarchy) bool executeViewHierarchy)
{ {
_pageFactory = pageFactory; _pageFactory = pageFactory;
_pageActivator = pageActivator; _pageActivator = pageActivator;
_viewStartProvider = viewStartProvider;
_page = page; _page = page;
_executeViewHierarchy = executeViewHierarchy; _executeViewHierarchy = executeViewHierarchy;
} }
@ -44,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
if (_executeViewHierarchy) if (_executeViewHierarchy)
{ {
var bodyContent = await RenderPageAsync(_page, context); var bodyContent = await RenderPageAsync(_page, context, executeViewStart: true);
await RenderLayoutAsync(context, bodyContent); await RenderLayoutAsync(context, bodyContent);
} }
else else
@ -53,7 +57,9 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
private async Task<string> RenderPageAsync(IRazorPage page, ViewContext context) private async Task<string> RenderPageAsync(IRazorPage page,
ViewContext context,
bool executeViewStart)
{ {
var contentBuilder = new StringBuilder(1024); var contentBuilder = new StringBuilder(1024);
using (var bodyWriter = new StringWriter(contentBuilder)) using (var bodyWriter = new StringWriter(contentBuilder))
@ -64,6 +70,11 @@ namespace Microsoft.AspNet.Mvc.Razor
context.Writer = bodyWriter; context.Writer = bodyWriter;
try try
{ {
if (executeViewStart)
{
// Execute view starts using the same context + writer as the page to render.
await RenderViewStartAsync(context);
}
await RenderPageCoreAsync(page, context); await RenderPageCoreAsync(page, context);
} }
finally finally
@ -86,6 +97,19 @@ namespace Microsoft.AspNet.Mvc.Razor
await page.ExecuteAsync(); await page.ExecuteAsync();
} }
private async Task RenderViewStartAsync(ViewContext context)
{
var viewStarts = _viewStartProvider.GetViewStartPages(_page.Path);
foreach (var viewStart in viewStarts)
{
await RenderPageCoreAsync(viewStart, context);
// Copy over interesting properties from the ViewStart page to the entry page.
_page.Layout = viewStart.Layout;
}
}
private async Task RenderLayoutAsync(ViewContext context, private async Task RenderLayoutAsync(ViewContext context,
string bodyContent) string bodyContent)
{ {
@ -104,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.Razor
layoutPage.PreviousSectionWriters = previousPage.SectionWriters; layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
layoutPage.BodyContent = bodyContent; layoutPage.BodyContent = bodyContent;
bodyContent = await RenderPageAsync(layoutPage, context); bodyContent = await RenderPageAsync(layoutPage, context, executeViewStart: false);
// Verify that RenderBody is called, or that RenderSection is called for all sections // Verify that RenderBody is called, or that RenderSection is called for all sections
layoutPage.EnsureBodyAndSectionsWereRendered(); layoutPage.EnsureBodyAndSectionsWereRendered();

View File

@ -28,12 +28,15 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly IRazorPageFactory _pageFactory; private readonly IRazorPageFactory _pageFactory;
private readonly IRazorPageActivator _viewActivator; private readonly IRazorPageActivator _viewActivator;
private readonly IViewStartProvider _viewStartProvider;
public RazorViewEngine(IRazorPageFactory pageFactory, public RazorViewEngine(IRazorPageFactory pageFactory,
IRazorPageActivator viewActivator) IRazorPageActivator viewActivator,
IViewStartProvider viewStartProvider)
{ {
_pageFactory = pageFactory; _pageFactory = pageFactory;
_viewActivator = viewActivator; _viewActivator = viewActivator;
_viewStartProvider = viewStartProvider;
} }
public IEnumerable<string> ViewLocationFormats public IEnumerable<string> ViewLocationFormats
@ -96,6 +99,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
var view = new RazorView(_pageFactory, var view = new RazorView(_pageFactory,
_viewActivator, _viewActivator,
_viewStartProvider,
page, page,
executeViewHierarchy: !partial); executeViewHierarchy: !partial);
return ViewEngineResult.Found(viewName, view); return ViewEngineResult.Found(viewName, view);

View File

@ -0,0 +1,92 @@
// 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.Generic;
using System.IO;
using System.Linq;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <inheritdoc />
public class ViewStartProvider : IViewStartProvider
{
private const string ViewStartFileName = "_ViewStart.cshtml";
private readonly string _appRoot;
private readonly IRazorPageFactory _pageFactory;
public ViewStartProvider(IApplicationEnvironment appEnv,
IRazorPageFactory pageFactory)
{
_appRoot = TrimTrailingSlash(appEnv.ApplicationBasePath);
_pageFactory = pageFactory;
}
/// <inheritdoc />
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
{
var viewStartLocations = GetViewStartLocations(path);
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
.Where(p => p != null)
.ToArray();
// GetViewStartLocations return ViewStarts inside-out that is the _ViewStart closest to the page
// is the first: e.g. [ /Views/Home/_ViewStart, /Views/_ViewStart, /_ViewStart ]
// However they need to be executed outside in, so we'll reverse the sequence.
Array.Reverse(viewStarts);
return viewStarts;
}
internal IEnumerable<string> GetViewStartLocations(string path)
{
if (string.IsNullOrEmpty(path))
{
return Enumerable.Empty<string>();
}
var viewStartLocations = new List<string>();
var currentDir = GetViewDirectory(_appRoot, path);
while (IsSubDirectory(_appRoot, currentDir))
{
viewStartLocations.Add(Path.Combine(currentDir, ViewStartFileName));
currentDir = Path.GetDirectoryName(currentDir);
}
return viewStartLocations;
}
private static bool IsSubDirectory(string appRoot, string currentDir)
{
return currentDir.StartsWith(appRoot, StringComparison.OrdinalIgnoreCase);
}
private static string GetViewDirectory(string appRoot, string viewPath)
{
if (viewPath.StartsWith("~/"))
{
viewPath = viewPath.Substring(2);
}
else if (viewPath[0] == Path.DirectorySeparatorChar ||
viewPath[0] == Path.AltDirectorySeparatorChar)
{
viewPath = viewPath.Substring(1);
}
var viewDir = Path.GetDirectoryName(viewPath);
return Path.GetFullPath(Path.Combine(appRoot, viewDir));
}
private static string TrimTrailingSlash(string path)
{
if (path.Length > 0 &&
path[path.Length - 1] == Path.DirectorySeparatorChar)
{
return path.Substring(0, path.Length - 1);
}
return path;
}
}
}

View File

@ -30,14 +30,15 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
/// <inheritdoc /> /// <inheritdoc />
public IRazorPage CreateInstance([NotNull] string viewPath) public IRazorPage CreateInstance([NotNull] string path)
{ {
var fileInfo = _fileInfoCache.GetFileInfo(viewPath); var fileInfo = _fileInfoCache.GetFileInfo(path);
if (fileInfo != null) if (fileInfo != null)
{ {
var result = _compilationService.Compile(fileInfo); var result = _compilationService.Compile(fileInfo);
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType); var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
page.Path = path;
return page; return page;
} }

View File

@ -43,6 +43,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton<IViewEngineProvider, DefaultViewEngineProvider>(); yield return describe.Singleton<IViewEngineProvider, DefaultViewEngineProvider>();
yield return describe.Scoped<ICompositeViewEngine, CompositeViewEngine>(); yield return describe.Scoped<ICompositeViewEngine, CompositeViewEngine>();
yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>(); yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>();
yield return describe.Singleton<IViewStartProvider, ViewStartProvider>();
yield return describe.Singleton<IRazorPageActivator, RazorPageActivator>(); yield return describe.Singleton<IRazorPageActivator, RazorPageActivator>();
// Virtual path view factory needs to stay scoped so views can get get scoped services. // Virtual path view factory needs to stay scoped so views can get get scoped services.

View File

@ -28,6 +28,7 @@
<Compile Include="RazorPageTest.cs" /> <Compile Include="RazorPageTest.cs" />
<Compile Include="RazorViewTest.cs" /> <Compile Include="RazorViewTest.cs" />
<Compile Include="SpanFactory.cs" /> <Compile Include="SpanFactory.cs" />
<Compile Include="ViewStartProviderTest.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -160,15 +160,35 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
}, result.SearchedLocations); }, result.SearchedLocations);
} }
[Fact]
public void FindView_ReturnsRazorView_IfLookupWasSuccessful()
{
// Arrange
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(It.IsAny<string>()))
.Returns(Mock.Of<IRazorPage>());
var viewEngine = new RazorViewEngine(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
Mock.Of<IViewStartProvider>());
// Act
var result = viewEngine.FindView(_controllerTestContext, "test-view");
// Assert
Assert.True(result.Success);
Assert.IsType<RazorView>(result.View);
Assert.Equal("/Views/bar/test-view.cshtml", result.ViewName);
}
private IViewEngine CreateSearchLocationViewEngineTester() private IViewEngine CreateSearchLocationViewEngineTester()
{ {
var pageFactory = new Mock<IRazorPageFactory>(); var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(vpf => vpf.CreateInstance(It.IsAny<string>())) pageFactory.Setup(vpf => vpf.CreateInstance(It.IsAny<string>()))
.Returns<RazorPage>(null); .Returns<RazorPage>(null);
var pageActivator = Mock.Of<IRazorPageActivator>(); var viewEngine = new RazorViewEngine(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
var viewEngine = new RazorViewEngine(pageFactory.Object, pageActivator); Mock.Of<IViewStartProvider>());
return viewEngine; return viewEngine;
} }

View File

@ -4,8 +4,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.PipelineCore;
using Moq; using Moq;
using Xunit; using Xunit;
@ -27,6 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}); });
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: false); executeViewHierarchy: false);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -51,9 +52,10 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Same(viewData, v.ViewContext.ViewData); Assert.Same(viewData, v.ViewContext.ViewData);
}); });
var activator = new Mock<IRazorPageActivator>(); var activator = new Mock<IRazorPageActivator>();
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object, activator.Object,
CreateViewStartProvider(),
page, page,
executeViewHierarchy: false); executeViewHierarchy: false);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -86,6 +88,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Verifiable(); .Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object, activator.Object,
CreateViewStartProvider(),
page, page,
executeViewHierarchy: false); executeViewHierarchy: false);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -98,15 +101,17 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Fact] [Fact]
public async Task RenderAsync_WithoutHierarchy_DoesNotExecuteLayoutPages() public async Task RenderAsync_WithoutHierarchy_DoesNotExecuteLayoutOrViewStartPages()
{ {
var page = new TestableRazorPage(v => var page = new TestableRazorPage(v =>
{ {
v.Layout = LayoutPath; v.Layout = LayoutPath;
}); });
var pageFactory = new Mock<IRazorPageFactory>(); var pageFactory = new Mock<IRazorPageFactory>();
var viewStartProvider = CreateViewStartProvider();
var view = new RazorView(pageFactory.Object, var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
viewStartProvider,
page, page,
executeViewHierarchy: false); executeViewHierarchy: false);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -116,6 +121,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Assert // Assert
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never()); pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never());
Mock.Get(viewStartProvider).Verify(v => v.GetViewStartPages(It.IsAny<string>()), Times.Never());
} }
[Fact] [Fact]
@ -129,6 +135,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}); });
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -152,6 +159,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}); });
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -177,6 +185,49 @@ namespace Microsoft.AspNet.Mvc.Razor
.Verifiable(); .Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(), var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object, activator.Object,
CreateViewStartProvider(),
page,
executeViewHierarchy: true);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
activator.Verify();
}
[Fact]
public async Task RenderAsync_WithHierarchy_ExecutesViewStart()
{
// Arrange
var actualLayoutPath = "";
var layoutPath = "/Views/_Shared/_Layout.cshtml";
var viewStart1 = new TestableRazorPage(v =>
{
v.Layout = "/fake-layout-path";
});
var viewStart2 = new TestableRazorPage(v =>
{
v.Layout = layoutPath;
});
var page = new TestableRazorPage(v =>
{
// This path must have been set as a consequence of running viewStart
actualLayoutPath = v.Layout;
// Clear out layout so we don't render it
v.Layout = null;
});
var activator = new Mock<IRazorPageActivator>();
activator.Setup(a => a.Activate(viewStart1, It.IsAny<ViewContext>()))
.Verifiable();
activator.Setup(a => a.Activate(viewStart2, It.IsAny<ViewContext>()))
.Verifiable();
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider(viewStart1, viewStart2),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -231,6 +282,7 @@ foot-content";
var view = new RazorView(pageFactory.Object, var view = new RazorView(pageFactory.Object,
activator.Object, activator.Object,
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -264,6 +316,7 @@ foot-content";
var view = new RazorView(pageFactory.Object, var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -290,6 +343,7 @@ foot-content";
var view = new RazorView(pageFactory.Object, var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -344,6 +398,7 @@ body-content";
var view = new RazorView(pageFactory.Object, var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(), Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider(),
page, page,
executeViewHierarchy: true); executeViewHierarchy: true);
var viewContext = CreateViewContext(view); var viewContext = CreateViewContext(view);
@ -357,8 +412,8 @@ body-content";
private static ViewContext CreateViewContext(RazorView view) private static ViewContext CreateViewContext(RazorView view)
{ {
var httpContext = new Mock<HttpContext>(); var httpContext = new DefaultHttpContext();
var actionContext = new ActionContext(httpContext.Object, routeData: null, actionDescriptor: null); var actionContext = new ActionContext(httpContext, routeData: null, actionDescriptor: null);
return new ViewContext( return new ViewContext(
actionContext, actionContext,
view, view,
@ -366,6 +421,16 @@ body-content";
new StringWriter()); new StringWriter());
} }
private static IViewStartProvider CreateViewStartProvider(params IRazorPage[] viewStartPages)
{
viewStartPages = viewStartPages ?? new IRazorPage[0];
var viewStartProvider = new Mock<IViewStartProvider>();
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
.Returns(viewStartPages);
return viewStartProvider.Object;
}
private class TestableRazorPage : RazorPage private class TestableRazorPage : RazorPage
{ {
private readonly Action<TestableRazorPage> _executeAction; private readonly Action<TestableRazorPage> _executeAction;

View File

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Framework.Runtime;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor.Test
{
public class ViewStartProviderTest
{
[Theory]
[InlineData(null)]
[InlineData("")]
public void GetViewStartLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
{
// Arrange
var appPath = @"x:\test";
var provider = new ViewStartProvider(GetAppEnv(appPath), Mock.Of<IRazorPageFactory>());
// Act
var result = provider.GetViewStartLocations(viewPath);
// Assert
Assert.Empty(result);
}
public static IEnumerable<object[]> GetViewStartLocations_ReturnsPotentialViewStartLocationsData
{
get
{
yield return new object[]
{
@"x:\test\myapp",
"/Views/Home/View.cshtml",
new[]
{
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
@"x:\test\myapp\Views\_ViewStart.cshtml",
@"x:\test\myapp\_ViewStart.cshtml",
}
};
yield return new object[]
{
@"x:\test\myapp",
"Views/Home/View.cshtml",
new[]
{
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
@"x:\test\myapp\Views\_ViewStart.cshtml",
@"x:\test\myapp\_ViewStart.cshtml",
}
};
yield return new object[]
{
@"x:\test\myapp\",
"Views/Home/View.cshtml",
new[]
{
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
@"x:\test\myapp\Views\_ViewStart.cshtml",
@"x:\test\myapp\_ViewStart.cshtml",
}
};
}
}
[Theory]
[MemberData("GetViewStartLocations_ReturnsPotentialViewStartLocationsData")]
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string appPath,
string viewPath,
IEnumerable<string> expected)
{
// Arrange
var provider = new ViewStartProvider(GetAppEnv(appPath), Mock.Of<IRazorPageFactory>());
// Act
var result = provider.GetViewStartLocations(viewPath);
// Assert
Assert.Equal(expected, result);
}
private static IApplicationEnvironment GetAppEnv(string appPath)
{
var appEnv = new Mock<IApplicationEnvironment>();
appEnv.Setup(p => p.ApplicationBasePath)
.Returns(appPath);
return appEnv.Object;
}
}
}