Adding RazorView to Microsoft.AspNet.Mvc.Razor
RazorView was part of the previous commit but was separated to make it easier to see the diff in RazorPage that was formerly named RazorView Adding IRazorPage and changes per code review comments
This commit is contained in:
parent
9e535f6897
commit
340bd7550a
|
|
@ -0,0 +1,48 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents properties and methods that are used by <see cref="RazorView"/> for execution.
|
||||
/// </summary>
|
||||
public interface IRazorPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the view context of the renderign view.
|
||||
/// </summary>
|
||||
ViewContext ViewContext { get; set; }
|
||||
|
||||
string BodyContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of a layout page.
|
||||
/// </summary>
|
||||
string Layout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sections that can be rendered by this page.
|
||||
/// </summary>
|
||||
Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sections that are defined by this page.
|
||||
/// </summary>
|
||||
Dictionary<string, HelperResult> SectionWriters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Renders the page and writes the output to the <see cref="ViewContext.Writer"/>.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the result of executing the page.</returns>
|
||||
Task ExecuteAsync();
|
||||
|
||||
/// <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>
|
||||
void EnsureBodyAndSectionsWereRendered();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to activate properties on a <see cref="RazorPage"/> instance.
|
||||
/// Provides methods to activate properties on a <see cref="IRazorPage"/> instance.
|
||||
/// </summary>
|
||||
public interface IRazorPageActivator
|
||||
{
|
||||
|
|
@ -13,6 +13,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// </summary>
|
||||
/// <param name="page">The page to activate.</param>
|
||||
/// <param name="context">The <see cref="ViewContext"/> for the executing view.</param>
|
||||
void Activate(RazorPage page, ViewContext context);
|
||||
void Activate(IRazorPage page, ViewContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,15 @@
|
|||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods that are used for creating <see cref="RazorPage"/> instances at a given path.
|
||||
/// Defines methods that are used for creating <see cref="IRazorPage"/> instances at a given path.
|
||||
/// </summary>
|
||||
public interface IRazorPageFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RazorPage"/> for the specified path.
|
||||
/// Creates a <see cref="IRazorPage"/> 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);
|
||||
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
|
||||
IRazorPage CreateInstance(string viewPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
<Compile Include="Compilation\ICompilationService.cs" />
|
||||
<Compile Include="Compilation\RoslynCompilationService.cs" />
|
||||
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
||||
<Compile Include="FileBasedRazorPageFactory.cs" />
|
||||
<Compile Include="IRazorPage.cs" />
|
||||
<Compile Include="VirtualPathRazorPageFactory.cs" />
|
||||
<Compile Include="HelperResult.cs" />
|
||||
<Compile Include="IRazorPageActivator.cs" />
|
||||
<Compile Include="IRazorPageFactory.cs" />
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
|
||||
/// </summary>
|
||||
public abstract class RazorPage
|
||||
public abstract class RazorPage : IRazorPage
|
||||
{
|
||||
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private bool _renderedBody;
|
||||
|
|
@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ViewContext ViewContext { get; set; }
|
||||
|
||||
public string Layout { get; set; }
|
||||
|
|
@ -86,10 +87,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
public string BodyContent { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, HelperResult> SectionWriters { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task ExecuteAsync();
|
||||
|
||||
public virtual void Write(object value)
|
||||
|
|
@ -292,10 +296,7 @@ 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>
|
||||
/// <inheritdoc />
|
||||
public void EnsureBodyAndSectionsWereRendered()
|
||||
{
|
||||
// If PreviousSectionWriters is set, ensure all defined sections were rendered.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Activate([NotNull] RazorPage page, [NotNull] ViewContext context)
|
||||
public void Activate([NotNull] IRazorPage page, [NotNull] ViewContext context)
|
||||
{
|
||||
var activationInfo = _activationInfo.GetOrAdd(page.GetType(),
|
||||
CreateViewActivationInfo);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IView"/> that executes one or more <see cref="RazorPage"/> instances as part of
|
||||
/// view rendering.
|
||||
/// </summary>
|
||||
public class RazorView : IView
|
||||
{
|
||||
private readonly IRazorPageFactory _pageFactory;
|
||||
private readonly IRazorPageActivator _pageActivator;
|
||||
private readonly IRazorPage _page;
|
||||
private readonly bool _executeViewHierarchy;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of RazorView
|
||||
/// </summary>
|
||||
/// <param name="page">The page to execute</param>
|
||||
/// <param name="pageFactory">The view factory used to instantiate additional views.</param>
|
||||
/// <param name="pageActivator">The <see cref="IRazorPageActivator"/> used to activate pages.</param>
|
||||
/// <param name="executeViewHierarchy">A value that indiciates whether the view hierarchy that involves
|
||||
/// view start and layout pages are executed as part of the executing the page.</param>
|
||||
public RazorView([NotNull] IRazorPageFactory pageFactory,
|
||||
[NotNull] IRazorPageActivator pageActivator,
|
||||
[NotNull] IRazorPage page,
|
||||
bool executeViewHierarchy)
|
||||
{
|
||||
_pageFactory = pageFactory;
|
||||
_pageActivator = pageActivator;
|
||||
_page = page;
|
||||
_executeViewHierarchy = executeViewHierarchy;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RenderAsync([NotNull] ViewContext context)
|
||||
{
|
||||
if (_executeViewHierarchy)
|
||||
{
|
||||
var bodyContent = await RenderPageAsync(_page, context);
|
||||
await RenderLayoutAsync(context, bodyContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RenderPageCoreAsync(_page, context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> RenderPageAsync(IRazorPage page, ViewContext context)
|
||||
{
|
||||
var contentBuilder = new StringBuilder(1024);
|
||||
using (var bodyWriter = new StringWriter(contentBuilder))
|
||||
{
|
||||
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
||||
// and ViewComponents to reference it.
|
||||
var oldWriter = context.Writer;
|
||||
context.Writer = bodyWriter;
|
||||
try
|
||||
{
|
||||
await RenderPageCoreAsync(page, context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Writer = oldWriter;
|
||||
}
|
||||
}
|
||||
|
||||
return contentBuilder.ToString();
|
||||
}
|
||||
|
||||
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
|
||||
{
|
||||
// Activating a page might mutate the ViewContext (for instance ViewContext.ViewData) is mutated by
|
||||
// RazorPageActivator. We'll instead pass in a copy of the ViewContext.
|
||||
var pageViewContext = new ViewContext(context, context.View, context.ViewData, context.Writer);
|
||||
page.ViewContext = pageViewContext;
|
||||
_pageActivator.Activate(page, pageViewContext);
|
||||
|
||||
await page.ExecuteAsync();
|
||||
}
|
||||
|
||||
private async Task RenderLayoutAsync(ViewContext context,
|
||||
string bodyContent)
|
||||
{
|
||||
// A layout page can specify another layout page. We'll need to continue
|
||||
// looking for layout pages until they're no longer specified.
|
||||
var previousPage = _page;
|
||||
while (!string.IsNullOrEmpty(previousPage.Layout))
|
||||
{
|
||||
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout);
|
||||
if (layoutPage == null)
|
||||
{
|
||||
var message = Resources.FormatLayoutCannotBeLocated(previousPage.Layout);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
||||
layoutPage.BodyContent = bodyContent;
|
||||
|
||||
bodyContent = await RenderPageAsync(layoutPage, context);
|
||||
|
||||
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
||||
layoutPage.EnsureBodyAndSectionsWereRendered();
|
||||
|
||||
previousPage = layoutPage;
|
||||
}
|
||||
|
||||
await context.Writer.WriteAsync(bodyContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private ViewEngineResult CreateFoundResult(RazorPage page, string viewName, bool partial)
|
||||
private ViewEngineResult CreateFoundResult(IRazorPage page, string viewName, bool partial)
|
||||
{
|
||||
var view = new RazorView(_pageFactory,
|
||||
_viewActivator,
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// Represents a <see cref="IRazorPageFactory"/> that creates <see cref="RazorPage"/> instances
|
||||
/// from razor files in the file system.
|
||||
/// </summary>
|
||||
public class FileBasedRazorPageFactory : IRazorPageFactory
|
||||
public class VirtualPathRazorPageFactory : IRazorPageFactory
|
||||
{
|
||||
private readonly IRazorCompilationService _compilationService;
|
||||
private readonly ITypeActivator _activator;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IFileInfoCache _fileInfoCache;
|
||||
|
||||
public FileBasedRazorPageFactory(IRazorCompilationService compilationService,
|
||||
ITypeActivator typeActivator,
|
||||
IServiceProvider serviceProvider,
|
||||
IFileInfoCache fileInfoCache)
|
||||
public VirtualPathRazorPageFactory(IRazorCompilationService compilationService,
|
||||
ITypeActivator typeActivator,
|
||||
IServiceProvider serviceProvider,
|
||||
IFileInfoCache fileInfoCache)
|
||||
{
|
||||
_compilationService = compilationService;
|
||||
_activator = typeActivator;
|
||||
|
|
@ -30,14 +30,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RazorPage CreateInstance([NotNull] string viewPath)
|
||||
public IRazorPage CreateInstance([NotNull] string viewPath)
|
||||
{
|
||||
var fileInfo = _fileInfoCache.GetFileInfo(viewPath.TrimStart('~'));
|
||||
var fileInfo = _fileInfoCache.GetFileInfo(viewPath);
|
||||
|
||||
if (fileInfo != null)
|
||||
{
|
||||
var result = _compilationService.Compile(fileInfo);
|
||||
var page = (RazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
||||
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
yield return describe.Singleton<IRazorPageActivator, RazorPageActivator>();
|
||||
// Virtual path view factory needs to stay scoped so views can get get scoped services.
|
||||
yield return describe.Scoped<IRazorPageFactory, FileBasedRazorPageFactory>();
|
||||
yield return describe.Scoped<IRazorPageFactory, VirtualPathRazorPageFactory>();
|
||||
yield return describe.Singleton<IFileInfoCache, ExpiringFileInfoCache>();
|
||||
|
||||
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
var expectedViewData = viewContext.ViewData;
|
||||
var expectedWriter = viewContext.Writer;
|
||||
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||
.Callback((RazorPage p, ViewContext c) =>
|
||||
.Callback((IRazorPage p, ViewContext c) =>
|
||||
{
|
||||
Assert.NotSame(c, viewContext);
|
||||
c.ViewData = viewData;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@{
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
ViewWithFullPath-content
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@{
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
ViewWithLayout-Content
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@{
|
||||
Layout = "~/Views/ViewEngine/_NestedLayout.cshtml";
|
||||
Layout = "/Views/ViewEngine/_NestedLayout.cshtml";
|
||||
}
|
||||
ViewWithNestedLayout-Content
|
||||
Loading…
Reference in New Issue