diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/IMvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/IMvcRazorHost.cs
index 1011b242dc..79a0b2d700 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/IMvcRazorHost.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/IMvcRazorHost.cs
@@ -6,8 +6,24 @@ using Microsoft.AspNet.Razor;
namespace Microsoft.AspNet.Mvc.Razor
{
+ ///
+ /// Specifies the contracts for a Razor host that parses Razor files and generates C# code.
+ ///
public interface IMvcRazorHost
{
+ ///
+ /// Flag that indicates if page execution instrumentation code should be injected into the output.
+ ///
+ bool EnableInstrumentation { get; set; }
+
+ ///
+ /// Parses and generates the contents of a Razor file represented by .
+ ///
+ /// 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.
+ /// A that represents the Razor contents.
+ /// A instance that represents the results of code generation.
+ ///
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
index 0c54131bbe..598cf8cfd7 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
@@ -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)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
index f79fc0b351..4b3bdbd373 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
@@ -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
///
string Layout { get; set; }
+ ///
+ /// Gets or sets a instance used to instrument the page execution.
+ ///
+ IPageExecutionContext PageExecutionContext { get; set; }
+
///
/// Gets or sets the sections that can be rendered by this page.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
index 1d07b517ca..b897a77c8f 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -58,6 +58,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("FlushPointCannotBeInvoked"), p0);
}
+ ///
+ /// The {0} returned by '{1}' must be an instance of '{2}'.
+ ///
+ internal static string Instrumentation_WriterMustBeBufferedTextWriter
+ {
+ get { return GetString("Instrumentation_WriterMustBeBufferedTextWriter"); }
+ }
+
+ ///
+ /// The {0} returned by '{1}' must be an instance of '{2}'.
+ ///
+ internal static string FormatInstrumentation_WriterMustBeBufferedTextWriter(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Instrumentation_WriterMustBeBufferedTextWriter"), p0, p1, p2);
+ }
+
///
/// The layout view '{0}' could not be located.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index a1bddb1057..4e6abbcf78 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -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
///
public ViewContext ViewContext { get; set; }
+ ///
public string Layout { get; set; }
+ ///
+ public IPageExecutionContext PageExecutionContext { get; set; }
+
///
/// Gets the TextWriter that the page is writing output to.
///
@@ -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 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)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs
index ba06edc82f..9ea894453c 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs
@@ -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.
///
- 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; }
}
- ///
- /// Gets a flag that determines if this instance of is buffering content.
- ///
+ ///
public bool IsBuffering { get; private set; } = true;
///
@@ -267,10 +265,7 @@ namespace Microsoft.AspNet.Mvc.Razor
await _unbufferedWriter.FlushAsync();
}
- ///
- /// Copies the content of the to the instance.
- ///
- /// The writer to copy contents to.
+ ///
public void CopyTo(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
@@ -286,11 +281,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- ///
- /// Copies the content of the to the specified instance.
- ///
- /// The writer to copy contents to.
- /// A task that represents the asynchronous copy operation.
+ ///
public Task CopyToAsync(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index 9d0f92e270..e2cfc45b7b 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -126,6 +126,9 @@
'{0}' cannot be invoked when a Layout page is set to be executed.
+
+ The {0} returned by '{1}' must be an instance of '{2}'.
+
The layout view '{0}' could not be located.
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Services/IBufferedTextWriter.cs b/src/Microsoft.AspNet.Mvc.Razor/Services/IBufferedTextWriter.cs
new file mode 100644
index 0000000000..c0fbae88eb
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Services/IBufferedTextWriter.cs
@@ -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
+{
+ ///
+ /// Specifies the contracts for a that buffers its content.
+ ///
+ [AssemblyNeutral]
+ public interface IBufferedTextWriter
+ {
+ ///
+ /// Gets a flag that determines if content is currently being buffered.
+ ///
+ bool IsBuffering { get; }
+
+ ///
+ /// Copies the buffered content to the .
+ ///
+ /// The writer to copy the contents to./param>
+ void CopyTo(TextWriter writer);
+
+ ///
+ /// Asynchronously copies the buffered content to the .
+ ///
+ /// The writer to copy the contents to./param>
+ /// A representing the copy operation.
+ Task CopyToAsync(TextWriter writer);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionContext.cs b/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionContext.cs
new file mode 100644
index 0000000000..51990d628f
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionContext.cs
@@ -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
+{
+ ///
+ /// Specifies the contracts for a execution context that instruments web page execution.
+ ///
+ [AssemblyNeutral]
+ public interface IPageExecutionContext
+ {
+ ///
+ /// Invoked at the start of a write operation.
+ ///
+ /// The absolute character position of the expression or text in the Razor file.
+ /// The character length of the expression or text in the Razor file.
+ /// A flag that indicates if the operation is for a literal text and not for a
+ /// language expression.
+ void BeginContext(int position, int length, bool isLiteral);
+
+ ///
+ /// Invoked at the end of a write operation.
+ ///
+ void EndContext();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionListenerFeature.cs b/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionListenerFeature.cs
new file mode 100644
index 0000000000..70e80076b6
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Services/IPageExecutionListenerFeature.cs
@@ -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
+{
+ ///
+ /// Specifies the contracts for a HTTP feature that provides the context to instrument a web page.
+ ///
+ [AssemblyNeutral]
+ public interface IPageExecutionListenerFeature
+ {
+ ///
+ /// Decorates the used by web page instances to
+ /// write the result to.
+ ///
+ /// The output for the web page.
+ /// A that wraps .
+ ///
+ TextWriter DecorateWriter(TextWriter writer);
+
+ ///
+ /// Creates a for the specified .
+ ///
+ /// The path of the .
+ /// The obtained from .
+ ///
+ ///
+ IPageExecutionContext GetContext(string sourceFilePath, TextWriter writer);
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
index afd458d64f..65b9aaf818 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
@@ -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
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
index 75a14a7bd1..accaa77f29 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
@@ -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("prefix", 0),
+ new PositionTagged("suffix", 34),
+ new AttributeValue(new PositionTagged("prefix", 0),
+ new PositionTagged