From d17db92e19c4bcf77eea5fc54fa1c2ff958bc34a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 4 Nov 2015 12:04:46 -0800 Subject: [PATCH] Log messages to DiagnosticListener in addition to page instrumentation Fixes #3281 --- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 35 +++ .../RazorPageActivatorTest.cs | 173 +++++++------- .../RazorPageTest.cs | 226 ++++++++++++++++++ .../project.json | 5 + .../TestDiagnosticListener.cs | 65 ++++- 5 files changed, 420 insertions(+), 84 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 266f072555..b03ff33284 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -84,6 +84,12 @@ namespace Microsoft.AspNet.Mvc.Razor /// public IPageExecutionContext PageExecutionContext { get; set; } + /// + /// Gets or sets a instance used to instrument the page execution. + /// + [RazorInject] + public DiagnosticSource DiagnosticSource { get; set; } + /// /// Gets the that the page is writing output to. /// @@ -1063,12 +1069,41 @@ namespace Microsoft.AspNet.Mvc.Razor public void BeginContext(int position, int length, bool isLiteral) { + const string BeginContextEvent = "Microsoft.AspNet.Mvc.Razor.BeginInstrumentationContext"; + PageExecutionContext?.BeginContext(position, length, isLiteral); + if (DiagnosticSource?.IsEnabled(BeginContextEvent) == true) + { + DiagnosticSource.Write( + BeginContextEvent, + new + { + httpContext = Context, + path = Path, + isPartial = IsPartial, + position = position, + length = length, + isLiteral = isLiteral, + }); + } } public void EndContext() { + const string EndContextEvent = "Microsoft.AspNet.Mvc.Razor.EndInstrumentationContext"; + PageExecutionContext?.EndContext(); + if (DiagnosticSource?.IsEnabled(EndContextEvent) == true) + { + DiagnosticSource.Write( + EndContextEvent, + new + { + httpContext = Context, + path = Path, + isPartial = IsPartial, + }); + } } /// diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageActivatorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageActivatorTest.cs index 93e4586647..636e1f45cd 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageActivatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageActivatorTest.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Razor.Internal; @@ -15,6 +17,7 @@ using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.AspNet.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.WebEncoders.Testing; using Moq; using Xunit; @@ -33,24 +36,26 @@ namespace Microsoft.AspNet.Mvc.Razor var myService = new MyService(); var helper = Mock.Of>(); var htmlEncoder = new HtmlTestEncoder(); - var serviceProvider = new Mock(); - serviceProvider.Setup(p => p.GetService(typeof(MyService))) - .Returns(myService); - serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))) - .Returns(helper); - serviceProvider.Setup(p => p.GetService(typeof(HtmlEncoder))) - .Returns(htmlEncoder); - var httpContext = new Mock(); - httpContext.SetupGet(c => c.RequestServices) - .Returns(serviceProvider.Object); + var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); + var serviceProvider = new ServiceCollection() + .AddInstance(myService) + .AddInstance(helper) + .AddInstance(htmlEncoder) + .AddInstance(diagnosticSource) + .BuildServiceProvider(); + var httpContext = new DefaultHttpContext + { + RequestServices = serviceProvider + }; - var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); - var viewContext = new ViewContext(actionContext, - Mock.Of(), - new ViewDataDictionary(new EmptyModelMetadataProvider()), - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + new ViewDataDictionary(new EmptyModelMetadataProvider()), + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); // Act activator.Activate(instance, viewContext); @@ -59,6 +64,7 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.Same(helper, instance.Html); Assert.Same(myService, instance.MyService); Assert.Same(viewContext, myService.ViewContext); + Assert.Same(diagnosticSource, instance.DiagnosticSource); Assert.Null(instance.MyService2); } @@ -72,25 +78,23 @@ namespace Microsoft.AspNet.Mvc.Razor var myService = new MyService(); var helper = Mock.Of>(); var serviceProvider = new Mock(); - var httpContext = new Mock(); - httpContext.SetupGet(c => c.RequestServices) - .Returns(serviceProvider.Object); + var httpContext = new DefaultHttpContext + { + RequestServices = new ServiceCollection().BuildServiceProvider() + }; - var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); - var viewContext = new ViewContext(actionContext, - Mock.Of(), - new ViewDataDictionary(new EmptyModelMetadataProvider()), - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + new ViewDataDictionary(new EmptyModelMetadataProvider()), + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); // Act and Assert var ex = Assert.Throws(() => activator.Activate(instance, viewContext)); - var message = string.Format(CultureInfo.InvariantCulture, - "View of type '{0}' cannot be activated by '{1}'.", - instance.GetType().FullName, - typeof(RazorPageActivator).FullName); - + var message = $"View of type '{instance.GetType()}' cannot be activated by '{typeof(RazorPageActivator)}'."; Assert.Equal(message, ex.Message); } @@ -104,28 +108,29 @@ namespace Microsoft.AspNet.Mvc.Razor var myService = new MyService(); var helper = Mock.Of>(); var htmlEncoder = new HtmlTestEncoder(); - var serviceProvider = new Mock(); - serviceProvider.Setup(p => p.GetService(typeof(MyService))) - .Returns(myService); - serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))) - .Returns(helper); - serviceProvider.Setup(p => p.GetService(typeof(HtmlEncoder))) - .Returns(htmlEncoder); - var httpContext = new Mock(); - httpContext.SetupGet(c => c.RequestServices) - .Returns(serviceProvider.Object); + var serviceProvider = new ServiceCollection() + .AddInstance(myService) + .AddInstance(helper) + .AddInstance(htmlEncoder) + .AddInstance(new DiagnosticListener("Microsoft.Aspnet.Mvc")) + .BuildServiceProvider(); + var httpContext = new DefaultHttpContext + { + RequestServices = serviceProvider + }; - var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()) { Model = new MyModel() }; - var viewContext = new ViewContext(actionContext, - Mock.Of(), - viewData, - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + viewData, + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); // Act activator.Activate(instance, viewContext); @@ -143,28 +148,29 @@ namespace Microsoft.AspNet.Mvc.Razor var myService = new MyService(); var helper = Mock.Of>(); var htmlEncoder = new HtmlTestEncoder(); - var serviceProvider = new Mock(); - serviceProvider.Setup(p => p.GetService(typeof(MyService))) - .Returns(myService); - serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))) - .Returns(helper); - serviceProvider.Setup(p => p.GetService(typeof(HtmlEncoder))) - .Returns(htmlEncoder); - var httpContext = new Mock(); - httpContext.SetupGet(c => c.RequestServices) - .Returns(serviceProvider.Object); + var serviceProvider = new ServiceCollection() + .AddInstance(myService) + .AddInstance(helper) + .AddInstance(htmlEncoder) + .AddInstance(new DiagnosticListener("Microsoft.Aspnet.Mvc")) + .BuildServiceProvider(); + var httpContext = new DefaultHttpContext + { + RequestServices = serviceProvider + }; - var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()) { Model = new MyModel() }; - var viewContext = new ViewContext(actionContext, - Mock.Of(), - viewData, - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + viewData, + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); // Act activator.Activate(instance, viewContext); @@ -182,25 +188,26 @@ namespace Microsoft.AspNet.Mvc.Razor var myService = new MyService(); var helper = Mock.Of>(); var htmlEncoder = new HtmlTestEncoder(); - var serviceProvider = new Mock(); - serviceProvider.Setup(p => p.GetService(typeof(MyService))) - .Returns(myService); - serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))) - .Returns(helper); - serviceProvider.Setup(p => p.GetService(typeof(HtmlEncoder))) - .Returns(htmlEncoder); - var httpContext = new Mock(); - httpContext.SetupGet(c => c.RequestServices) - .Returns(serviceProvider.Object); + var serviceProvider = new ServiceCollection() + .AddInstance(myService) + .AddInstance(helper) + .AddInstance(htmlEncoder) + .AddInstance(new DiagnosticListener("Microsoft.AspNet.Mvc")) + .BuildServiceProvider(); + var httpContext = new DefaultHttpContext + { + RequestServices = serviceProvider + }; - var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); - var viewContext = new ViewContext(actionContext, - Mock.Of(), - viewData, - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + viewData, + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); // Act activator.Activate(instance, viewContext); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 515360e2a2..c30e2e0978 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; @@ -745,6 +746,231 @@ namespace Microsoft.AspNet.Mvc.Razor context.Verify(); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WriteAttribute_CallsBeginAndEndContext_OnPageExecutionListenerContext(bool isPartial) + { + // Arrange + var path = "path-to-page"; + var page = CreatePage(p => + { + p.HtmlEncoder = new HtmlTestEncoder(); + p.BeginWriteAttribute("href", "prefix", 0, "suffix", 34, 2); + p.WriteAttributeValue("prefix", 0, "attr1-value", 8, 14, true); + p.WriteAttributeValue("prefix2", 22, "attr2", 29, 5, false); + p.EndWriteAttribute(); + }); + page.Path = path; + page.IsPartial = isPartial; + var adapter = new TestDiagnosticListener(); + var diagnosticListener = new DiagnosticListener("Microsoft.AspNet.Mvc.Razor"); + diagnosticListener.SubscribeWithAdapter(adapter); + page.DiagnosticSource = diagnosticListener; + + // Act + await page.ExecuteAsync(); + + // Assert + Func assertStartEvent = data => + { + var beginEvent = Assert.IsType(data); + Assert.NotNull(beginEvent.HttpContext); + Assert.Equal(path, beginEvent.Path); + Assert.Equal(isPartial, beginEvent.IsPartial); + + return beginEvent; + }; + + Action assertEndEvent = data => + { + var endEvent = Assert.IsType(data); + Assert.NotNull(endEvent.HttpContext); + Assert.Equal(path, endEvent.Path); + Assert.Equal(isPartial, endEvent.IsPartial); + }; + + Assert.Collection(adapter.PageInstrumentationData, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(0, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(0, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(8, beginEvent.Position); + Assert.Equal(14, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(22, beginEvent.Position); + Assert.Equal(7, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(29, beginEvent.Position); + Assert.Equal(5, beginEvent.Length); + Assert.False(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(34, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WriteAttribute_WithBoolValue_CallsBeginAndEndContext_OnPageExecutionListenerContext(bool isPartial) + { + // Arrange + var path = "some-path"; + var page = CreatePage(p => + { + p.HtmlEncoder = new HtmlTestEncoder(); + p.BeginWriteAttribute("href", "prefix", 0, "suffix", 10, 1); + p.WriteAttributeValue("", 6, "true", 6, 4, false); + p.EndWriteAttribute(); + }); + page.Path = path; + page.IsPartial = isPartial; + var adapter = new TestDiagnosticListener(); + var diagnosticListener = new DiagnosticListener("Microsoft.AspNet.Mvc.Razor"); + diagnosticListener.SubscribeWithAdapter(adapter); + page.DiagnosticSource = diagnosticListener; + + // Act + await page.ExecuteAsync(); + + // Assert + Func assertStartEvent = data => + { + var beginEvent = Assert.IsType(data); + Assert.NotNull(beginEvent.HttpContext); + Assert.Equal(path, beginEvent.Path); + Assert.Equal(isPartial, beginEvent.IsPartial); + + return beginEvent; + }; + + Action assertEndEvent = data => + { + var endEvent = Assert.IsType(data); + Assert.NotNull(endEvent.HttpContext); + Assert.Equal(path, endEvent.Path); + Assert.Equal(isPartial, endEvent.IsPartial); + }; + + Assert.Collection(adapter.PageInstrumentationData, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(0, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(6, beginEvent.Position); + Assert.Equal(4, beginEvent.Length); + Assert.False(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(10, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WriteAttribute_CallsBeginAndEndContext_OnPrefixAndSuffixValues(bool isPartial) + { + // Arrange + var path = "some-path"; + var page = CreatePage(p => + { + p.BeginWriteAttribute("href", "prefix", 0, "tail", 7, 0); + p.EndWriteAttribute(); + }); + page.Path = path; + page.IsPartial = isPartial; + var adapter = new TestDiagnosticListener(); + var diagnosticListener = new DiagnosticListener("Microsoft.AspNet.Mvc.Razor"); + diagnosticListener.SubscribeWithAdapter(adapter); + page.DiagnosticSource = diagnosticListener; + + // Act + await page.ExecuteAsync(); + + // Assert + Func assertStartEvent = data => + { + var beginEvent = Assert.IsType(data); + Assert.NotNull(beginEvent.HttpContext); + Assert.Equal(path, beginEvent.Path); + Assert.Equal(isPartial, beginEvent.IsPartial); + + return beginEvent; + }; + + Action assertEndEvent = data => + { + var endEvent = Assert.IsType(data); + Assert.NotNull(endEvent.HttpContext); + Assert.Equal(path, endEvent.Path); + Assert.Equal(isPartial, endEvent.IsPartial); + }; + + Assert.Collection(adapter.PageInstrumentationData, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(0, beginEvent.Position); + Assert.Equal(6, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent, + data => + { + var beginEvent = assertStartEvent(data); + Assert.Equal(7, beginEvent.Position); + Assert.Equal(4, beginEvent.Length); + Assert.True(beginEvent.IsLiteral); + }, + assertEndEvent); + } + public static TheoryData AddHtmlAttributeValues_ValueData { get diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json index 20d7e1ac28..cc33562f65 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json @@ -13,9 +13,14 @@ "version": "6.0.0-*", "type": "build" }, + "Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources": { + "version": "6.0.0-*", + "type": "build" + }, "Microsoft.AspNet.Testing": "1.0.0-*", "Microsoft.Dnx.Runtime": "1.0.0-*", "Microsoft.Extensions.DependencyInjection": "1.0.0-*", + "Microsoft.Extensions.DiagnosticAdapter": "1.0.0-*", "xunit.runner.aspnet": "2.0.0-aspnet-*" }, "commands": { diff --git a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs index 5de699d2b1..ee2f61af2c 100644 --- a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs @@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc string viewName, IProxyView view) { - ViewFound = new OnViewFoundEventData() + ViewFound = new OnViewFoundEventData() { ActionContext = actionContext, IsPartial = isPartial, @@ -324,5 +324,68 @@ namespace Microsoft.AspNet.Mvc View = view }; } + + public class BeginPageInstrumentationData + { + public IProxyHttpContext HttpContext { get; set; } + + public string Path { get; set; } + + public bool IsPartial { get; set; } + + public int Position { get; set; } + + public int Length { get; set; } + + public bool IsLiteral { get; set; } + } + + public class EndPageInstrumentationData + { + public IProxyHttpContext HttpContext { get; set; } + + public string Path { get; set; } + + public bool IsPartial { get; set; } + } + + public List PageInstrumentationData { get; set; } = new List(); + + [DiagnosticName("Microsoft.AspNet.Mvc.Razor.BeginInstrumentationContext")] + public virtual void OnBeginPageInstrumentationContext( + IProxyHttpContext httpContext, + string path, + bool isPartial, + int position, + int length, + bool isLiteral) + { + PageInstrumentationData.Add(new BeginPageInstrumentationData + { + HttpContext = httpContext, + Path = path, + IsPartial = isPartial, + Position = position, + Length = length, + IsLiteral = isLiteral, + }); + } + + [DiagnosticName("Microsoft.AspNet.Mvc.Razor.EndInstrumentationContext")] + public virtual void OnEndPageInstrumentationContext( + IProxyHttpContext httpContext, + string path, + bool isPartial, + int position, + int length, + bool isLiteral) + { + PageInstrumentationData.Add(new EndPageInstrumentationData + { + HttpContext = httpContext, + Path = path, + IsPartial = isPartial, + }); + } } }