diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewComponentDiagnosticSourceExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewComponentDiagnosticSourceExtensions.cs new file mode 100644 index 0000000000..80682b7a7e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewComponentDiagnosticSourceExtensions.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.ViewComponents; +using Microsoft.AspNet.Mvc.ViewEngines; + +namespace Microsoft.AspNet.Mvc.Diagnostics +{ + public static class ViewComponentDiagnosticSourceExtensions + { + public static void BeforeViewComponent( + this DiagnosticSource diagnosticSource, + ViewComponentContext context, + object viewComponent) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.BeforeViewComponent")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.BeforeViewComponent", + new + { + actionDescriptor = context.ViewContext.ActionDescriptor, + viewComponentContext = context, + viewComponent = viewComponent + }); + } + } + + public static void AfterViewComponent( + this DiagnosticSource diagnosticSource, + ViewComponentContext context, + IViewComponentResult result, + object viewComponent) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.AfterViewComponent")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.AfterViewComponent", + new + { + actionDescriptor = context.ViewContext.ActionDescriptor, + viewComponentContext = context, + viewComponentResult = result, + viewComponent = viewComponent + }); + } + } + + public static void ViewComponentBeforeViewExecute( + this DiagnosticSource diagnosticSource, + ViewComponentContext context, + IView view) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewComponentBeforeViewExecute")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.ViewComponentBeforeViewExecute", + new + { + actionDescriptor = context.ViewContext.ActionDescriptor, + viewComponentContext = context, + view = view + }); + } + } + + public static void ViewComponentAfterViewExecute( + this DiagnosticSource diagnosticSource, + ViewComponentContext context, + IView view) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewComponentAfterViewExecute")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.ViewComponentAfterViewExecute", + new + { + actionDescriptor = context.ViewContext.ActionDescriptor, + viewComponentContext = context, + view = view + }); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewExecutorDiagnosticSourceExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewExecutorDiagnosticSourceExtensions.cs new file mode 100644 index 0000000000..eade9c269c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/DiagnosticSource/ViewExecutorDiagnosticSourceExtensions.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. 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.Diagnostics; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.ViewEngines; + +namespace Microsoft.AspNet.Mvc.Diagnostics +{ + public static class ViewExecutorDiagnosticSourceExtensions + { + public static void BeforeView( + this DiagnosticSource diagnosticSource, + IView view, + ViewContext viewContext) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.BeforeView")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.BeforeView", + new { view = view, viewContext = viewContext, }); + } + } + + public static void AfterView( + this DiagnosticSource diagnosticSource, + IView view, + ViewContext viewContext) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.AfterView")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.AfterView", + new { view = view, viewContext = viewContext, }); + } + } + + public static void ViewFound( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + bool isPartial, + PartialViewResult viewResult, + string viewName, + IView view) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.ViewFound", + new + { + actionContext = actionContext, + isPartial = isPartial, + result = viewResult, + viewName = viewName, + view = view, + }); + } + } + + public static void ViewNotFound( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + bool isPartial, + PartialViewResult viewResult, + string viewName, + IEnumerable searchedLocations) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewNotFound")) + { + diagnosticSource.Write( + "Microsoft.AspNet.Mvc.ViewNotFound", + new + { + actionContext = actionContext, + isPartial = isPartial, + result = viewResult, + viewName = viewName, + searchedLocations = searchedLocations, + }); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs index 88c569b9b1..a655ab4c86 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs @@ -2,10 +2,12 @@ // 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.Reflection; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Controllers; +using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewFeatures; @@ -16,10 +18,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { private readonly ITypeActivatorCache _typeActivatorCache; private readonly IViewComponentActivator _viewComponentActivator; + private readonly DiagnosticSource _diagnosticSource; public DefaultViewComponentInvoker( ITypeActivatorCache typeActivatorCache, - IViewComponentActivator viewComponentActivator) + IViewComponentActivator viewComponentActivator, + DiagnosticSource diagnosticSource) { if (typeActivatorCache == null) { @@ -31,8 +35,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents throw new ArgumentNullException(nameof(viewComponentActivator)); } + if (diagnosticSource == null) + { + throw new ArgumentNullException(nameof(diagnosticSource)); + } + _typeActivatorCache = typeActivatorCache; _viewComponentActivator = viewComponentActivator; + _diagnosticSource = diagnosticSource; } public void Invoke(ViewComponentContext context) @@ -52,6 +62,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } var result = InvokeSyncCore(method, context); + result.Execute(context); } @@ -124,9 +135,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var component = CreateComponent(context); + _diagnosticSource.BeforeViewComponent(context, component); + var result = await ControllerActionExecutor.ExecuteAsync(method, component, context.Arguments); - return CoerceToViewComponentResult(result); + var viewComponentResult = CoerceToViewComponentResult(result); + + _diagnosticSource.AfterViewComponent(context, viewComponentResult, component); + + return viewComponentResult; } public IViewComponentResult InvokeSyncCore(MethodInfo method, ViewComponentContext context) @@ -145,6 +162,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents object result = null; + _diagnosticSource.BeforeViewComponent(context, component); + try { result = method.Invoke(component, context.Arguments); @@ -156,7 +175,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents exceptionInfo.Throw(); } - return CoerceToViewComponentResult(result); + var viewComponentResult = CoerceToViewComponentResult(result); + + _diagnosticSource.AfterViewComponent(context, viewComponentResult, component); + + return viewComponentResult; } private static IViewComponentResult CoerceToViewComponentResult(object value) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs index bfa67c2c1d..0df9ca4d45 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.AspNet.Mvc.Infrastructure; namespace Microsoft.AspNet.Mvc.ViewComponents @@ -10,13 +11,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { private readonly ITypeActivatorCache _typeActivatorCache; private readonly IViewComponentActivator _viewComponentActivator; + private readonly DiagnosticSource _diagnosticSource; public DefaultViewComponentInvokerFactory( ITypeActivatorCache typeActivatorCache, - IViewComponentActivator viewComponentActivator) + IViewComponentActivator viewComponentActivator, + DiagnosticSource diagnosticSource) { _typeActivatorCache = typeActivatorCache; _viewComponentActivator = viewComponentActivator; + _diagnosticSource = diagnosticSource; } /// @@ -32,7 +36,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents return new DefaultViewComponentInvoker( _typeActivatorCache, - _viewComponentActivator); + _viewComponentActivator, + _diagnosticSource); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs index 8df60f911d..728d7fecc4 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs @@ -2,8 +2,10 @@ // 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.Threading.Tasks; +using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; @@ -20,6 +22,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents private const string ViewPathFormat = "Components/{0}/{1}"; private const string DefaultViewName = "Default"; + private DiagnosticSource _diagnosticSource; + /// /// Gets or sets the view name. /// @@ -115,7 +119,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents using (view as IDisposable) { + if (_diagnosticSource == null) + { + _diagnosticSource = context.ViewContext.HttpContext.RequestServices.GetRequiredService(); + } + + _diagnosticSource.ViewComponentBeforeViewExecute(context, view); + await view.RenderAsync(childViewContext); + + _diagnosticSource.ViewComponentAfterViewExecute(context, view); } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs index b6eecbc153..b2ac16d64f 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.ViewEngines; @@ -70,37 +71,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var result = viewEngine.FindPartialView(actionContext, viewName); if (result.Success) { - if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound")) - { - DiagnosticSource.Write( - "Microsoft.AspNet.Mvc.ViewFound", - new - { - actionContext = actionContext, - isPartial = true, - result = viewResult, - viewName = viewName, - view = result.View, - }); - } + DiagnosticSource.ViewFound(actionContext, true, viewResult, viewName, result.View); Logger.LogVerbose("The partial view '{PartialViewName}' was found.", viewName); } else { - if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewNotFound")) - { - DiagnosticSource.Write( - "Microsoft.AspNet.Mvc.ViewNotFound", - new - { - actionContext = actionContext, - isPartial = true, - result = viewResult, - viewName = viewName, - searchedLocations = result.SearchedLocations - }); - } + DiagnosticSource.ViewNotFound(actionContext, true, viewResult, viewName, result.SearchedLocations); Logger.LogError( "The partial view '{PartialViewName}' was not found. Searched locations: {SearchedViewLocations}", diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs index 0aa5f9b1c9..d6fa0258e1 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; @@ -162,21 +163,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures writer, ViewOptions.HtmlHelperOptions); - if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.BeforeView")) - { - DiagnosticSource.Write( - "Microsoft.AspNet.Mvc.BeforeView", - new { view = view, viewContext = viewContext, }); - } + DiagnosticSource.BeforeView(view, viewContext); await view.RenderAsync(viewContext); - - if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.AfterView")) - { - DiagnosticSource.Write( - "Microsoft.AspNet.Mvc.AfterView", - new { view = view, viewContext = viewContext, }); - } + + DiagnosticSource.AfterView(view, viewContext); // Perf: Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying // response asynchronously. In the absence of this line, the buffer gets synchronously written to the diff --git a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentContext.cs b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentContext.cs new file mode 100644 index 0000000000..147b959936 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentContext.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. 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 +{ + public interface IProxyViewComponentContext + { + } +} diff --git a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentResult.cs b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentResult.cs new file mode 100644 index 0000000000..a8a5484ac1 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/IProxyViewComponentResult.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. 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 +{ + public interface IProxyViewComponentResult + { + } +} diff --git a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs index 57c570ac50..5de699d2b1 100644 --- a/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.Mvc.TestDiagnosticListener.Sources/TestDiagnosticListener.cs @@ -220,5 +220,109 @@ namespace Microsoft.AspNet.Mvc ViewContext = viewContext, }; } + + public class OnBeforeViewComponentEventData + { + public IProxyActionDescriptor ActionDescriptor { get; set; } + + public IProxyViewComponentContext ViewComponentContext { get; set; } + + public object ViewComponent { get; set; } + } + + public OnBeforeViewComponentEventData BeforeViewComponent { get; set; } + + [DiagnosticName("Microsoft.AspNet.Mvc.BeforeViewComponent")] + public virtual void OnBeforeViewComponent( + IProxyActionDescriptor actionDescriptor, + IProxyViewComponentContext viewComponentContext, + object viewComponent) + { + BeforeViewComponent = new OnBeforeViewComponentEventData() + { + ActionDescriptor = actionDescriptor, + ViewComponentContext = viewComponentContext, + ViewComponent = viewComponent + }; + } + + public class OnAfterViewComponentEventData + { + public IProxyActionDescriptor ActionDescriptor { get; set; } + + public IProxyViewComponentContext ViewComponentContext { get; set; } + + public IProxyViewComponentResult ViewComponentResult { get; set; } + + public object ViewComponent { get; set; } + } + + public OnAfterViewComponentEventData AfterViewComponent { get; set; } + + [DiagnosticName("Microsoft.AspNet.Mvc.AfterViewComponent")] + public virtual void OnAfterViewComponent( + IProxyActionDescriptor actionDescriptor, + IProxyViewComponentContext viewComponentContext, + IProxyViewComponentResult viewComponentResult, + object viewComponent) + { + AfterViewComponent = new OnAfterViewComponentEventData() + { + ActionDescriptor = actionDescriptor, + ViewComponentContext = viewComponentContext, + ViewComponentResult = viewComponentResult, + ViewComponent = viewComponent + }; + } + + public class OnViewComponentBeforeViewExecuteEventData + { + public IProxyActionDescriptor ActionDescriptor { get; set; } + + public IProxyViewComponentContext ViewComponentContext { get; set; } + + public IProxyView View { get; set; } + } + + public OnViewComponentBeforeViewExecuteEventData ViewComponentBeforeViewExecute { get; set; } + + [DiagnosticName("Microsoft.AspNet.Mvc.ViewComponentBeforeViewExecute")] + public virtual void OnViewComponentBeforeViewExecute( + IProxyActionDescriptor actionDescriptor, + IProxyViewComponentContext viewComponentContext, + IProxyView view) + { + ViewComponentBeforeViewExecute = new OnViewComponentBeforeViewExecuteEventData() + { + ActionDescriptor = actionDescriptor, + ViewComponentContext = viewComponentContext, + View = view + }; + } + + public class OnViewComponentAfterViewExecuteEventData + { + public IProxyActionDescriptor ActionDescriptor { get; set; } + + public IProxyViewComponentContext ViewComponentContext { get; set; } + + public IProxyView View { get; set; } + } + + public OnViewComponentAfterViewExecuteEventData ViewComponentAfterViewExecute { get; set; } + + [DiagnosticName("Microsoft.AspNet.Mvc.ViewComponentAfterViewExecute")] + public virtual void OnViewComponentAfterViewExecute( + IProxyActionDescriptor actionDescriptor, + IProxyViewComponentContext viewComponentContext, + IProxyView view) + { + ViewComponentAfterViewExecute = new OnViewComponentAfterViewExecuteEventData() + { + ActionDescriptor = actionDescriptor, + ViewComponentContext = viewComponentContext, + View = view + }; + } } } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs index f9b6bfe39d..a79574ecaa 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; @@ -103,7 +104,7 @@ namespace Microsoft.AspNet.Mvc var expected = $"A view component named '{typeof(TextViewComponent).FullName}' could not be found."; var actionContext = CreateActionContext(); - var services = CreateServices(actionContext.HttpContext); + var services = CreateServices(diagnosticListener: null, context: actionContext.HttpContext); services.AddSingleton(); @@ -175,6 +176,44 @@ namespace Microsoft.AspNet.Mvc Assert.Equal("Hello-Async, World!", body); } + [Fact] + public async Task ExecuteResultAsync_ExecutesViewComponent_AndWritesDiagnosticSource() + { + // Arrange + var descriptor = new ViewComponentDescriptor() + { + FullName = "Full.Name.Text", + ShortName = "Text", + Type = typeof(TextViewComponent), + }; + + var adapter = new TestDiagnosticListener(); + + var actionContext = CreateActionContext(adapter, descriptor); + + var viewComponentResult = new ViewComponentResult() + { + Arguments = new object[] { "World!" }, + ViewComponentName = "Text", + TempData = _tempDataDictionary, + }; + + // Act + await viewComponentResult.ExecuteResultAsync(actionContext); + + // Assert + var body = ReadBody(actionContext.HttpContext.Response); + Assert.Equal("Hello, World!", body); + + Assert.NotNull(adapter.BeforeViewComponent?.ActionDescriptor); + Assert.NotNull(adapter.BeforeViewComponent?.ViewComponentContext); + Assert.NotNull(adapter.BeforeViewComponent?.ViewComponent); + Assert.NotNull(adapter.AfterViewComponent?.ActionDescriptor); + Assert.NotNull(adapter.AfterViewComponent?.ViewComponentContext); + Assert.NotNull(adapter.AfterViewComponent?.ViewComponentResult); + Assert.NotNull(adapter.AfterViewComponent?.ViewComponent); + } + [Fact] public async Task ExecuteResultAsync_ExecutesViewComponent_ByShortName() { @@ -413,12 +452,18 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType); } - private IServiceCollection CreateServices(HttpContext context, params ViewComponentDescriptor[] descriptors) + private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors) { var httpContext = new HttpContextAccessor() { HttpContext = context }; var tempDataProvider = new SessionStateTempDataProvider(); + var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); + if (diagnosticListener != null) + { + diagnosticSource.SubscribeWithAdapter(diagnosticListener); + } var services = new ServiceCollection(); + services.AddInstance(diagnosticSource); services.AddSingleton, TestOptionsManager>(); services.AddTransient(); services.AddSingleton(); @@ -435,10 +480,10 @@ namespace Microsoft.AspNet.Mvc return services; } - private HttpContext CreateHttpContext(params ViewComponentDescriptor[] descriptors) + private HttpContext CreateHttpContext(object diagnosticListener, params ViewComponentDescriptor[] descriptors) { var httpContext = new DefaultHttpContext(); - var services = CreateServices(httpContext, descriptors); + var services = CreateServices(diagnosticListener, httpContext, descriptors); httpContext.Response.Body = new MemoryStream(); httpContext.RequestServices = services.BuildServiceProvider(); @@ -446,11 +491,17 @@ namespace Microsoft.AspNet.Mvc return httpContext; } + private ActionContext CreateActionContext(object diagnosticListener, params ViewComponentDescriptor[] descriptors) + { + return new ActionContext(CreateHttpContext(diagnosticListener, descriptors), new RouteData(), new ActionDescriptor()); + } + private ActionContext CreateActionContext(params ViewComponentDescriptor[] descriptors) { - return new ActionContext(CreateHttpContext(descriptors), new RouteData(), new ActionDescriptor()); + return CreateActionContext(null, descriptors); } + private class FixedSetViewComponentDescriptorProvider : IViewComponentDescriptorProvider { private readonly ViewComponentDescriptor[] _descriptors; diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs index 7d65c4e319..312c908430 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -3,6 +3,7 @@ #if MOCK_SUPPORT using System; +using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; @@ -91,6 +92,48 @@ namespace Microsoft.AspNet.Mvc view.Verify(); } + [Fact] + public void Execute_ResolvesView_AndWritesDiagnosticSource() + { + // Arrange + var view = new Mock(MockBehavior.Strict); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Returns(Task.FromResult(result: true)) + .Verifiable(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) + .Returns(ViewEngineResult.Found("Default", view.Object)) + .Verifiable(); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewData = viewData, + TempData = _tempDataDictionary, + }; + + var adapter = new TestDiagnosticListener(); + + var viewComponentContext = GetViewComponentContext(view.Object, viewData, adapter); + + // Act + result.Execute(viewComponentContext); + + // Assert + viewEngine.Verify(); + view.Verify(); + + Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.ActionDescriptor); + Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.ViewComponentContext); + Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.View); + Assert.NotNull(adapter.ViewComponentAfterViewExecute?.ActionDescriptor); + Assert.NotNull(adapter.ViewComponentAfterViewExecute?.ViewComponentContext); + Assert.NotNull(adapter.ViewComponentAfterViewExecute?.View); + } + [Fact] public void Execute_ThrowsIfPartialViewCannotBeFound() { @@ -204,6 +247,8 @@ namespace Microsoft.AspNet.Mvc var serviceProvider = new Mock(); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) .Returns(viewEngine.Object); + serviceProvider.Setup(p => p.GetService(typeof(DiagnosticSource))) + .Returns(new DiagnosticListener("Test")); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -345,9 +390,23 @@ namespace Microsoft.AspNet.Mvc viewEngine.Verify(); } - private static ViewComponentContext GetViewComponentContext(IView view, ViewDataDictionary viewData) + private static ViewComponentContext GetViewComponentContext(IView view, ViewDataDictionary viewData, object diagnosticListener = null) { - var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); + if (diagnosticListener == null) + { + diagnosticListener = new TestDiagnosticListener(); + } + + diagnosticSource.SubscribeWithAdapter(diagnosticListener); + + var serviceProvider = new Mock(); + serviceProvider.Setup(s => s.GetService(typeof(DiagnosticSource))).Returns(diagnosticSource); + + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = serviceProvider.Object; + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var viewContext = new ViewContext( actionContext, view,