From ec01107b027024655eb92d8e97572a94f58d8cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Wed, 21 Feb 2024 13:48:53 +0100 Subject: [PATCH 1/8] tmp: try to reproduce extension method load instability --- src/Samples/AspNetCore/Startup.cs | 13 +- .../DotvvmServiceConfigurator.cs | 2 +- src/Samples/AspNetCoreLatest/Startup.cs | 15 +- .../Common/DotVVM.Samples.Common.csproj | 158 +----------------- src/Tools/AppStartupInstabilityTester.py | 49 ++++++ 5 files changed, 76 insertions(+), 161 deletions(-) create mode 100644 src/Tools/AppStartupInstabilityTester.py diff --git a/src/Samples/AspNetCore/Startup.cs b/src/Samples/AspNetCore/Startup.cs index 4c569954b1..097957df9d 100644 --- a/src/Samples/AspNetCore/Startup.cs +++ b/src/Samples/AspNetCore/Startup.cs @@ -94,6 +94,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF } private string GetApplicationPath(IWebHostEnvironment env) - => Path.Combine(Path.GetDirectoryName(env.ContentRootPath), "Common"); + { + var common = Path.Combine(Path.GetDirectoryName(env.ContentRootPath), "Common"); + if (Directory.Exists(common)) + { + return common; + } + if (File.Exists(Path.Combine(env.ContentRootPath, "Views/Default.dothtml"))) + { + return env.ContentRootPath; + } + throw new DirectoryNotFoundException("Cannot find the 'Common' directory nor the 'Views' directory in the application root."); + } } } diff --git a/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs b/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs index eef93199fe..32bb31cbc5 100644 --- a/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs +++ b/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs @@ -10,7 +10,7 @@ public void ConfigureServices(IDotvvmServiceCollection services) { CommonConfiguration.ConfigureServices(services); services.AddDefaultTempStorages("Temp"); - services.AddHotReload(); + // services.AddHotReload(); } } } diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index cb24824f6f..b308c9cbb6 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -99,12 +99,23 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.UseStaticFiles(); app.UseEndpoints(endpoints => { - endpoints.MapDotvvmHotReload(); + // endpoints.MapDotvvmHotReload(); endpoints.MapMetrics(); // prometheus metrics on /metrics }); } private string GetApplicationPath(IWebHostEnvironment env) - => Path.Combine(Path.GetDirectoryName(env.ContentRootPath), "Common"); + { + if (File.Exists(Path.Combine(env.ContentRootPath, "Views/Default.dothtml"))) + { + return env.ContentRootPath; + } + var common = Path.Combine(Path.GetDirectoryName(env.ContentRootPath), "Common"); + if (File.Exists(Path.Combine(common, "Views/Default.dothtml"))) + { + return common; + } + throw new DirectoryNotFoundException("Cannot find the 'Common' directory nor the 'Views' directory in the application root."); + } } } diff --git a/src/Samples/Common/DotVVM.Samples.Common.csproj b/src/Samples/Common/DotVVM.Samples.Common.csproj index 216f55da77..41bcf2a7ef 100644 --- a/src/Samples/Common/DotVVM.Samples.Common.csproj +++ b/src/Samples/Common/DotVVM.Samples.Common.csproj @@ -19,167 +19,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Tools/AppStartupInstabilityTester.py b/src/Tools/AppStartupInstabilityTester.py new file mode 100644 index 0000000000..80e6f77f32 --- /dev/null +++ b/src/Tools/AppStartupInstabilityTester.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import subprocess, requests, os, time, argparse + +parser = argparse.ArgumentParser(description="Repeatedly starts the server and every time checks if some pages are working, use to find startup-time race condition bugs") +parser.add_argument("--port", type=int, default=16017, help="Port to run the server on") +parser.add_argument("--working-directory", type=str, default=".", help="Working directory to run the server in") +parser.add_argument("--server-path", type=str, default="bin/Debug/net8.0/DotVVM.Samples.BasicSamples.AspNetCoreLatest", help="Path to the server executable") +parser.add_argument("--environment", type=str, default="Development", help="Asp.Net Core environment (Development, Production)") +args = parser.parse_args() + +port = args.port + +def server_start() -> subprocess.Popen: + """Starts the server and returns the process object""" + server = subprocess.Popen([ + args.server_path, "--environment", args.environment, "--urls", f"http://localhost:{port}"], + cwd=args.working_directory, + ) + return server + +def req(path): + try: + response = requests.get(f"http://localhost:{port}{path}") + return response.status_code + except requests.exceptions.ConnectionError: + return None + +iteration = 0 +while True: + iteration += 1 + print(f"Starting iteration {iteration}") + server = server_start() + time.sleep(0.1) + while req("/") is None: + time.sleep(0.1) + + probes = [ + req("/"), + req("/FeatureSamples/LambdaExpressions/StaticCommands"), + req("/FeatureSamples/LambdaExpressions/ClientSideFiltering"), + req("/FeatureSamples/LambdaExpressions/LambdaExpressions") + ] + if set(probes) != {200}: + print(f"Iteration {iteration} failed: {probes}") + time.sleep(100000000) + + server.terminate() + server.wait() From 4cd20f5be8a045d57cbef639c0805c06cd549f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 25 Feb 2024 11:58:16 +0100 Subject: [PATCH 2/8] Added dump of extension methods to the test --- src/Samples/Common/DotvvmStartup.cs | 3 ++ .../DumpExtensionMethodsPresenter.cs | 45 +++++++++++++++++++ .../Tests/Tests/Feature/StaticCommandTests.cs | 25 ++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs diff --git a/src/Samples/Common/DotvvmStartup.cs b/src/Samples/Common/DotvvmStartup.cs index fc32374ecc..9172d44334 100644 --- a/src/Samples/Common/DotvvmStartup.cs +++ b/src/Samples/Common/DotvvmStartup.cs @@ -31,6 +31,7 @@ using DotVVM.Samples.Common.ViewModels.FeatureSamples.BindingVariables; using DotVVM.Samples.Common.Views.ControlSamples.TemplateHost; using DotVVM.Framework.ResourceManagement; +using DotVVM.Samples.Common.Presenters; using DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes; namespace DotVVM.Samples.BasicSamples @@ -251,6 +252,8 @@ private static void AddRoutes(DotvvmConfiguration config) config.RouteTable.Add("FeatureSamples_PostBack_PostBackHandlers_Localized", "FeatureSamples/PostBack/PostBackHandlers_Localized", "Views/FeatureSamples/PostBack/ConfirmPostBackHandler.dothtml", presenterFactory: LocalizablePresenter.BasedOnQuery("lang")); config.RouteTable.Add("Errors_UndefinedRouteLinkParameters-PageDetail", "Erros/UndefinedRouteLinkParameters/{Id}", "Views/Errors/UndefinedRouteLinkParameters.dothtml", new { Id = 0 }); + + config.RouteTable.Add("DumpExtensionsMethods", "dump-extension-methods", _ => new DumpExtensionMethodsPresenter()); } private static void AddControls(DotvvmConfiguration config) diff --git a/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs b/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs new file mode 100644 index 0000000000..bb6308547e --- /dev/null +++ b/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation; +using DotVVM.Framework.Hosting; +using DotVVM.Framework.Utils; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; + +namespace DotVVM.Samples.Common.Presenters +{ + public class DumpExtensionMethodsPresenter : IDotvvmPresenter + { + public async Task ProcessRequest(IDotvvmRequestContext context) + { + var cache = context.Configuration.ServiceProvider.GetService(); + + var contents = typeof(ExtensionMethodsCache) + .GetField("methodsCache", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(cache) as ConcurrentDictionary>; + + var dump = contents.SelectMany(p => p.Value.Select(m => new { + Namespace = p.Key, + m.Name, + m.DeclaringType!.FullName, + Params = m.GetParameters().Select(p => new { + p.Name, + Type = p.ParameterType!.FullName + }), + m.IsGenericMethodDefinition, + GenericParameters = m.IsGenericMethodDefinition ? m.GetGenericArguments().Select(a => new { + a.Name + }) : null + })) + .OrderBy(m => m.Namespace).ThenBy(m => m.Name); + + await context.HttpContext.Response.WriteAsync("ExtensionMethodsCache dump: " + JsonConvert.SerializeObject(dump)); + } + } +} diff --git a/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs b/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs index 164bf1fa71..3900fcf6d5 100644 --- a/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs +++ b/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Threading; +using System.Threading.Tasks; using DotVVM.Samples.Tests.Base; using DotVVM.Testing.Abstractions; using OpenQA.Selenium; @@ -816,16 +819,34 @@ public void Feature_List_Translation_Remove_Range() } [Fact] - public void Feature_List_Translation_Remove_Reverse() + public async Task Feature_List_Translation_Remove_Reverse() { + var wasError = false; + RunInAllBrowsers(browser => { browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_ListMethodTranslations); + browser.Wait(1000); + if (browser.FindElements(".exceptionMessage").Count > 0) + { + wasError = true; + return; + } + var rows = GetSortedRow(browser, "Reverse"); var column = GetColumnContent(rows, 0); browser.WaitFor(() => Assert.Equal(10, column.Count), 500); Assert.Equal(new List { "10", "9", "8", "7", "6", "5", "4", "3", "2", "1" }, column); }); + + if (wasError) + { + // error page - extension methods not found + var client = new HttpClient(); + var json = await client.GetStringAsync(TestSuiteRunner.Configuration.BaseUrls[0].TrimEnd('/') + "/dump-extension-methods"); + TestOutput.WriteLine(json); + throw new Exception("Test failed"); + } } protected IElementWrapperCollection GetSortedRow(IBrowserWrapper browser, string btn) From e0fd59dac8f0d3ab81ab9cce2e07b1d6c928f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 23 Mar 2024 17:27:13 +0100 Subject: [PATCH 3/8] Isolated test case and output if the error returns --- .../DotvvmServiceConfigurator.cs | 2 +- src/Samples/AspNetCoreLatest/Startup.cs | 2 +- .../DumpExtensionMethodsPresenter.cs | 2 +- .../Tests/Tests/Feature/StaticCommandTests.cs | 36 ++++++++++--------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs b/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs index 32bb31cbc5..eef93199fe 100644 --- a/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs +++ b/src/Samples/AspNetCoreLatest/DotvvmServiceConfigurator.cs @@ -10,7 +10,7 @@ public void ConfigureServices(IDotvvmServiceCollection services) { CommonConfiguration.ConfigureServices(services); services.AddDefaultTempStorages("Temp"); - // services.AddHotReload(); + services.AddHotReload(); } } } diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index b308c9cbb6..4342847d57 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -99,7 +99,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.UseStaticFiles(); app.UseEndpoints(endpoints => { - // endpoints.MapDotvvmHotReload(); + endpoints.MapDotvvmHotReload(); endpoints.MapMetrics(); // prometheus metrics on /metrics }); } diff --git a/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs b/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs index bb6308547e..6b418fb287 100644 --- a/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs +++ b/src/Samples/Common/Presenters/DumpExtensionMethodsPresenter.cs @@ -39,7 +39,7 @@ public async Task ProcessRequest(IDotvvmRequestContext context) })) .OrderBy(m => m.Namespace).ThenBy(m => m.Name); - await context.HttpContext.Response.WriteAsync("ExtensionMethodsCache dump: " + JsonConvert.SerializeObject(dump)); + await context.HttpContext.Response.WriteAsync("ExtensionMethodsCache dump: " + JsonConvert.SerializeObject(dump, Formatting.Indented)); } } } diff --git a/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs b/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs index 3900fcf6d5..3f9807656a 100644 --- a/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs +++ b/src/Samples/Tests/Tests/Feature/StaticCommandTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -817,35 +818,38 @@ public void Feature_List_Translation_Remove_Range() Assert.Equal(new List { "1", "2", "8", "9", "10" }, column); }); } - + [Fact] - public async Task Feature_List_Translation_Remove_Reverse() + public void Feature_List_Translation_Remove_Reverse() { - var wasError = false; - RunInAllBrowsers(browser => { browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_ListMethodTranslations); - browser.Wait(1000); - if (browser.FindElements(".exceptionMessage").Count > 0) - { - wasError = true; - return; - } - var rows = GetSortedRow(browser, "Reverse"); var column = GetColumnContent(rows, 0); browser.WaitFor(() => Assert.Equal(10, column.Count), 500); Assert.Equal(new List { "10", "9", "8", "7", "6", "5", "4", "3", "2", "1" }, column); }); + } + + [Fact] + public async Task Feature_ExtensionMethodsNotResolvedOnStartup() + { + var client = new HttpClient(); + + // try to visit the page + var pageResponse = await client.GetAsync(TestSuiteRunner.Configuration.BaseUrls[0].TrimEnd('/') + "/" + SamplesRouteUrls.FeatureSamples_JavascriptTranslation_ListMethodTranslations); + TestOutput.WriteLine($"Page response: {(int)pageResponse.StatusCode}"); + var wasError = pageResponse.StatusCode != HttpStatusCode.OK; + // dump extension methods on the output + var json = await client.GetStringAsync(TestSuiteRunner.Configuration.BaseUrls[0].TrimEnd('/') + "/dump-extension-methods"); + TestOutput.WriteLine(json); + if (wasError) { - // error page - extension methods not found - var client = new HttpClient(); - var json = await client.GetStringAsync(TestSuiteRunner.Configuration.BaseUrls[0].TrimEnd('/') + "/dump-extension-methods"); - TestOutput.WriteLine(json); - throw new Exception("Test failed"); + // fail the test on error + throw new Exception("Extension methods were not resolved on application startup."); } } From 97c19a81af40269ff80384f6d42577ec1969074b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Thu, 4 Apr 2024 12:43:19 +0200 Subject: [PATCH 4/8] JS: fix VM validation inside dynamic object --- .../Scripts/tests/stateManagement.data.ts | 10 +++++++++ .../Scripts/tests/validation.test.ts | 21 +++++++++++++++++++ .../Scripts/validation/validation.ts | 7 +++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts b/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts index 95e55e9a48..32c04813a4 100644 --- a/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts +++ b/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts @@ -115,6 +115,16 @@ initDotvvm({ "C": 4, "D": 8, } + }, + tValidated: { + type: "object", + properties: { + RegexValidated: { + validationRules: [ + { ruleName: "regularExpression", errorMessage: "Must have even length", parameters: ["^(..)+$"] } + ] + } + } } } }) diff --git a/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts b/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts index 1e11d688d4..f0226ba970 100644 --- a/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts +++ b/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts @@ -3,6 +3,12 @@ import { globalValidationObject as validation, ValidationErrorDescriptor } from import { createComplexObservableSubViewmodel, createComplexObservableViewmodel, ObservableHierarchy, ObservableSubHierarchy } from "./observableHierarchies" import { getErrors } from "../validation/error" import { setLogger } from "../utils/logging"; +import { runClientSideValidation } from '../validation/validation' +import dotvvm from '../dotvvm-root' +import { getStateManager } from "../dotvvm-base"; +import { StateManager } from "../state-manager"; + +require("./stateManagement.data") describe("DotVVM.Validation - public API", () => { @@ -319,6 +325,21 @@ describe("DotVVM.Validation - public API", () => { }) }); + +describe("DotVVM.Validation - view model validation", () => { + const s = getStateManager() as StateManager + test("Validated object in dynamic", () => { + dotvvm.updateState(x => ({...x, Dynamic: { something: "abc", validatedObj: { $type: "tValidated", RegexValidated: "abcd" } } })) + s.doUpdateNow() + runClientSideValidation(s.stateObservable, {} as any) + expect(dotvvm.validation.errors).toHaveLength(0) + s.patchState({ Dynamic: { validatedObj: { RegexValidated: "abcde" }}}) + s.doUpdateNow() + runClientSideValidation(s.stateObservable, {} as any) + expect(dotvvm.validation.errors).toHaveLength(1) + }) +}) + function SetupComplexObservableViewmodelWithErrorsOnProp1AndProp21() { validation.removeErrors("/"); const vm = createComplexObservableViewmodel(); diff --git a/src/Framework/Framework/Resources/Scripts/validation/validation.ts b/src/Framework/Framework/Resources/Scripts/validation/validation.ts index 6c62f2b541..64d707d909 100644 --- a/src/Framework/Framework/Resources/Scripts/validation/validation.ts +++ b/src/Framework/Framework/Resources/Scripts/validation/validation.ts @@ -66,7 +66,7 @@ const createValidationHandler = (pathFunction: (context: KnockoutBindingContext) } }) -const runClientSideValidation = (validationTarget: any, options: PostbackOptions) => { +export const runClientSideValidation = (validationTarget: any, options: PostbackOptions) => { watchAndTriggerValidationErrorChanged(options, () => { @@ -171,6 +171,9 @@ function validateViewModel(viewModel: any, path: string): void { } function validateRecursive(observable: KnockoutObservable, propertyValue: any, type: TypeDefinition, propertyPath: string) { + if (compileConstants.debug && !ko.isObservable(observable)) { + throw Error(`Property ${propertyPath} isn't a knockout observable and cannot be validated.`) + } const lastSetError: CoerceResult = (observable as any)[lastSetErrorSymbol]; if (lastSetError && lastSetError.isError) { ValidationError.attach(lastSetError.message, propertyPath, observable); @@ -203,7 +206,7 @@ function validateRecursive(observable: KnockoutObservable, propertyValue: a validateViewModel(propertyValue, propertyPath); } else { for (const k of keys(propertyValue)) { - validateRecursive(ko.unwrap(propertyValue[k]), propertyValue[k], { type: "dynamic" }, propertyPath + "/" + k); + validateRecursive(propertyValue[k], ko.unwrap(propertyValue[k]), { type: "dynamic" }, propertyPath + "/" + k); } } } From 7ced2df3a494730995cd50abfd2fcc3061008058 Mon Sep 17 00:00:00 2001 From: Miroslav Adamec Date: Thu, 18 Apr 2024 13:16:09 +0200 Subject: [PATCH 5/8] feat: app insights - add grouped operation name --- .../OperationNameTelemetryInitializer.cs | 31 +++++++++++++++++++ .../TracingBuilderExtensions.cs | 1 + ...VM.Tracing.ApplicationInsights.Owin.csproj | 1 + .../OperationNameTelemetryInitializer.cs | 24 ++++++++++++++ .../TracingBuilderExtensions.cs | 1 + 5 files changed, 58 insertions(+) create mode 100644 src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs create mode 100644 src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs diff --git a/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs b/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs new file mode 100644 index 0000000000..1fe87fb3fb --- /dev/null +++ b/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs @@ -0,0 +1,31 @@ +using DotVVM.Framework.Hosting; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.AspNetCore.Http; + +namespace DotVVM.Tracing.ApplicationInsights.AspNetCore; + +public class OperationNameTelemetryInitializer : ITelemetryInitializer +{ + private readonly IHttpContextAccessor _accessor; + + public OperationNameTelemetryInitializer(IHttpContextAccessor accessor) + { + _accessor = accessor; + } + + public void Initialize(ITelemetry telemetry) + { + var url = _accessor.HttpContext?.GetDotvvmContext()?.Route?.Url; + if (string.IsNullOrWhiteSpace(url) == false && telemetry is RequestTelemetry) + { + var method = _accessor.HttpContext.Request.Method; + var operationName = $"{method} /{url}"; + + var requestTelemetry = telemetry as RequestTelemetry; + requestTelemetry.Name = operationName; + requestTelemetry.Context.Operation.Name = operationName; + } + } +} diff --git a/src/Tracing/ApplicationInsights.AspNetCore/TracingBuilderExtensions.cs b/src/Tracing/ApplicationInsights.AspNetCore/TracingBuilderExtensions.cs index bf370072cf..114564f26d 100644 --- a/src/Tracing/ApplicationInsights.AspNetCore/TracingBuilderExtensions.cs +++ b/src/Tracing/ApplicationInsights.AspNetCore/TracingBuilderExtensions.cs @@ -30,6 +30,7 @@ public static IDotvvmServiceCollection AddApplicationInsightsTracing(this IDotvv services.AddDotvvmApplicationInsights(); services.Services.AddApplicationInsightsTelemetryProcessor(); + services.Services.AddSingleton(); services.Services.TryAddSingleton(); services.Services.AddTransient, ApplicationInsightSetup>(); diff --git a/src/Tracing/ApplicationInsights.Owin/DotVVM.Tracing.ApplicationInsights.Owin.csproj b/src/Tracing/ApplicationInsights.Owin/DotVVM.Tracing.ApplicationInsights.Owin.csproj index 80f82fc59a..25a52f73b1 100644 --- a/src/Tracing/ApplicationInsights.Owin/DotVVM.Tracing.ApplicationInsights.Owin.csproj +++ b/src/Tracing/ApplicationInsights.Owin/DotVVM.Tracing.ApplicationInsights.Owin.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs b/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs new file mode 100644 index 0000000000..f8fe79ab08 --- /dev/null +++ b/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs @@ -0,0 +1,24 @@ +using System.Web; +using DotVVM.Framework.Hosting; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.Channel; + +namespace DotVVM.Tracing.ApplicationInsights.Owin; + +public class OperationNameTelemetryInitializer : ITelemetryInitializer +{ + public void Initialize(ITelemetry telemetry) + { + var url = HttpContext.Current?.GetOwinContext()?.GetDotvvmContext()?.Route?.Url; + if (string.IsNullOrWhiteSpace(url) == false && telemetry is RequestTelemetry) + { + var method = HttpContext.Current.Request.HttpMethod; + var operationName = $"{method} /{url}"; + + var requestTelemetry = telemetry as RequestTelemetry; + requestTelemetry.Name = operationName; + requestTelemetry.Context.Operation.Name = operationName; + } + } +} diff --git a/src/Tracing/ApplicationInsights.Owin/TracingBuilderExtensions.cs b/src/Tracing/ApplicationInsights.Owin/TracingBuilderExtensions.cs index 5941e8717e..582d2bde0e 100644 --- a/src/Tracing/ApplicationInsights.Owin/TracingBuilderExtensions.cs +++ b/src/Tracing/ApplicationInsights.Owin/TracingBuilderExtensions.cs @@ -18,6 +18,7 @@ public static class TracingBuilderExtensions public static IDotvvmServiceCollection AddApplicationInsightsTracing(this IDotvvmServiceCollection services) { TelemetryConfiguration.Active.TelemetryProcessorChainBuilder.Use(next => new RequestTelemetryFilter(next)).Build(); + TelemetryConfiguration.Active.TelemetryInitializers.Add(new OperationNameTelemetryInitializer()); services.Services.TryAddSingleton(); services.AddDotvvmApplicationInsights(); From 19dbeed66568bba122492dd1934b9f14321399f2 Mon Sep 17 00:00:00 2001 From: Miroslav Adamec Date: Mon, 22 Apr 2024 14:59:49 +0200 Subject: [PATCH 6/8] improve http context operations for telemetry --- .../OperationNameTelemetryInitializer.cs | 7 ++++--- .../OperationNameTelemetryInitializer.cs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs b/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs index 1fe87fb3fb..bc39bc5823 100644 --- a/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs +++ b/src/Tracing/ApplicationInsights.AspNetCore/OperationNameTelemetryInitializer.cs @@ -17,10 +17,11 @@ public OperationNameTelemetryInitializer(IHttpContextAccessor accessor) public void Initialize(ITelemetry telemetry) { - var url = _accessor.HttpContext?.GetDotvvmContext()?.Route?.Url; - if (string.IsNullOrWhiteSpace(url) == false && telemetry is RequestTelemetry) + var context = _accessor.HttpContext; + var url = context?.GetDotvvmContext()?.Route?.Url; + if (url != null && telemetry is RequestTelemetry) { - var method = _accessor.HttpContext.Request.Method; + var method = context.Request.Method; var operationName = $"{method} /{url}"; var requestTelemetry = telemetry as RequestTelemetry; diff --git a/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs b/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs index f8fe79ab08..d0cda14c33 100644 --- a/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs +++ b/src/Tracing/ApplicationInsights.Owin/OperationNameTelemetryInitializer.cs @@ -10,10 +10,11 @@ public class OperationNameTelemetryInitializer : ITelemetryInitializer { public void Initialize(ITelemetry telemetry) { - var url = HttpContext.Current?.GetOwinContext()?.GetDotvvmContext()?.Route?.Url; - if (string.IsNullOrWhiteSpace(url) == false && telemetry is RequestTelemetry) + var context = HttpContext.Current; + var url = context?.GetOwinContext()?.GetDotvvmContext()?.Route?.Url; + if (url != null && telemetry is RequestTelemetry) { - var method = HttpContext.Current.Request.HttpMethod; + var method = context.Request.HttpMethod; var operationName = $"{method} /{url}"; var requestTelemetry = telemetry as RequestTelemetry; From c46dcd345dbaac388b5221af93a9f906a2c0217c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 21 Apr 2024 18:02:56 +0200 Subject: [PATCH 7/8] Fixed too early initialization of view compilation service --- src/Framework/Hosting.AspNetCore/Hosting/DotvvmHealthCheck.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Framework/Hosting.AspNetCore/Hosting/DotvvmHealthCheck.cs b/src/Framework/Hosting.AspNetCore/Hosting/DotvvmHealthCheck.cs index 847f3c186b..de647fe0ae 100644 --- a/src/Framework/Hosting.AspNetCore/Hosting/DotvvmHealthCheck.cs +++ b/src/Framework/Hosting.AspNetCore/Hosting/DotvvmHealthCheck.cs @@ -47,14 +47,14 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc public static void RegisterHealthCheck(IServiceCollection services) { - services.ConfigureWithServices((options, s) => { + services.Configure(options => { if (options.Registrations.Any(c => c.Name == "DotVVM")) return; options.Registrations.Add( new HealthCheckRegistration( "DotVVM", - ActivatorUtilities.CreateInstance(s), + s => ActivatorUtilities.CreateInstance(s), null, new [] { "dotvvm" } ) From 0871b6ae925bff23cca58a75f122e236d86856a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20=C5=A0est=C3=A1k?= Date: Mon, 6 May 2024 12:39:47 +0100 Subject: [PATCH 8/8] Changed casting of ChromeDriver to ChromiumDriver - MS Edge fix --- src/Samples/Tests/Tests/Complex/SPAErrorReportingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Samples/Tests/Tests/Complex/SPAErrorReportingTests.cs b/src/Samples/Tests/Tests/Complex/SPAErrorReportingTests.cs index db5b0ea1e0..7bbfab9bf2 100644 --- a/src/Samples/Tests/Tests/Complex/SPAErrorReportingTests.cs +++ b/src/Samples/Tests/Tests/Complex/SPAErrorReportingTests.cs @@ -32,7 +32,7 @@ public void Complex_SPAErrorReporting_NavigationAndPostbacks() void SetOfflineMode(bool offline) { - ((ChromeDriver)browser.Driver).NetworkConditions = new ChromiumNetworkConditions() { + ((ChromiumDriver)browser.Driver).NetworkConditions = new ChromiumNetworkConditions() { IsOffline = offline, Latency = TimeSpan.FromMilliseconds(5), DownloadThroughput = 500 * 1024,