Separating view execution and Razor behavior
* Introducing RazorPage and RazorPageOfT that represent the Razor execution aspect of view execution. Moving view execution hierarchy behavior (Layout, partial views etc) into a separate RazorView type. * Renaming IVirtualPathViewFactory to IRazorPageFactory, IRazorViewActivator to IRazorPageActivator * Renaming VirtualPathViewFactor to FileBasedPageFactory to correctly reflect what it does. Fixes #814
This commit is contained in:
parent
c1112fcaf1
commit
9e535f6897
15
Mvc.sln
15
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.21813.0
|
VisualStudioVersion = 14.0.21901.1
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -51,6 +51,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Test",
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}"
|
||||||
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersSite", "test\WebSites\ValueProvidersSite\ValueProvidersSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersSite", "test\WebSites\ValueProvidersSite\ValueProvidersSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|
@ -263,6 +265,16 @@ Global
|
||||||
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|x86.ActiveCfg = Release|Any CPU
|
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
|
@ -299,6 +311,7 @@ Global
|
||||||
{42CDBF4A-E238-4C0F-A416-44588363EB4C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{42CDBF4A-E238-4C0F-A416-44588363EB4C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
{5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
|
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,25 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public class VirtualPathViewFactory : IVirtualPathViewFactory
|
/// <summary>
|
||||||
|
/// Represents a <see cref="IRazorPageFactory"/> that creates <see cref="RazorPage"/> instances
|
||||||
|
/// from razor files in the file system.
|
||||||
|
/// </summary>
|
||||||
|
public class FileBasedRazorPageFactory : IRazorPageFactory
|
||||||
{
|
{
|
||||||
private readonly IRazorCompilationService _compilationService;
|
private readonly IRazorCompilationService _compilationService;
|
||||||
private readonly ITypeActivator _activator;
|
private readonly ITypeActivator _activator;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IFileInfoCache _fileInfoCache;
|
private readonly IFileInfoCache _fileInfoCache;
|
||||||
|
|
||||||
public VirtualPathViewFactory(IRazorCompilationService compilationService,
|
public FileBasedRazorPageFactory(IRazorCompilationService compilationService,
|
||||||
ITypeActivator typeActivator,
|
ITypeActivator typeActivator,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IFileInfoCache fileInfoCache)
|
IFileInfoCache fileInfoCache)
|
||||||
{
|
{
|
||||||
_compilationService = compilationService;
|
_compilationService = compilationService;
|
||||||
_activator = typeActivator;
|
_activator = typeActivator;
|
||||||
|
|
@ -26,14 +29,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
_fileInfoCache = fileInfoCache;
|
_fileInfoCache = fileInfoCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IView CreateInstance([NotNull] string virtualPath)
|
/// <inheritdoc />
|
||||||
|
public RazorPage CreateInstance([NotNull] string viewPath)
|
||||||
{
|
{
|
||||||
var fileInfo = _fileInfoCache.GetFileInfo(virtualPath);
|
var fileInfo = _fileInfoCache.GetFileInfo(viewPath.TrimStart('~'));
|
||||||
|
|
||||||
if (fileInfo != null)
|
if (fileInfo != null)
|
||||||
{
|
{
|
||||||
var result = _compilationService.Compile(fileInfo);
|
var result = _compilationService.Compile(fileInfo);
|
||||||
return (IView)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
var page = (RazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
||||||
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -4,15 +4,15 @@
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides methods to activate properties on a view instance.
|
/// Provides methods to activate properties on a <see cref="RazorPage"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRazorViewActivator
|
public interface IRazorPageActivator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When implemented in a type, activates an instantiated view.
|
/// When implemented in a type, activates an instantiated page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="view">The view to activate.</param>
|
/// <param name="page">The page to activate.</param>
|
||||||
/// <param name="context">The <see cref="ViewContext"/> for the view.</param>
|
/// <param name="context">The <see cref="ViewContext"/> for the executing view.</param>
|
||||||
void Activate(RazorView view, ViewContext context);
|
void Activate(RazorPage page, ViewContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines methods that are used for creating <see cref="RazorPage"/> instances at a given path.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRazorPageFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="RazorPage"/> for the specified path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="viewPath">The path to locate the RazorPage.</param>
|
||||||
|
/// <returns>The RazorPage instance if it exists, null otherwise.</returns>
|
||||||
|
RazorPage CreateInstance(string viewPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,19 +29,20 @@
|
||||||
<Compile Include="Compilation\ICompilationService.cs" />
|
<Compile Include="Compilation\ICompilationService.cs" />
|
||||||
<Compile Include="Compilation\RoslynCompilationService.cs" />
|
<Compile Include="Compilation\RoslynCompilationService.cs" />
|
||||||
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
||||||
|
<Compile Include="FileBasedRazorPageFactory.cs" />
|
||||||
<Compile Include="HelperResult.cs" />
|
<Compile Include="HelperResult.cs" />
|
||||||
<Compile Include="IRazorViewActivator.cs" />
|
<Compile Include="IRazorPageActivator.cs" />
|
||||||
|
<Compile Include="IRazorPageFactory.cs" />
|
||||||
<Compile Include="PositionTagged.cs" />
|
<Compile Include="PositionTagged.cs" />
|
||||||
<Compile Include="Properties\Resources.Designer.cs" />
|
<Compile Include="Properties\Resources.Designer.cs" />
|
||||||
|
<Compile Include="RazorPage.cs" />
|
||||||
|
<Compile Include="RazorPageOfT.cs" />
|
||||||
<Compile Include="RazorView.cs" />
|
<Compile Include="RazorView.cs" />
|
||||||
<Compile Include="RazorViewActivator.cs" />
|
<Compile Include="RazorPageActivator.cs" />
|
||||||
<Compile Include="RazorViewOfT.cs" />
|
<Compile Include="RazorViewEngine.cs" />
|
||||||
<Compile Include="Razor\IRazorCompilationService.cs" />
|
<Compile Include="Razor\IRazorCompilationService.cs" />
|
||||||
<Compile Include="Razor\RazorCompilationService.cs" />
|
<Compile Include="Razor\RazorCompilationService.cs" />
|
||||||
<Compile Include="Services\IRoslynMetadataReference.cs" />
|
<Compile Include="Services\IRoslynMetadataReference.cs" />
|
||||||
<Compile Include="ViewEngine\IVirtualPathViewFactory.cs" />
|
|
||||||
<Compile Include="ViewEngine\RazorViewEngine.cs" />
|
|
||||||
<Compile Include="ViewEngine\VirtualPathViewFactory.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>
|
||||||
|
|
@ -202,6 +202,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewCannotBeActivated"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewCannotBeActivated"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0} must be set to access '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewContextMustBeSet
|
||||||
|
{
|
||||||
|
get { return GetString("ViewContextMustBeSet"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0} must be set to access '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewContextMustBeSet(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewContextMustBeSet"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// View '{0}' must have extension '{1}' when the view represents a full path.
|
/// View '{0}' must have extension '{1}' when the view represents a full path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,25 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public abstract class RazorView : IView
|
/// <summary>
|
||||||
|
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class RazorPage
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private bool _renderedBody;
|
private bool _renderedBody;
|
||||||
|
|
||||||
|
public RazorPage()
|
||||||
|
{
|
||||||
|
SectionWriters = new Dictionary<string, HelperResult>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
[Activate]
|
[Activate]
|
||||||
public IUrlHelper Url { get; set; }
|
public IUrlHelper Url { get; set; }
|
||||||
|
|
||||||
|
|
@ -40,7 +46,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
public string Layout { get; set; }
|
public string Layout { get; set; }
|
||||||
|
|
||||||
protected TextWriter Output { get; set; }
|
/// <summary>
|
||||||
|
/// Gets the TextWriter that the page is writing output to.
|
||||||
|
/// </summary>
|
||||||
|
public virtual TextWriter Output
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ViewContext == null)
|
||||||
|
{
|
||||||
|
var message = Resources.FormatViewContextMustBeSet("ViewContext", "Output");
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewContext.Writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual IPrincipal User
|
public virtual IPrincipal User
|
||||||
{
|
{
|
||||||
|
|
@ -63,66 +84,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BodyContent { get; set; }
|
public string BodyContent { get; set; }
|
||||||
|
|
||||||
private Dictionary<string, HelperResult> SectionWriters { get; set; }
|
public Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||||
|
|
||||||
private Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
public Dictionary<string, HelperResult> SectionWriters { get; private set; }
|
||||||
|
|
||||||
public virtual async Task RenderAsync([NotNull] ViewContext context)
|
|
||||||
{
|
|
||||||
SectionWriters = new Dictionary<string, HelperResult>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
ViewContext = context;
|
|
||||||
|
|
||||||
var contentBuilder = new StringBuilder(1024);
|
|
||||||
using (var bodyWriter = new StringWriter(contentBuilder))
|
|
||||||
{
|
|
||||||
Output = bodyWriter;
|
|
||||||
|
|
||||||
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
|
||||||
// and ViewComponents to reference it.
|
|
||||||
var oldWriter = context.Writer;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context.Writer = bodyWriter;
|
|
||||||
await ExecuteAsync();
|
|
||||||
|
|
||||||
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
|
||||||
VerifyRenderedBodyOrSections();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
context.Writer = oldWriter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyContent = contentBuilder.ToString();
|
|
||||||
if (!string.IsNullOrEmpty(Layout))
|
|
||||||
{
|
|
||||||
await RenderLayoutAsync(context, bodyContent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await context.Writer.WriteAsync(bodyContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RenderLayoutAsync(ViewContext context, string bodyContent)
|
|
||||||
{
|
|
||||||
var virtualPathFactory = context.HttpContext.RequestServices.GetService<IVirtualPathViewFactory>();
|
|
||||||
var layoutView = (RazorView)virtualPathFactory.CreateInstance(Layout);
|
|
||||||
|
|
||||||
if (layoutView == null)
|
|
||||||
{
|
|
||||||
var message = Resources.FormatLayoutCannotBeLocated(Layout);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutView.PreviousSectionWriters = SectionWriters;
|
|
||||||
layoutView.BodyContent = bodyContent;
|
|
||||||
await layoutView.RenderAsync(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task ExecuteAsync();
|
public abstract Task ExecuteAsync();
|
||||||
|
|
||||||
|
|
@ -326,6 +292,32 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that RenderBody is called and that RenderSection is called for all sections for a page that is
|
||||||
|
/// part of view execution hierarchy.
|
||||||
|
/// </summary>
|
||||||
|
public void EnsureBodyAndSectionsWereRendered()
|
||||||
|
{
|
||||||
|
// If PreviousSectionWriters is set, ensure all defined sections were rendered.
|
||||||
|
if (PreviousSectionWriters != null)
|
||||||
|
{
|
||||||
|
var sectionsNotRendered = PreviousSectionWriters.Keys.Except(_renderedSections,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (sectionsNotRendered.Any())
|
||||||
|
{
|
||||||
|
var sectionNames = string.Join(", ", sectionsNotRendered);
|
||||||
|
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(sectionNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If BodyContent is set, ensure it was rendered.
|
||||||
|
if (BodyContent != null && !_renderedBody)
|
||||||
|
{
|
||||||
|
// If a body was defined, then RenderBody should have been called.
|
||||||
|
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureMethodCanBeInvoked(string methodName)
|
private void EnsureMethodCanBeInvoked(string methodName)
|
||||||
{
|
{
|
||||||
if (PreviousSectionWriters == null)
|
if (PreviousSectionWriters == null)
|
||||||
|
|
@ -333,24 +325,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName));
|
throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VerifyRenderedBodyOrSections()
|
|
||||||
{
|
|
||||||
if (BodyContent != null)
|
|
||||||
{
|
|
||||||
var sectionsNotRendered = PreviousSectionWriters.Keys.Except(_renderedSections,
|
|
||||||
StringComparer.OrdinalIgnoreCase);
|
|
||||||
if (sectionsNotRendered.Any())
|
|
||||||
{
|
|
||||||
var sectionNames = String.Join(", ", sectionsNotRendered);
|
|
||||||
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(sectionNames));
|
|
||||||
}
|
|
||||||
else if (!_renderedBody)
|
|
||||||
{
|
|
||||||
// If a body was defined, then RenderBody should have been called.
|
|
||||||
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,30 +10,26 @@ using Microsoft.Framework.DependencyInjection;
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class RazorViewActivator : IRazorViewActivator
|
public class RazorPageActivator : IRazorPageActivator
|
||||||
{
|
{
|
||||||
// Name of the "public TModel Model" property on RazorView<TModel>
|
// Name of the "public TModel Model" property on RazorPage<TModel>
|
||||||
private const string ModelPropertyName = "Model";
|
private const string ModelPropertyName = "Model";
|
||||||
private readonly ITypeActivator _typeActivator;
|
private readonly ITypeActivator _typeActivator;
|
||||||
private readonly ConcurrentDictionary<Type, ViewActivationInfo> _activationInfo;
|
private readonly ConcurrentDictionary<Type, PageActivationInfo> _activationInfo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the RazorViewActivator class.
|
/// Initializes a new instance of the <see cref="RazorPageActivator"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RazorViewActivator(ITypeActivator typeActivator)
|
public RazorPageActivator(ITypeActivator typeActivator)
|
||||||
{
|
{
|
||||||
_typeActivator = typeActivator;
|
_typeActivator = typeActivator;
|
||||||
_activationInfo = new ConcurrentDictionary<Type, ViewActivationInfo>();
|
_activationInfo = new ConcurrentDictionary<Type, PageActivationInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Activates the specified view by using the specified ViewContext.
|
public void Activate([NotNull] RazorPage page, [NotNull] ViewContext context)
|
||||||
/// </summary>
|
|
||||||
/// <param name="view">The view to activate.</param>
|
|
||||||
/// <param name="context">The ViewContext for the executing view.</param>
|
|
||||||
public void Activate([NotNull] RazorView view, [NotNull] ViewContext context)
|
|
||||||
{
|
{
|
||||||
var activationInfo = _activationInfo.GetOrAdd(view.GetType(),
|
var activationInfo = _activationInfo.GetOrAdd(page.GetType(),
|
||||||
CreateViewActivationInfo);
|
CreateViewActivationInfo);
|
||||||
|
|
||||||
context.ViewData = CreateViewDataDictionary(context, activationInfo);
|
context.ViewData = CreateViewDataDictionary(context, activationInfo);
|
||||||
|
|
@ -41,11 +37,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
for (var i = 0; i < activationInfo.PropertyActivators.Length; i++)
|
for (var i = 0; i < activationInfo.PropertyActivators.Length; i++)
|
||||||
{
|
{
|
||||||
var activateInfo = activationInfo.PropertyActivators[i];
|
var activateInfo = activationInfo.PropertyActivators[i];
|
||||||
activateInfo.Activate(view, context);
|
activateInfo.Activate(page, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewDataDictionary CreateViewDataDictionary(ViewContext context, ViewActivationInfo activationInfo)
|
private ViewDataDictionary CreateViewDataDictionary(ViewContext context, PageActivationInfo activationInfo)
|
||||||
{
|
{
|
||||||
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
|
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
|
||||||
// ViewContext.ViewData is an incompatibile type.
|
// ViewContext.ViewData is an incompatibile type.
|
||||||
|
|
@ -66,10 +62,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return context.ViewData;
|
return context.ViewData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewActivationInfo CreateViewActivationInfo(Type type)
|
private PageActivationInfo CreateViewActivationInfo(Type type)
|
||||||
{
|
{
|
||||||
// Look for a property named "Model". If it is non-null, we'll assume this is
|
// Look for a property named "Model". If it is non-null, we'll assume this is
|
||||||
// the equivalent of TModel Model property on RazorView<TModel>
|
// the equivalent of TModel Model property on RazorPage<TModel>
|
||||||
var modelProperty = type.GetRuntimeProperty(ModelPropertyName);
|
var modelProperty = type.GetRuntimeProperty(ModelPropertyName);
|
||||||
if (modelProperty == null)
|
if (modelProperty == null)
|
||||||
{
|
{
|
||||||
|
|
@ -80,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var modelType = modelProperty.PropertyType;
|
var modelType = modelProperty.PropertyType;
|
||||||
var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
|
var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
|
||||||
|
|
||||||
return new ViewActivationInfo
|
return new PageActivationInfo
|
||||||
{
|
{
|
||||||
ViewDataDictionaryType = viewDataType,
|
ViewDataDictionaryType = viewDataType,
|
||||||
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(type,
|
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(type,
|
||||||
|
|
@ -115,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return new PropertyActivator<ViewContext>(property, valueAccessor);
|
return new PropertyActivator<ViewContext>(property, valueAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewActivationInfo
|
private class PageActivationInfo
|
||||||
{
|
{
|
||||||
public PropertyActivator<ViewContext>[] PropertyActivators { get; set; }
|
public PropertyActivator<ViewContext>[] PropertyActivators { get; set; }
|
||||||
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public abstract class RazorView<TModel> : RazorView
|
/// <summary>
|
||||||
|
/// Represents the properties and methods that are needed in order to render a view that uses Razor syntax.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">The type of the view data model.</typeparam>
|
||||||
|
public abstract class RazorPage<TModel> : RazorPage
|
||||||
{
|
{
|
||||||
public TModel Model
|
public TModel Model
|
||||||
{
|
{
|
||||||
|
|
@ -18,13 +19,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
[Activate]
|
[Activate]
|
||||||
public ViewDataDictionary<TModel> ViewData { get; set; }
|
public ViewDataDictionary<TModel> ViewData { get; set; }
|
||||||
|
|
||||||
public override Task RenderAsync([NotNull] ViewContext context)
|
|
||||||
{
|
|
||||||
var viewActivator = context.HttpContext.RequestServices.GetService<IRazorViewActivator>();
|
|
||||||
viewActivator.Activate(this, context);
|
|
||||||
|
|
||||||
return base.RenderAsync(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,11 +26,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
"/Views/Shared/{0}" + ViewExtension,
|
"/Views/Shared/{0}" + ViewExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly IVirtualPathViewFactory _virtualPathFactory;
|
private readonly IRazorPageFactory _pageFactory;
|
||||||
|
private readonly IRazorPageActivator _viewActivator;
|
||||||
|
|
||||||
public RazorViewEngine(IVirtualPathViewFactory virtualPathFactory)
|
public RazorViewEngine(IRazorPageFactory pageFactory,
|
||||||
|
IRazorPageActivator viewActivator)
|
||||||
{
|
{
|
||||||
_virtualPathFactory = virtualPathFactory;
|
_pageFactory = pageFactory;
|
||||||
|
_viewActivator = viewActivator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> ViewLocationFormats
|
public IEnumerable<string> ViewLocationFormats
|
||||||
|
|
@ -41,18 +44,19 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public ViewEngineResult FindView([NotNull] IDictionary<string, object> context,
|
public ViewEngineResult FindView([NotNull] IDictionary<string, object> context,
|
||||||
[NotNull] string viewName)
|
[NotNull] string viewName)
|
||||||
{
|
{
|
||||||
var viewEngineResult = CreateViewEngineResult(context, viewName);
|
var viewEngineResult = CreateViewEngineResult(context, viewName, partial: false);
|
||||||
return viewEngineResult;
|
return viewEngineResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ViewEngineResult FindPartialView([NotNull] IDictionary<string, object> context,
|
public ViewEngineResult FindPartialView([NotNull] IDictionary<string, object> context,
|
||||||
[NotNull] string partialViewName)
|
[NotNull] string partialViewName)
|
||||||
{
|
{
|
||||||
return FindView(context, partialViewName);
|
return CreateViewEngineResult(context, partialViewName, partial: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewEngineResult CreateViewEngineResult([NotNull] IDictionary<string, object> context,
|
private ViewEngineResult CreateViewEngineResult([NotNull] IDictionary<string, object> context,
|
||||||
[NotNull] string viewName)
|
[NotNull] string viewName,
|
||||||
|
bool partial)
|
||||||
{
|
{
|
||||||
var nameRepresentsPath = IsSpecificPath(viewName);
|
var nameRepresentsPath = IsSpecificPath(viewName);
|
||||||
|
|
||||||
|
|
@ -64,8 +68,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
Resources.FormatViewMustEndInExtension(viewName, ViewExtension));
|
Resources.FormatViewMustEndInExtension(viewName, ViewExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
var view = _virtualPathFactory.CreateInstance(viewName);
|
var page = _pageFactory.CreateInstance(viewName);
|
||||||
return view != null ? ViewEngineResult.Found(viewName, view) :
|
|
||||||
|
return page != null ? CreateFoundResult(page, viewName, partial) :
|
||||||
ViewEngineResult.NotFound(viewName, new[] { viewName });
|
ViewEngineResult.NotFound(viewName, new[] { viewName });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -76,10 +81,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
foreach (var path in potentialPaths)
|
foreach (var path in potentialPaths)
|
||||||
{
|
{
|
||||||
var view = _virtualPathFactory.CreateInstance(path);
|
var page = _pageFactory.CreateInstance(path);
|
||||||
if (view != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
return ViewEngineResult.Found(viewName, view);
|
return CreateFoundResult(page, path, partial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +92,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ViewEngineResult CreateFoundResult(RazorPage page, string viewName, bool partial)
|
||||||
|
{
|
||||||
|
var view = new RazorView(_pageFactory,
|
||||||
|
_viewActivator,
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: !partial);
|
||||||
|
return ViewEngineResult.Found(viewName, view);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsSpecificPath(string name)
|
private static bool IsSpecificPath(string name)
|
||||||
{
|
{
|
||||||
return name[0] == '~' || name[0] == '/';
|
return name[0] == '~' || name[0] == '/';
|
||||||
|
|
@ -153,6 +153,9 @@
|
||||||
<data name="ViewCannotBeActivated" xml:space="preserve">
|
<data name="ViewCannotBeActivated" xml:space="preserve">
|
||||||
<value>View of type '{0}' cannot be activated by '{1}'.</value>
|
<value>View of type '{0}' cannot be activated by '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewContextMustBeSet" xml:space="preserve">
|
||||||
|
<value>'{0} must be set to access '{1}'.</value>
|
||||||
|
</data>
|
||||||
<data name="ViewMustEndInExtension" xml:space="preserve">
|
<data name="ViewMustEndInExtension" xml:space="preserve">
|
||||||
<value>View '{0}' must have extension '{1}' when the view represents a full path.</value>
|
<value>View '{0}' must have extension '{1}' when the view represents a full path.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// 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 interface IVirtualPathViewFactory
|
|
||||||
{
|
|
||||||
IView CreateInstance(string virtualPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
|
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
|
||||||
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
||||||
|
|
||||||
yield return describe.Instance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorView).FullName));
|
yield return describe.Instance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorPage).FullName));
|
||||||
|
|
||||||
yield return describe.Transient<ICompilationService, RoslynCompilationService>();
|
yield return describe.Transient<ICompilationService, RoslynCompilationService>();
|
||||||
|
|
||||||
|
|
@ -44,9 +44,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
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<IRazorViewActivator, RazorViewActivator>();
|
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.
|
||||||
yield return describe.Scoped<IVirtualPathViewFactory, VirtualPathViewFactory>();
|
yield return describe.Scoped<IRazorPageFactory, FileBasedRazorPageFactory>();
|
||||||
yield return describe.Singleton<IFileInfoCache, ExpiringFileInfoCache>();
|
yield return describe.Singleton<IFileInfoCache, ExpiringFileInfoCache>();
|
||||||
|
|
||||||
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
|
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
<Content Include="project.json" />
|
<Content Include="project.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="ViewEngineTests.cs" />
|
||||||
<Compile Include="ActivatorTests.cs" />
|
<Compile Include="ActivatorTests.cs" />
|
||||||
<Compile Include="ValueProviderTests.cs" />
|
<Compile Include="ValueProviderTests.cs" />
|
||||||
<Compile Include="CompositeViewEngineTests.cs" />
|
<Compile Include="CompositeViewEngineTests.cs" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using RazorWebSite;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ViewEngineTests
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider = TestHelper.CreateServices("RazorWebSite");
|
||||||
|
private readonly Action<IBuilder> _app = new Startup().Configure;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RazorView_ExecutesPageAndLayoutData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new[] { "ViewWithoutLayout", @"ViewWithoutLayout-Content" };
|
||||||
|
yield return new[]
|
||||||
|
{
|
||||||
|
"ViewWithLayout",
|
||||||
|
@"<layout>
|
||||||
|
|
||||||
|
ViewWithLayout-Content
|
||||||
|
</layout>"
|
||||||
|
};
|
||||||
|
yield return new[]
|
||||||
|
{
|
||||||
|
"ViewWithFullPath",
|
||||||
|
@"<layout>
|
||||||
|
|
||||||
|
ViewWithFullPath-content
|
||||||
|
</layout>"
|
||||||
|
};
|
||||||
|
yield return new[]
|
||||||
|
{
|
||||||
|
"ViewWithNestedLayout",
|
||||||
|
@"<layout>
|
||||||
|
|
||||||
|
<nested-layout>
|
||||||
|
/ViewEngine/ViewWithNestedLayout
|
||||||
|
|
||||||
|
ViewWithNestedLayout-Content
|
||||||
|
</nested-layout>
|
||||||
|
</layout>"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData("RazorView_ExecutesPageAndLayoutData")]
|
||||||
|
public async Task RazorView_ExecutesPageAndLayout(string actionName, string expected)
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.Handler;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetAsync("http://localhost/ViewEngine/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
|
Assert.Equal(expected, body.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorView_ExecutesPartialPagesWithCorrectContext()
|
||||||
|
{
|
||||||
|
var expected =
|
||||||
|
@"<partial>98052
|
||||||
|
|
||||||
|
</partial>
|
||||||
|
test-value";
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.Handler;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetAsync("http://localhost/ViewEngine/ViewWithPartial");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
|
Assert.Equal(expected, body.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
|
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
|
||||||
"RoutingWebSite": "",
|
"RoutingWebSite": "",
|
||||||
|
"RazorWebSite": "",
|
||||||
"ValueProvidersSite": "",
|
"ValueProvidersSite": "",
|
||||||
"Xunit.KRunner": "1.0.0-*"
|
"Xunit.KRunner": "1.0.0-*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="MvcRazorCodeParserTest.cs" />
|
<Compile Include="MvcRazorCodeParserTest.cs" />
|
||||||
<Compile Include="RazorCompilationServiceTest.cs" />
|
<Compile Include="RazorCompilationServiceTest.cs" />
|
||||||
<Compile Include="RazorViewActivatorTest.cs" />
|
<Compile Include="RazorPageActivatorTest.cs" />
|
||||||
<Compile Include="RazorViewEngineTest.cs" />
|
<Compile Include="RazorViewEngineTest.cs" />
|
||||||
|
<Compile Include="RazorPageTest.cs" />
|
||||||
<Compile Include="RazorViewTest.cs" />
|
<Compile Include="RazorViewTest.cs" />
|
||||||
<Compile Include="SpanFactory.cs" />
|
<Compile Include="SpanFactory.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public class RazorViewActivatorTest
|
public class RazorPageActivatorTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Activate_ActivatesAndContextualizesPropertiesOnViews()
|
public void Activate_ActivatesAndContextualizesPropertiesOnViews()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var activator = new RazorViewActivator(Mock.Of<ITypeActivator>());
|
var activator = new RazorPageActivator(Mock.Of<ITypeActivator>());
|
||||||
var instance = new TestView();
|
var instance = new TestRazorPage();
|
||||||
|
|
||||||
var myService = new MyService();
|
var myService = new MyService();
|
||||||
var helper = Mock.Of<IHtmlHelper<object>>();
|
var helper = Mock.Of<IHtmlHelper<object>>();
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var routeContext = new RouteContext(httpContext.Object);
|
var routeContext = new RouteContext(httpContext.Object);
|
||||||
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
||||||
var viewContext = new ViewContext(actionContext,
|
var viewContext = new ViewContext(actionContext,
|
||||||
instance,
|
Mock.Of<IView>(),
|
||||||
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
|
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
||||||
|
|
@ -55,8 +55,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public void Activate_ThrowsIfTheViewDoesNotDeriveFromRazorViewOfT()
|
public void Activate_ThrowsIfTheViewDoesNotDeriveFromRazorViewOfT()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var activator = new RazorViewActivator(Mock.Of<ITypeActivator>());
|
var activator = new RazorPageActivator(Mock.Of<ITypeActivator>());
|
||||||
var instance = new DoesNotDeriveFromRazorViewOfT();
|
var instance = new DoesNotDeriveFromRazorPageOfT();
|
||||||
|
|
||||||
var myService = new MyService();
|
var myService = new MyService();
|
||||||
var helper = Mock.Of<IHtmlHelper<object>>();
|
var helper = Mock.Of<IHtmlHelper<object>>();
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var routeContext = new RouteContext(httpContext.Object);
|
var routeContext = new RouteContext(httpContext.Object);
|
||||||
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
||||||
var viewContext = new ViewContext(actionContext,
|
var viewContext = new ViewContext(actionContext,
|
||||||
instance,
|
Mock.Of<IView>(),
|
||||||
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
|
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var message = string.Format(CultureInfo.InvariantCulture,
|
var message = string.Format(CultureInfo.InvariantCulture,
|
||||||
"View of type '{0}' cannot be activated by '{1}'.",
|
"View of type '{0}' cannot be activated by '{1}'.",
|
||||||
instance.GetType().FullName,
|
instance.GetType().FullName,
|
||||||
typeof(RazorViewActivator).FullName);
|
typeof(RazorPageActivator).FullName);
|
||||||
|
|
||||||
Assert.Equal(message, ex.Message);
|
Assert.Equal(message, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +86,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var typeActivator = new TypeActivator();
|
var typeActivator = new TypeActivator();
|
||||||
var activator = new RazorViewActivator(typeActivator);
|
var activator = new RazorPageActivator(typeActivator);
|
||||||
var instance = new TestView();
|
var instance = new TestRazorPage();
|
||||||
|
|
||||||
var myService = new MyService();
|
var myService = new MyService();
|
||||||
var helper = Mock.Of<IHtmlHelper<object>>();
|
var helper = Mock.Of<IHtmlHelper<object>>();
|
||||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
Model = new MyModel()
|
Model = new MyModel()
|
||||||
};
|
};
|
||||||
var viewContext = new ViewContext(actionContext,
|
var viewContext = new ViewContext(actionContext,
|
||||||
instance,
|
Mock.Of<IView>(),
|
||||||
viewData,
|
viewData,
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
||||||
|
|
@ -122,8 +122,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var typeActivator = new TypeActivator();
|
var typeActivator = new TypeActivator();
|
||||||
var activator = new RazorViewActivator(typeActivator);
|
var activator = new RazorPageActivator(typeActivator);
|
||||||
var instance = new TestView();
|
var instance = new TestRazorPage();
|
||||||
var myService = new MyService();
|
var myService = new MyService();
|
||||||
var helper = Mock.Of<IHtmlHelper<object>>();
|
var helper = Mock.Of<IHtmlHelper<object>>();
|
||||||
var serviceProvider = new Mock<IServiceProvider>();
|
var serviceProvider = new Mock<IServiceProvider>();
|
||||||
|
|
@ -141,7 +141,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
Model = new MyModel()
|
Model = new MyModel()
|
||||||
};
|
};
|
||||||
var viewContext = new ViewContext(actionContext,
|
var viewContext = new ViewContext(actionContext,
|
||||||
instance,
|
Mock.Of<IView>(),
|
||||||
viewData,
|
viewData,
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
||||||
|
|
@ -157,8 +157,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var typeActivator = new TypeActivator();
|
var typeActivator = new TypeActivator();
|
||||||
var activator = new RazorViewActivator(typeActivator);
|
var activator = new RazorPageActivator(typeActivator);
|
||||||
var instance = new DoesNotDeriveFromRazorViewOfTButHasModelProperty();
|
var instance = new DoesNotDeriveFromRazorPageOfTButHasModelProperty();
|
||||||
var myService = new MyService();
|
var myService = new MyService();
|
||||||
var helper = Mock.Of<IHtmlHelper<object>>();
|
var helper = Mock.Of<IHtmlHelper<object>>();
|
||||||
var serviceProvider = new Mock<IServiceProvider>();
|
var serviceProvider = new Mock<IServiceProvider>();
|
||||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
|
||||||
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>());
|
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>());
|
||||||
var viewContext = new ViewContext(actionContext,
|
var viewContext = new ViewContext(actionContext,
|
||||||
instance,
|
Mock.Of<IView>(),
|
||||||
viewData,
|
viewData,
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
Assert.IsType<ViewDataDictionary<string>>(viewContext.ViewData);
|
Assert.IsType<ViewDataDictionary<string>>(viewContext.ViewData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class TestViewBase<TModel> : RazorView<TModel>
|
private abstract class TestPageBase<TModel> : RazorPage<TModel>
|
||||||
{
|
{
|
||||||
[Activate]
|
[Activate]
|
||||||
public MyService MyService { get; set; }
|
public MyService MyService { get; set; }
|
||||||
|
|
@ -192,7 +192,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public MyService MyService2 { get; set; }
|
public MyService MyService2 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestView : TestViewBase<MyModel>
|
private class TestRazorPage : TestPageBase<MyModel>
|
||||||
{
|
{
|
||||||
[Activate]
|
[Activate]
|
||||||
internal IHtmlHelper<object> Html { get; private set; }
|
internal IHtmlHelper<object> Html { get; private set; }
|
||||||
|
|
@ -203,11 +203,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class DoesNotDeriveFromRazorViewOfTBase<TModel> : RazorView
|
private abstract class DoesNotDeriveFromRazorPageOfTBase<TModel> : RazorPage
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DoesNotDeriveFromRazorViewOfT : DoesNotDeriveFromRazorViewOfTBase<MyModel>
|
private class DoesNotDeriveFromRazorPageOfT : DoesNotDeriveFromRazorPageOfTBase<MyModel>
|
||||||
{
|
{
|
||||||
public override Task ExecuteAsync()
|
public override Task ExecuteAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -215,7 +215,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DoesNotDeriveFromRazorViewOfTButHasModelProperty : DoesNotDeriveFromRazorViewOfTBase<MyModel>
|
private class DoesNotDeriveFromRazorPageOfTButHasModelProperty : DoesNotDeriveFromRazorPageOfTBase<MyModel>
|
||||||
{
|
{
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNet.Testing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
{
|
||||||
|
public class RazorPageTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var viewContext = CreateViewContext();
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.DefineSection("qux", new HelperResult(action: null));
|
||||||
|
v.DefineSection("qux", new HelperResult(action: null));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
() => page.ExecuteAsync());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Section 'qux' is already defined.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderSection_RendersSectionFromPreviousPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var viewContext = CreateViewContext();
|
||||||
|
HelperResult actual = null;
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
actual = v.RenderSection("bar");
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "bar", expected }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderSection_ThrowsIfPreviousSectionWritersIsNotSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Exception ex = null;
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
ex = Assert.Throws<InvalidOperationException>(() => v.RenderSection("bar"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("The method 'RenderSection' cannot be invoked by this view.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderSection_ThrowsIfRequiredSectionIsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.RenderSection("bar");
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "baz", expected }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => page.ExecuteAsync());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Section 'bar' is not defined.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSectionDefined_ThrowsIfPreviousSectionWritersIsNotRegistered()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = CreatePage(v => { });
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
ExceptionAssert.Throws<InvalidOperationException>(() => page.IsSectionDefined("foo"),
|
||||||
|
"The method 'IsSectionDefined' cannot be invoked by this view.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IsSectionDefined_ReturnsFalseIfSectionNotDefined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
bool? actual = null;
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
actual = v.IsSectionDefined("foo");
|
||||||
|
v.RenderSection("baz");
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "baz", new HelperResult(writer => { }) }
|
||||||
|
};
|
||||||
|
page.BodyContent = "body-content";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(false, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IsSectionDefined_ReturnsTrueIfSectionDefined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
bool? actual = null;
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
actual = v.IsSectionDefined("baz");
|
||||||
|
v.RenderSection("baz");
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "baz", new HelperResult(writer => { }) }
|
||||||
|
};
|
||||||
|
page.BodyContent = "body-content";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(true, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.RenderSection("header");
|
||||||
|
v.RenderSection("header");
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "header", new HelperResult(writer => { }) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("RenderSection has already been called for the section named 'header'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EnsureBodyAndSectionsWereRendered_ThrowsIfDefinedSectionIsNotRendered()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.RenderSection("sectionA");
|
||||||
|
});
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{ "header", expected },
|
||||||
|
{ "footer", expected },
|
||||||
|
{ "sectionA", expected },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureBodyAndSectionsWereRendered());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("The following sections have been defined but have not been rendered: 'header, footer'.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EnsureBodyAndSectionsWereRendered_ThrowsIfRenderBodyIsNotCalledFromPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
page.BodyContent = "some content";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureBodyAndSectionsWereRendered());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_RendersSectionsAndBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = @"Layout start
|
||||||
|
Header section
|
||||||
|
body content
|
||||||
|
Footer section
|
||||||
|
Layout end
|
||||||
|
";
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.WriteLiteral("Layout start" + Environment.NewLine);
|
||||||
|
v.Write(v.RenderSection("header"));
|
||||||
|
v.Write(v.RenderBodyPublic());
|
||||||
|
v.Write(v.RenderSection("footer"));
|
||||||
|
v.WriteLiteral("Layout end" + Environment.NewLine);
|
||||||
|
|
||||||
|
});
|
||||||
|
page.BodyContent = "body content" + Environment.NewLine;
|
||||||
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"footer", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.WriteLine("Footer section");
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.WriteLine("Header section");
|
||||||
|
})
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actual = ((StringWriter)page.Output).ToString();
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestableRazorPage CreatePage(Action<TestableRazorPage> executeAction)
|
||||||
|
{
|
||||||
|
var view = new Mock<TestableRazorPage> { CallBase = true };
|
||||||
|
if (executeAction != null)
|
||||||
|
{
|
||||||
|
view.Setup(v => v.ExecuteAsync())
|
||||||
|
.Callback(() => executeAction(view.Object))
|
||||||
|
.Returns(Task.FromResult(0));
|
||||||
|
}
|
||||||
|
view.Object.ViewContext = CreateViewContext();
|
||||||
|
|
||||||
|
return view.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ViewContext CreateViewContext()
|
||||||
|
{
|
||||||
|
var actionContext = new ActionContext(Mock.Of<HttpContext>(), routeData: null, actionDescriptor: null);
|
||||||
|
return new ViewContext(
|
||||||
|
actionContext,
|
||||||
|
Mock.Of<IView>(),
|
||||||
|
null,
|
||||||
|
new StringWriter());
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class TestableRazorPage : RazorPage
|
||||||
|
{
|
||||||
|
public HtmlString RenderBodyPublic()
|
||||||
|
{
|
||||||
|
return base.RenderBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -162,10 +162,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
|
|
||||||
private IViewEngine CreateSearchLocationViewEngineTester()
|
private IViewEngine CreateSearchLocationViewEngineTester()
|
||||||
{
|
{
|
||||||
var virtualPathFactory = new Mock<IVirtualPathViewFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
virtualPathFactory.Setup(vpf => vpf.CreateInstance(It.IsAny<string>())).Returns<IView>(null);
|
pageFactory.Setup(vpf => vpf.CreateInstance(It.IsAny<string>()))
|
||||||
|
.Returns<RazorPage>(null);
|
||||||
|
|
||||||
var viewEngine = new RazorViewEngine(virtualPathFactory.Object);
|
var pageActivator = Mock.Of<IRazorPageActivator>();
|
||||||
|
|
||||||
|
var viewEngine = new RazorViewEngine(pageFactory.Object, pageActivator);
|
||||||
|
|
||||||
return viewEngine;
|
return viewEngine;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,317 +5,385 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Testing;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor.Test
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public class RazorViewTest
|
public class RazorViewTest
|
||||||
{
|
{
|
||||||
private const string LayoutPath = "~/Shared/_Layout.cshtml";
|
private const string LayoutPath = "~/Shared/_Layout.cshtml";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
public async Task RenderAsync_WithoutHierarchy_DoesNotCreateOutputBuffer()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var view = CreateView(v =>
|
TextWriter actual = null;
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
v.DefineSection("qux", new HelperResult(action: null));
|
actual = v.Output;
|
||||||
v.DefineSection("qux", new HelperResult(action: null));
|
v.Write("Hello world");
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView: null);
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: false);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
var expected = viewContext.Writer;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
await view.RenderAsync(viewContext);
|
||||||
() => view.RenderAsync(viewContext));
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Section 'qux' is already defined.", ex.Message);
|
Assert.Same(expected, actual);
|
||||||
|
Assert.Equal("Hello world", expected.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RenderSection_RendersSectionFromPreviousPage()
|
public async Task RenderAsync_WithoutHierarchy_ActivatesViews_WithACopyOfViewContext()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>());
|
||||||
HelperResult actual = null;
|
var page = new TestableRazorPage(v =>
|
||||||
var view = CreateView(v =>
|
{
|
||||||
|
// viewData is assigned to ViewContext by the activator
|
||||||
|
Assert.Same(viewData, v.ViewContext.ViewData);
|
||||||
|
});
|
||||||
|
var activator = new Mock<IRazorPageActivator>();
|
||||||
|
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
activator.Object,
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: false);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
var expectedViewData = viewContext.ViewData;
|
||||||
|
var expectedWriter = viewContext.Writer;
|
||||||
|
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||||
|
.Callback((RazorPage p, ViewContext c) =>
|
||||||
|
{
|
||||||
|
Assert.NotSame(c, viewContext);
|
||||||
|
c.ViewData = viewData;
|
||||||
|
})
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
activator.Verify();
|
||||||
|
Assert.Same(expectedViewData, viewContext.ViewData);
|
||||||
|
Assert.Same(expectedWriter, viewContext.Writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithoutHierarchy_ActivatesViews()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = new TestableRazorPage(v => { });
|
||||||
|
var activator = new Mock<IRazorPageActivator>();
|
||||||
|
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
activator.Object,
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: false);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
activator.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithoutHierarchy_DoesNotExecuteLayoutPages()
|
||||||
|
{
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
v.DefineSection("bar", expected);
|
|
||||||
v.Layout = LayoutPath;
|
v.Layout = LayoutPath;
|
||||||
});
|
});
|
||||||
var layoutView = CreateView(v =>
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
var view = new RazorView(pageFactory.Object,
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: false);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithHierarchy_CreatesOutputBuffer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
TextWriter actual = null;
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
actual = v.Output;
|
||||||
|
});
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
var original = viewContext.Writer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<StringWriter>(actual);
|
||||||
|
Assert.NotSame(original, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithHierarchy_CopiesBufferedContentToOutput()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.WriteLiteral("Hello world");
|
||||||
|
});
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
var original = viewContext.Writer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Hello world", original.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithHierarchy_ActivatesPages()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.WriteLiteral("Hello world");
|
||||||
|
});
|
||||||
|
var activator = new Mock<IRazorPageActivator>();
|
||||||
|
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
activator.Object,
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
activator.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithHierarchy_ExecutesLayoutPages()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected =
|
||||||
|
@"layout-content
|
||||||
|
head-content
|
||||||
|
body-content
|
||||||
|
foot-content";
|
||||||
|
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.WriteLiteral("body-content");
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
v.DefineSection("head", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.Write("head-content");
|
||||||
|
}));
|
||||||
|
v.DefineSection("foot", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.Write("foot-content");
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
var layout = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.Write("layout-content" + Environment.NewLine);
|
||||||
|
v.Write(v.RenderSection("head"));
|
||||||
|
v.Write(Environment.NewLine);
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
v.Write(Environment.NewLine);
|
||||||
|
v.Write(v.RenderSection("foot"));
|
||||||
|
});
|
||||||
|
var activator = new Mock<IRazorPageActivator>();
|
||||||
|
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
activator.Setup(a => a.Activate(layout, It.IsAny<ViewContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
||||||
|
.Returns(layout);
|
||||||
|
|
||||||
|
var view = new RazorView(pageFactory.Object,
|
||||||
|
activator.Object,
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Verify the activator was invoked for the primary page and layout page.
|
||||||
|
activator.Verify();
|
||||||
|
Assert.Equal(expected, viewContext.Writer.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_WithHierarchy_ThrowsIfSectionsWereDefinedButNotRendered()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.DefineSection("head", new HelperResult(writer => { }));
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
v.DefineSection("foot", new HelperResult(writer => { }));
|
||||||
|
});
|
||||||
|
var layout = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
actual = v.RenderSection("bar");
|
|
||||||
v.RenderBodyPublic();
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView);
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
||||||
|
.Returns(layout);
|
||||||
|
|
||||||
// Act
|
var view = new RazorView(pageFactory.Object,
|
||||||
await view.RenderAsync(viewContext);
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
// Assert
|
executeViewHierarchy: true);
|
||||||
Assert.Same(actual, expected);
|
var viewContext = CreateViewContext(view);
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RenderSection_ThrowsIfNoPreviousPage()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
Exception ex = null;
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
ex = Assert.Throws<InvalidOperationException>(() => v.RenderSection("bar"));
|
|
||||||
});
|
|
||||||
var viewContext = CreateViewContext(layoutView: null);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await view.RenderAsync(viewContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("The method 'RenderSection' cannot be invoked by this view.",
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RenderSection_ThrowsIfRequiredSectionIsNotFound()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = new HelperResult(action: null);
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.DefineSection("baz", expected);
|
|
||||||
v.Layout = LayoutPath;
|
|
||||||
});
|
|
||||||
var layoutView = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.RenderSection("bar");
|
|
||||||
});
|
|
||||||
var viewContext = CreateViewContext(layoutView);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("Section 'bar' is not defined.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IsSectionDefined_ThrowsIfNoPreviousExecutingPage()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var view = CreateView(v => { });
|
|
||||||
var viewContext = CreateViewContext(layoutView: null);
|
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
ExceptionAssert.Throws<InvalidOperationException>(() => view.IsSectionDefined("foo"),
|
|
||||||
"The method 'IsSectionDefined' cannot be invoked by this view.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task IsSectionDefined_ReturnsFalseIfSectionNotDefined()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
bool? actual = null;
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.DefineSection("baz", new HelperResult(writer => { }));
|
|
||||||
v.Layout = LayoutPath;
|
|
||||||
});
|
|
||||||
var layoutView = CreateView(v =>
|
|
||||||
{
|
|
||||||
actual = v.IsSectionDefined("foo");
|
|
||||||
v.RenderSection("baz");
|
|
||||||
v.RenderBodyPublic();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await view.RenderAsync(CreateViewContext(layoutView));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(false, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task IsSectionDefined_ReturnsTrueIfSectionDefined()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
bool? actual = null;
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.DefineSection("baz", new HelperResult(writer => { }));
|
|
||||||
v.Layout = LayoutPath;
|
|
||||||
});
|
|
||||||
var layoutView = CreateView(v =>
|
|
||||||
{
|
|
||||||
actual = v.IsSectionDefined("baz");
|
|
||||||
v.RenderSection("baz");
|
|
||||||
v.RenderBodyPublic();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await view.RenderAsync(CreateViewContext(layoutView));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(true, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = new HelperResult(action: null);
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.DefineSection("header", expected);
|
|
||||||
v.Layout = LayoutPath;
|
|
||||||
});
|
|
||||||
var layoutView = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.RenderSection("header");
|
|
||||||
v.RenderSection("header");
|
|
||||||
});
|
|
||||||
var viewContext = CreateViewContext(layoutView);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
Assert.Equal("The following sections have been defined but have not been rendered: 'head, foot'.", ex.Message);
|
||||||
// Assert
|
|
||||||
Assert.Equal("RenderSection has already been called for the section named 'header'.", ex.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RenderAsync_ThrowsIfDefinedSectionIsNotRendered()
|
public async Task RenderAsync_WithHierarchy_ThrowsIfBodyWasNotRendered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var page = new TestableRazorPage(v =>
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.DefineSection("header", expected);
|
|
||||||
v.DefineSection("footer", expected);
|
|
||||||
v.DefineSection("sectionA", expected);
|
|
||||||
v.Layout = LayoutPath;
|
|
||||||
});
|
|
||||||
var layoutView = CreateView(v =>
|
|
||||||
{
|
|
||||||
v.RenderSection("sectionA");
|
|
||||||
v.RenderBodyPublic();
|
|
||||||
});
|
|
||||||
var viewContext = CreateViewContext(layoutView);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("The following sections have been defined but have not been rendered: 'header, footer'.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RenderAsync_ThrowsIfRenderBodyIsNotCalledFromPage()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = new HelperResult(action: null);
|
|
||||||
var view = CreateView(v =>
|
|
||||||
{
|
{
|
||||||
v.Layout = LayoutPath;
|
v.Layout = LayoutPath;
|
||||||
});
|
});
|
||||||
var layoutView = CreateView(v =>
|
var layout = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView);
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
||||||
|
.Returns(layout);
|
||||||
|
|
||||||
// Act
|
var view = new RazorView(pageFactory.Object,
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
|
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RenderAsync_RendersSectionsAndBody()
|
public async Task RenderAsync_WithHierarchy_ExecutesNestedLayoutPages()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = @"Layout start
|
var expected =
|
||||||
Header section
|
@"layout-2
|
||||||
body content
|
bar-content
|
||||||
Footer section
|
layout-1
|
||||||
Layout end
|
foo-content
|
||||||
";
|
body-content";
|
||||||
var view = CreateView(v =>
|
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
v.Layout = LayoutPath;
|
v.DefineSection("foo", new HelperResult(writer =>
|
||||||
v.WriteLiteral("body content" + Environment.NewLine);
|
|
||||||
|
|
||||||
v.DefineSection("footer", new HelperResult(writer =>
|
|
||||||
{
|
{
|
||||||
writer.WriteLine("Footer section");
|
writer.WriteLine("foo-content");
|
||||||
}));
|
|
||||||
|
|
||||||
v.DefineSection("header", new HelperResult(writer =>
|
|
||||||
{
|
|
||||||
writer.WriteLine("Header section");
|
|
||||||
}));
|
}));
|
||||||
|
v.Layout = "~/Shared/Layout1.cshtml";
|
||||||
|
v.WriteLiteral("body-content");
|
||||||
});
|
});
|
||||||
var layoutView = CreateView(v =>
|
var layout1 = new TestableRazorPage(v =>
|
||||||
{
|
{
|
||||||
v.WriteLiteral("Layout start" + Environment.NewLine);
|
v.Write("layout-1" + Environment.NewLine);
|
||||||
v.Write(v.RenderSection("header"));
|
v.Write(v.RenderSection("foo"));
|
||||||
v.Write(v.RenderBodyPublic());
|
v.DefineSection("bar", new HelperResult(writer =>
|
||||||
v.Write(v.RenderSection("footer"));
|
{
|
||||||
v.WriteLiteral("Layout end" + Environment.NewLine);
|
writer.WriteLine("bar-content");
|
||||||
|
}));
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
v.Layout = "~/Shared/Layout2.cshtml";
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView);
|
var layout2 = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.Write("layout-2" + Environment.NewLine);
|
||||||
|
v.Write(v.RenderSection("bar"));
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
});
|
||||||
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
|
||||||
|
.Returns(layout1);
|
||||||
|
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml"))
|
||||||
|
.Returns(layout2);
|
||||||
|
|
||||||
|
var view = new RazorView(pageFactory.Object,
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
page,
|
||||||
|
executeViewHierarchy: true);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await view.RenderAsync(viewContext);
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actual = ((StringWriter)viewContext.Writer).ToString();
|
Assert.Equal(expected, viewContext.Writer.ToString());
|
||||||
Assert.Equal(expected, actual);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TestableRazorView CreateView(Action<TestableRazorView> executeAction)
|
private static ViewContext CreateViewContext(RazorView view)
|
||||||
{
|
{
|
||||||
var view = new Mock<TestableRazorView> { CallBase = true };
|
|
||||||
if (executeAction != null)
|
|
||||||
{
|
|
||||||
view.Setup(v => v.ExecuteAsync())
|
|
||||||
.Callback(() => executeAction(view.Object))
|
|
||||||
.Returns(Task.FromResult(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return view.Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ViewContext CreateViewContext(IView layoutView)
|
|
||||||
{
|
|
||||||
var viewFactory = new Mock<IVirtualPathViewFactory>();
|
|
||||||
viewFactory.Setup(v => v.CreateInstance(LayoutPath))
|
|
||||||
.Returns(layoutView);
|
|
||||||
var serviceProvider = new Mock<IServiceProvider>();
|
|
||||||
serviceProvider.Setup(f => f.GetService(typeof(IVirtualPathViewFactory)))
|
|
||||||
.Returns(viewFactory.Object);
|
|
||||||
|
|
||||||
var httpContext = new Mock<HttpContext>();
|
var httpContext = new Mock<HttpContext>();
|
||||||
httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider.Object);
|
var actionContext = new ActionContext(httpContext.Object, routeData: null, actionDescriptor: null);
|
||||||
|
|
||||||
var actionContext = new ActionContext(httpContext.Object, null, null);
|
|
||||||
return new ViewContext(
|
return new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
layoutView,
|
view,
|
||||||
null,
|
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
|
||||||
new StringWriter());
|
new StringWriter());
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TestableRazorView : RazorView
|
private class TestableRazorPage : RazorPage
|
||||||
{
|
{
|
||||||
public HtmlString RenderBodyPublic()
|
private readonly Action<TestableRazorPage> _executeAction;
|
||||||
|
|
||||||
|
public TestableRazorPage(Action<TestableRazorPage> executeAction)
|
||||||
{
|
{
|
||||||
return base.RenderBody();
|
_executeAction = executeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenderBodyPublic()
|
||||||
|
{
|
||||||
|
Write(RenderBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
_executeAction(this);
|
||||||
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
namespace RazorWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class ViewEngineController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult ViewWithoutLayout()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ViewWithFullPath()
|
||||||
|
{
|
||||||
|
return View(@"/Views/ViewEngine/ViewWithFullPath.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ViewWithLayout()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ViewWithNestedLayout()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ViewWithPartial()
|
||||||
|
{
|
||||||
|
ViewData["TestKey"] = "test-value";
|
||||||
|
var model = new Person
|
||||||
|
{
|
||||||
|
Address = new Address { ZipCode = "98052" }
|
||||||
|
};
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace RazorWebSite
|
||||||
|
{
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
public string ZipCode { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace RazorWebSite
|
||||||
|
{
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Address Address { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Mvc": "",
|
||||||
|
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc.TestConfiguration": ""
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net45": { },
|
||||||
|
"k10": { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>b07caf59-11ed-40e3-a5db-e1178f84fa78</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Console'">
|
||||||
|
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Web'">
|
||||||
|
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Project.json" />
|
||||||
|
<Content Include="Views\Shared\_Partial.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\ViewWithNestedLayout.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\ViewWithLayout.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\ViewWithFullPath.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\ViewWithPartial.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\ViewWithoutLayout.cshtml" />
|
||||||
|
<Content Include="Views\Shared\_Layout.cshtml" />
|
||||||
|
<Content Include="Views\ViewEngine\_NestedLayout.cshtml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Controllers\ViewEngineController.cs" />
|
||||||
|
<Compile Include="Models\Person.cs" />
|
||||||
|
<Compile Include="Models\Address.cs" />
|
||||||
|
<Compile Include="Startup.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace RazorWebSite
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = app.GetTestConfiguration();
|
||||||
|
|
||||||
|
// Set up application services
|
||||||
|
app.UseServices(services =>
|
||||||
|
{
|
||||||
|
// Add MVC services to the services container
|
||||||
|
services.AddMvc(configuration);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add MVC to the request pipeline
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<layout>
|
||||||
|
@RenderBody()
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@model RazorWebSite.Address
|
||||||
|
@ViewData.Model.ZipCode
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
ViewWithFullPath-content
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
ViewWithLayout-Content
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/ViewEngine/_NestedLayout.cshtml";
|
||||||
|
}
|
||||||
|
ViewWithNestedLayout-Content
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
@using RazorWebSite
|
||||||
|
@model Person
|
||||||
|
<partial>@await Html.PartialAsync("_Partial", Model.Address)
|
||||||
|
</partial>
|
||||||
|
@ViewBag.TestKey
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ViewWithoutLayout-Content
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@{
|
||||||
|
Layout = "/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
<nested-layout>
|
||||||
|
@Url.Action()
|
||||||
|
@RenderBody()
|
||||||
|
</nested-layout>
|
||||||
Loading…
Reference in New Issue