Changes to support PageExecutionContext in Razor

Fixes #1083
This commit is contained in:
Pranav K 2014-09-11 15:24:38 -07:00
parent a2023d35ee
commit 5d32d224f4
12 changed files with 225 additions and 19 deletions

View File

@ -6,8 +6,24 @@ using Microsoft.AspNet.Razor;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Specifies the contracts for a Razor host that parses Razor files and generates C# code.
/// </summary>
public interface IMvcRazorHost
{
/// <summary>
/// Flag that indicates if page execution instrumentation code should be injected into the output.
/// </summary>
bool EnableInstrumentation { get; set; }
/// <summary>
/// Parses and generates the contents of a Razor file represented by <paramref name="inputStream"/>.
/// </summary>
/// <param name="rootRelativePath">The path of the relative to the root of the application.
/// Used to generate line pragmas and calculate the class name of the generated type.</param>
/// <param name="inputStream">A <see cref="Stream"/> that represents the Razor contents.</param>
/// <returns>A <see cref="GeneratorResults"/> instance that represents the results of code generation.
/// </returns>
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
/// <summary>

View File

@ -82,7 +82,9 @@ namespace Microsoft.AspNet.Mvc.Razor
templateTypeName: "Microsoft.AspNet.Mvc.Razor.HelperResult",
defineSectionMethodName: "DefineSection")
{
ResolveUrlMethodName = "Href"
ResolveUrlMethodName = "Href",
BeginContextMethodName = "BeginContext",
EndContextMethodName = "EndContext"
};
foreach (var ns in _defaultNamespaces)

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.PageExecutionInstrumentation;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -44,6 +45,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
string Layout { get; set; }
/// <summary>
/// Gets or sets a <see cref="IPageExecutionContext"/> instance used to instrument the page execution.
/// </summary>
IPageExecutionContext PageExecutionContext { get; set; }
/// <summary>
/// Gets or sets the sections that can be rendered by this page.
/// </summary>

View File

@ -58,6 +58,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("FlushPointCannotBeInvoked"), p0);
}
/// <summary>
/// The {0} returned by '{1}' must be an instance of '{2}'.
/// </summary>
internal static string Instrumentation_WriterMustBeBufferedTextWriter
{
get { return GetString("Instrumentation_WriterMustBeBufferedTextWriter"); }
}
/// <summary>
/// The {0} returned by '{1}' must be an instance of '{2}'.
/// </summary>
internal static string FormatInstrumentation_WriterMustBeBufferedTextWriter(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Instrumentation_WriterMustBeBufferedTextWriter"), p0, p1, p2);
}
/// <summary>
/// The layout view '{0}' could not be located.
/// </summary>

View File

@ -10,6 +10,7 @@ using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.PageExecutionInstrumentation;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Razor
@ -47,8 +48,12 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <inheritdoc />
public ViewContext ViewContext { get; set; }
/// <inheritdoc />
public string Layout { get; set; }
/// <inheritdoc />
public IPageExecutionContext PageExecutionContext { get; set; }
/// <summary>
/// Gets the TextWriter that the page is writing output to.
/// </summary>
@ -268,6 +273,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Calculate length of the source span by the position of the next value (or suffix)
var sourceLength = next.Position - attrVal.Value.Position;
BeginContext(attrVal.Value.Position, sourceLength, isLiteral: attrVal.Literal);
// The extra branching here is to ensure that we call the Write*To(string) overload whe
// possible.
if (attrVal.Literal && stringValue != null)
@ -287,6 +293,7 @@ namespace Microsoft.AspNet.Mvc.Razor
WriteTo(writer, val.Value);
}
EndContext();
wroteSomething = true;
}
if (wroteSomething)
@ -308,7 +315,9 @@ namespace Microsoft.AspNet.Mvc.Razor
private void WritePositionTaggedLiteral(TextWriter writer, string value, int position)
{
BeginContext(position, value.Length, isLiteral: true);
WriteLiteralTo(writer, value);
EndContext();
}
private void WritePositionTaggedLiteral(TextWriter writer, PositionTagged<string> value)
@ -420,6 +429,16 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
public void BeginContext(int position, int length, bool isLiteral)
{
PageExecutionContext?.BeginContext(position, length, isLiteral);
}
public void EndContext()
{
PageExecutionContext?.EndContext();
}
private void EnsureMethodCanBeInvoked(string methodName)
{
if (PreviousSectionWriters == null)

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// This is primarily designed to avoid creating large in-memory strings.
/// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details.
/// </remarks>
public class RazorTextWriter : TextWriter
public class RazorTextWriter : TextWriter, IBufferedTextWriter
{
private static readonly Task _completedTask = Task.FromResult(0);
private readonly TextWriter _unbufferedWriter;
@ -43,9 +43,7 @@ namespace Microsoft.AspNet.Mvc.Razor
get { return _encoding; }
}
/// <summary>
/// Gets a flag that determines if this instance of <see cref="RazorTextWriter"/> is buffering content.
/// </summary>
/// <inheritdoc />
public bool IsBuffering { get; private set; } = true;
/// <summary>
@ -267,10 +265,7 @@ namespace Microsoft.AspNet.Mvc.Razor
await _unbufferedWriter.FlushAsync();
}
/// <summary>
/// Copies the content of the <see cref="RazorTextWriter"/> to the <see cref="TextWriter"/> instance.
/// </summary>
/// <param name="writer">The writer to copy contents to.</param>
/// <inheritdoc />
public void CopyTo(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
@ -286,11 +281,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
/// <summary>
/// Copies the content of the <see cref="RazorTextWriter"/> to the specified <see cref="TextWriter"/> instance.
/// </summary>
/// <param name="writer">The writer to copy contents to.</param>
/// <returns>A task that represents the asynchronous copy operation.</returns>
/// <inheritdoc />
public Task CopyToAsync(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;

View File

@ -126,6 +126,9 @@
<data name="FlushPointCannotBeInvoked" xml:space="preserve">
<value>'{0}' cannot be invoked when a Layout page is set to be executed.</value>
</data>
<data name="Instrumentation_WriterMustBeBufferedTextWriter" xml:space="preserve">
<value>The {0} returned by '{1}' must be an instance of '{2}'.</value>
</data>
<data name="LayoutCannotBeLocated" xml:space="preserve">
<value>The layout view '{0}' could not be located.</value>
</data>

View File

@ -0,0 +1,34 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Specifies the contracts for a <see cref="TextWriter"/> that buffers its content.
/// </summary>
[AssemblyNeutral]
public interface IBufferedTextWriter
{
/// <summary>
/// Gets a flag that determines if content is currently being buffered.
/// </summary>
bool IsBuffering { get; }
/// <summary>
/// Copies the buffered content to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The writer to copy the contents to./param>
void CopyTo(TextWriter writer);
/// <summary>
/// Asynchronously copies the buffered content to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The writer to copy the contents to./param>
/// <returns>A <see cref="Task"/> representing the copy operation.</returns>
Task CopyToAsync(TextWriter writer);
}
}

View File

@ -0,0 +1,28 @@
// 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.Framework.Runtime;
namespace Microsoft.AspNet.PageExecutionInstrumentation
{
/// <summary>
/// Specifies the contracts for a execution context that instruments web page execution.
/// </summary>
[AssemblyNeutral]
public interface IPageExecutionContext
{
/// <summary>
/// Invoked at the start of a write operation.
/// </summary>
/// <param name="position">The absolute character position of the expression or text in the Razor file.</param>
/// <param name="length">The character length of the expression or text in the Razor file.</param>
/// <param name="isLiteral">A flag that indicates if the operation is for a literal text and not for a
/// language expression.</param>
void BeginContext(int position, int length, bool isLiteral);
/// <summary>
/// Invoked at the end of a write operation.
/// </summary>
void EndContext();
}
}

View File

@ -0,0 +1,33 @@
// 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.IO;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.PageExecutionInstrumentation
{
/// <summary>
/// Specifies the contracts for a HTTP feature that provides the context to instrument a web page.
/// </summary>
[AssemblyNeutral]
public interface IPageExecutionListenerFeature
{
/// <summary>
/// Decorates the <see cref="TextWriter"/> used by web page instances to
/// write the result to.
/// </summary>
/// <param name="writer">The output <see cref="TextWriter"/> for the web page.</param>
/// <returns>A <see cref="TextWriter"/> that wraps <paramref name="writer"/>.</returns>
/// <remarks>
TextWriter DecorateWriter(TextWriter writer);
/// <summary>
/// Creates a <see cref="IPageExecutionContext"/> for the specified <paramref name="sourceFilePath"/>.
/// </summary>
/// <param name="sourceFilePath">The path of the <see cref="Mvc.Razor.IRazorPage"/>.</param>
/// <param name="writer">The <see cref="TextWriter"/> obtained from <see cref="DecorateWriter(TextWriter)"/>.
/// </param>
/// <returns></returns>
IPageExecutionContext GetContext(string sourceFilePath, TextWriter writer);
}
}

View File

@ -2,14 +2,10 @@
// 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 Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor

View File

@ -5,8 +5,8 @@ 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.PageExecutionInstrumentation;
using Microsoft.AspNet.PipelineCore;
using Microsoft.AspNet.Testing;
using Moq;
@ -351,6 +351,68 @@ Layout end
Assert.DoesNotThrow(() => page.SectionWriters["test-section"].WriteTo(TextWriter.Null));
}
[Fact]
public async Task WriteAttribute_CallsBeginAndEndContext_OnPageExecutionListenerContext()
{
// Arrange
var page = CreatePage(p =>
{
p.WriteAttribute("href",
new PositionTagged<string>("prefix", 0),
new PositionTagged<string>("suffix", 34),
new AttributeValue(new PositionTagged<string>("prefix", 0),
new PositionTagged<object>("attr1-value", 8),
literal: true),
new AttributeValue(new PositionTagged<string>("prefix2", 22),
new PositionTagged<object>("attr2", 29),
literal: false));
});
var context = new Mock<IPageExecutionContext>(MockBehavior.Strict);
var sequence = new MockSequence();
context.InSequence(sequence).Setup(f => f.BeginContext(0, 6, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
context.InSequence(sequence).Setup(f => f.BeginContext(8, 14, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
context.InSequence(sequence).Setup(f => f.BeginContext(22, 7, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
context.InSequence(sequence).Setup(f => f.BeginContext(29, 5, false)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
context.InSequence(sequence).Setup(f => f.BeginContext(34, 6, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
page.PageExecutionContext = context.Object;
// Act
await page.ExecuteAsync();
// Assert
context.Verify();
}
[Fact]
public async Task WriteAttribute_CallsBeginAndEndContext_OnPrefixAndSuffixValues()
{
// Arrange
var page = CreatePage(p =>
{
p.WriteAttribute("href",
new PositionTagged<string>("prefix", 0),
new PositionTagged<string>("tail", 7));
});
var context = new Mock<IPageExecutionContext>(MockBehavior.Strict);
var sequence = new MockSequence();
context.InSequence(sequence).Setup(f => f.BeginContext(0, 6, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
context.InSequence(sequence).Setup(f => f.BeginContext(7, 4, true)).Verifiable();
context.InSequence(sequence).Setup(f => f.EndContext()).Verifiable();
page.PageExecutionContext = context.Object;
// Act
await page.ExecuteAsync();
// Assert
context.Verify();
}
private static TestableRazorPage CreatePage(Action<TestableRazorPage> executeAction,
ViewContext context = null)
{