From 87f13a19c085ab4b6006c9b388e36ac0e628af25 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:56:11 -0700 Subject: [PATCH 01/21] Update branding to 6.0.5 (#41049) --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 7677e2bb8248..bb2938612641 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,8 +8,8 @@ 6 0 - 4 - true + 5 + false From d60e995e8c2023f34d42779765cb672434cd982d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:57:07 -0700 Subject: [PATCH 02/21] [release/6.0] Get richnav.yml building again (#40783) * Get richnav.yml building again - correct build.cmd location - generally align richnav.yml with ci.yml - ignore signing, internal download args, and publish args nit: correct name of one build step in ci.yml * Disable pipeline in PR validation - also remove irrelevant trigger branches * !fixup! Try doing native build w/o rich nav nit: put `-no`s together * !fixup! Disable rich nav explicitly - also remove installers build entirely; build will fail nits: - don't bother setting `$(EnableRichCodeNavigation)` or `$(OnlyPackPlatformSpecificPackages)` to `true` - job.yml sets `$(EnableRichCodeNavigation)` variable and `msbuild` will find that in environment - `$(OnlyPackPlatformSpecificPackages)` is not relevant in this pipeline; not packing Co-authored-by: Doug Bunting <6431421+dougbu@users.noreply.github.com> --- .azure/pipelines/richnav.yml | 48 ++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/.azure/pipelines/richnav.yml b/.azure/pipelines/richnav.yml index 46d8b9e3cb75..e5fe8756d315 100644 --- a/.azure/pipelines/richnav.yml +++ b/.azure/pipelines/richnav.yml @@ -6,59 +6,69 @@ trigger: branches: include: - - blazor-wasm - main - release/* - - internal/release/* + +# Do not run this pipeline for PR validation. +pr: none variables: - name: _BuildArgs value: '/p:SkipTestBuild=true' -- name: Windows86LogArgs +- name: WindowsNonX64LogArgs value: -ExcludeCIBinaryLog stages: - stage: build displayName: Build jobs: - # Build Windows (x64/x86) + # Build Windows (x64/x86/arm64) - template: jobs/default-build.yml parameters: codeSign: false jobName: Windows_build - jobDisplayName: "Build: Windows x64/x86" + jobDisplayName: "Build: Windows x64/x86/arm64" enableRichCodeNavigation: true agentOs: Windows steps: - - script: ./build.cmd + - script: ./eng/build.cmd -ci - -all -arch x64 - /p:EnableRichCodeNavigation=true + -buildNative + /p:EnableRichCodeNavigation=false + $(_BuildArgs) + displayName: Build x64 native assets + + - script: ./eng/build.cmd + -ci + -arch x64 + -all + -noBuildNative + -noBuildRepoTasks $(_BuildArgs) displayName: Build x64 # Build the x86 shared framework # This is going to actually build x86 native assets. - - script: ./build.cmd + - script: ./eng/build.cmd -ci - -noBuildRepoTasks -arch x86 -all -noBuildJava -noBuildNative - /p:EnableRichCodeNavigation=true + -noBuildRepoTasks $(_BuildArgs) - $(Windows86LogArgs) + $(WindowsNonX64LogArgs) displayName: Build x86 - # Windows installers bundle both x86 and x64 assets - - script: ./build.cmd + # Build the arm64 shared framework + - script: ./eng/build.cmd -ci - -noBuildRepoTasks - -buildInstallers + -arch arm64 + -noBuildJava -noBuildNative - /p:AssetManifestFileName=aspnetcore-win-x64-x86.xml - /p:EnableRichCodeNavigation=true + -noBuildRepoTasks $(_BuildArgs) - displayName: Build Installers + $(WindowsNonX64LogArgs) + displayName: Build ARM64 + From eb694ab14702d9ca683f5ed15ada4ab2f0bda7b9 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 5 Apr 2022 15:58:37 -0700 Subject: [PATCH 03/21] Support falling back to ConnectionId when RawConnectionId is 0 on HTTP.sys (#40760) --- src/Servers/HttpSys/src/RequestProcessing/Request.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index ffffc050672c..880beebf562d 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -120,7 +120,7 @@ internal Request(RequestContext requestContext) internal ulong RawConnectionId { get; } // No ulongs in public APIs... - public long ConnectionId => (long)RawConnectionId; + public long ConnectionId => RawConnectionId != 0 ? (long)RawConnectionId : (long)UConnectionId; internal ulong RequestId { get; } From 8107aa5e6efd9c36a9da70353c8ece7339425659 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:58:54 -0700 Subject: [PATCH 04/21] Cleanup logs after successful test runs (#40807) - backport for #39038 - `cherry-pick` of 6958517ccd76 - update `AssemblyTestLog` to perform actual log directory cleanup - add `ReportTestFailure()` method for tests to report failures, disabling cleanup - add `IAcceptFailureReports` for `AspNetTestAssemblyRunner` to report failures to `AssemblyTestLog` - extend `AspNetTestAssemblyRunner` to optionally create fixture instances using `static ForAssembly(Assembly)` - add `AssemblyTestLogFixtureAttribute` to register `AssemblyTestLog` as an assembly fixture - use `AssemblyTestLogFixtureAttribute` in all test projects - disable log cleanup in three existing tests - add tests of new cleanup features - also cover a few existing methods - do cleanup before creating new logger - was deleting the just-created global.log file nits: - use `is [not] null` and `new()` more - move `AssemblyTestLogTests` to same namespace as `AssemblyTestLog` --- .../Mvc.FunctionalTests/ErrorPageTests.cs | 12 +- src/Testing/src/AssemblyTestLog.cs | 72 ++++-- .../src/AssemblyTestLogFixtureAttribute.cs | 11 + .../build/Microsoft.AspNetCore.Testing.props | 5 +- .../src/xunit/AspNetTestAssemblyRunner.cs | 55 +++- .../src/xunit/IAcceptFailureReports.cs | 9 + .../test/AspNetTestAssemblyRunnerTest.cs | 219 ++++++++++++++++ src/Testing/test/AssemblyTestLogTests.cs | 243 +++++++++++++++--- .../test/TestableAspNetTestAssemblyRunner.cs | 105 ++++++++ src/Testing/test/TestableAssembly.cs | 95 +++++++ 10 files changed, 755 insertions(+), 71 deletions(-) create mode 100644 src/Testing/src/AssemblyTestLogFixtureAttribute.cs create mode 100644 src/Testing/src/xunit/IAcceptFailureReports.cs create mode 100644 src/Testing/test/AspNetTestAssemblyRunnerTest.cs create mode 100644 src/Testing/test/TestableAspNetTestAssemblyRunner.cs create mode 100644 src/Testing/test/TestableAssembly.cs diff --git a/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs b/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs index 11ff6b17d899..ccfb60a40bb4 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs @@ -1,14 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.IO; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text.Encodings.Web; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; using Microsoft.AspNetCore.TestHost; @@ -16,7 +12,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -24,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests /// /// Functional test to verify the error reporting of Razor compilation by diagnostic middleware. /// - public class ErrorPageTests : IClassFixture>, IDisposable + public class ErrorPageTests : IClassFixture> { private static readonly string PreserveCompilationContextMessage = HtmlEncoder.Default.Encode( "One or more compilation references may be missing. " + @@ -189,10 +184,5 @@ public async Task AggregateException_FlattensInnerExceptions() Assert.Contains(nullReferenceException, content); Assert.Contains(indexOutOfRangeException, content); } - - public void Dispose() - { - _assemblyTestLog.Dispose(); - } } } diff --git a/src/Testing/src/AssemblyTestLog.cs b/src/Testing/src/AssemblyTestLog.cs index 0e3d06f2eb3e..4e03f7ca3035 100644 --- a/src/Testing/src/AssemblyTestLog.cs +++ b/src/Testing/src/AssemblyTestLog.cs @@ -7,10 +7,8 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; @@ -22,20 +20,21 @@ namespace Microsoft.AspNetCore.Testing { - public class AssemblyTestLog : IDisposable + public class AssemblyTestLog : IAcceptFailureReports, IDisposable { private const string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH"; private const string LogFileExtension = ".log"; private static readonly int MaxPathLength = GetMaxPathLength(); - private static readonly object _lock = new object(); - private static readonly Dictionary _logs = new Dictionary(); + private static readonly object _lock = new(); + private static readonly Dictionary _logs = new(); private readonly ILoggerFactory _globalLoggerFactory; private readonly ILogger _globalLogger; private readonly string _baseDirectory; private readonly Assembly _assembly; private readonly IServiceProvider _serviceProvider; + private bool _testFailureReported; private static int GetMaxPathLength() { @@ -53,6 +52,9 @@ private AssemblyTestLog(ILoggerFactory globalLoggerFactory, ILogger globalLogger _serviceProvider = serviceProvider; } + // internal for testing + internal bool OnCI { get; set; } = SkipOnCIAttribute.OnCI(); + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) => StartTestLog(output, className, out loggerFactory, LogLevel.Debug, testName); @@ -178,11 +180,8 @@ public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string cl return serviceCollection.BuildServiceProvider(); } - // For back compat - public static AssemblyTestLog Create(string assemblyName, string baseDirectory) - => Create(Assembly.Load(new AssemblyName(assemblyName)), baseDirectory); - - public static AssemblyTestLog Create(Assembly assembly, string baseDirectory) + // internal for testing. Expectation is AspNetTestAssembly runner calls ForAssembly() first for every Assembly. + internal static AssemblyTestLog Create(Assembly assembly, string baseDirectory) { var logStart = DateTimeOffset.UtcNow; SerilogLoggerProvider serilogLoggerProvider = null; @@ -224,26 +223,46 @@ public static AssemblyTestLog ForAssembly(Assembly assembly) { if (!_logs.TryGetValue(assembly, out var log)) { - var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly); + var stackTrace = Environment.StackTrace; + if (!stackTrace.Contains( + "Microsoft.AspNetCore.Testing" +#if NETCOREAPP + , StringComparison.Ordinal +#endif + )) + { + throw new InvalidOperationException($"Unexpected initial {nameof(ForAssembly)} caller."); + } - log = Create(assembly, baseDirectory); - _logs[assembly] = log; + var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly); - // Try to clear previous logs, continue if it fails. + // Try to clear previous logs, continue if it fails. Do this before creating new global logger. var assemblyBaseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly); - if (!string.IsNullOrEmpty(assemblyBaseDirectory) && !TestFileOutputContext.GetPreserveExistingLogsInOutput(assembly)) + if (!string.IsNullOrEmpty(assemblyBaseDirectory) && + !TestFileOutputContext.GetPreserveExistingLogsInOutput(assembly)) { try { Directory.Delete(assemblyBaseDirectory, recursive: true); } - catch { } + catch + { + } } + + log = Create(assembly, baseDirectory); + _logs[assembly] = log; } + return log; } } + public void ReportTestFailure() + { + _testFailureReported = true; + } + private static TestFrameworkFileLoggerAttribute GetFileLoggerAttribute(Assembly assembly) => assembly.GetCustomAttribute() ?? throw new InvalidOperationException($"No {nameof(TestFrameworkFileLoggerAttribute)} found on the assembly {assembly.GetName().Name}. " @@ -269,13 +288,32 @@ private static SerilogLoggerProvider ConfigureFileLogging(string fileName, DateT .MinimumLevel.Verbose() .WriteTo.File(fileName, outputTemplate: "[{TimestampOffset}] [{SourceContext}] [{Level}] {Message:l}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true) .CreateLogger(); + return new SerilogLoggerProvider(serilogger, dispose: true); } - public void Dispose() + void IDisposable.Dispose() { (_serviceProvider as IDisposable)?.Dispose(); _globalLoggerFactory.Dispose(); + + // Clean up if no tests failed and we're not running local tests. (Ignoring tests of this class, OnCI is + // true on both build and Helix agents.) In particular, remove the directory containing the global.log + // file. All test class log files for this assembly are in subdirectories of this location. + if (!_testFailureReported && + OnCI && + _baseDirectory is not null && + Directory.Exists(_baseDirectory)) + { + try + { + Directory.Delete(_baseDirectory, recursive: true); + } + catch + { + // Best effort. Ignore problems deleting locked logged files. + } + } } private class AssemblyLogTimestampOffsetEnricher : ILogEventEnricher diff --git a/src/Testing/src/AssemblyTestLogFixtureAttribute.cs b/src/Testing/src/AssemblyTestLogFixtureAttribute.cs new file mode 100644 index 000000000000..e4a4452cd458 --- /dev/null +++ b/src/Testing/src/AssemblyTestLogFixtureAttribute.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Testing; + +public class AssemblyTestLogFixtureAttribute : AssemblyFixtureAttribute +{ + public AssemblyTestLogFixtureAttribute() : base(typeof(AssemblyTestLog)) + { + } +} diff --git a/src/Testing/src/build/Microsoft.AspNetCore.Testing.props b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props index 063e9094d172..47d06dfef7a7 100644 --- a/src/Testing/src/build/Microsoft.AspNetCore.Testing.props +++ b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props @@ -11,8 +11,8 @@ + BeforeTargets="GetAssemblyAttributes" + Condition="'$(GenerateLoggingTestingAssemblyAttributes)' != 'false'"> true false @@ -24,6 +24,7 @@ <_Parameter2>Microsoft.AspNetCore.Testing + <_Parameter1>$(PreserveExistingLogsInOutput) <_Parameter2>$(TargetFramework) diff --git a/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs index a83446375ddd..1d71bdf939be 100644 --- a/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs +++ b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Testing { public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner { - private readonly Dictionary _assemblyFixtureMappings = new Dictionary(); + private readonly Dictionary _assemblyFixtureMappings = new(); public AspNetTestAssemblyRunner( ITestAssembly testAssembly, @@ -26,6 +27,9 @@ public AspNetTestAssemblyRunner( { } + // internal for testing + internal IEnumerable Fixtures => _assemblyFixtureMappings.Values; + protected override async Task AfterTestAssemblyStartingAsync() { await base.AfterTestAssemblyStartingAsync(); @@ -33,8 +37,8 @@ protected override async Task AfterTestAssemblyStartingAsync() // Find all the AssemblyFixtureAttributes on the test assembly await Aggregator.RunAsync(async () => { - var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly) - .Assembly + var assembly = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly; + var fixturesAttributes = assembly .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false) .Cast() .ToList(); @@ -42,15 +46,30 @@ await Aggregator.RunAsync(async () => // Instantiate all the fixtures foreach (var fixtureAttribute in fixturesAttributes) { - var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) }); object instance = null; - if (ctorWithDiagnostics != null) + var staticCreator = fixtureAttribute.FixtureType.GetMethod( + name: "ForAssembly", + bindingAttr: BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new[] { typeof(Assembly) }, + modifiers: null); + if (staticCreator is null) { - instance = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink); + var ctorWithDiagnostics = fixtureAttribute + .FixtureType + .GetConstructor(new[] { typeof(IMessageSink) }); + if (ctorWithDiagnostics is null) + { + instance = Activator.CreateInstance(fixtureAttribute.FixtureType); + } + else + { + instance = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink); + } } else { - instance = Activator.CreateInstance(fixtureAttribute.FixtureType); + instance = staticCreator.Invoke(obj: null, parameters: new[] { assembly }); } _assemblyFixtureMappings[fixtureAttribute.FixtureType] = instance; @@ -66,12 +85,12 @@ await Aggregator.RunAsync(async () => protected override async Task BeforeTestAssemblyFinishedAsync() { // Dispose fixtures - foreach (var disposable in _assemblyFixtureMappings.Values.OfType()) + foreach (var disposable in Fixtures.OfType()) { Aggregator.Run(disposable.Dispose); } - foreach (var disposable in _assemblyFixtureMappings.Values.OfType()) + foreach (var disposable in Fixtures.OfType()) { await Aggregator.RunAsync(disposable.DisposeAsync); } @@ -79,12 +98,13 @@ protected override async Task BeforeTestAssemblyFinishedAsync() await base.BeforeTestAssemblyFinishedAsync(); } - protected override Task RunTestCollectionAsync( + protected override async Task RunTestCollectionAsync( IMessageBus messageBus, ITestCollection testCollection, IEnumerable testCases, CancellationTokenSource cancellationTokenSource) - => new AspNetTestCollectionRunner( + { + var runSummary = await new AspNetTestCollectionRunner( _assemblyFixtureMappings, testCollection, testCases, @@ -92,6 +112,17 @@ protected override Task RunTestCollectionAsync( messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), - cancellationTokenSource).RunAsync(); + cancellationTokenSource) + .RunAsync(); + if (runSummary.Failed != 0) + { + foreach (var fixture in Fixtures.OfType()) + { + fixture.ReportTestFailure(); + } + } + + return runSummary; + } } } diff --git a/src/Testing/src/xunit/IAcceptFailureReports.cs b/src/Testing/src/xunit/IAcceptFailureReports.cs new file mode 100644 index 000000000000..30ca366b3f1b --- /dev/null +++ b/src/Testing/src/xunit/IAcceptFailureReports.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Testing; + +internal interface IAcceptFailureReports +{ + void ReportTestFailure(); +} diff --git a/src/Testing/test/AspNetTestAssemblyRunnerTest.cs b/src/Testing/test/AspNetTestAssemblyRunnerTest.cs new file mode 100644 index 000000000000..dbce4c69bb82 --- /dev/null +++ b/src/Testing/test/AspNetTestAssemblyRunnerTest.cs @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing; + +public class AspNetTestAssemblyRunnerTest +{ + private const int NotCalled = -1; + + [Fact] + public async Task ForAssemblyHasHigherPriorityThanConstructors() + { + var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithAll)); + + await runner.AfterTestAssemblyStartingAsync_Public(); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.False(fixture.ConstructorWithMessageSinkCalled); + Assert.True(fixture.ForAssemblyCalled); + Assert.False(fixture.ParameterlessConstructorCalled); + } + + [Fact] + public async Task ConstructorWithMessageSinkHasHigherPriorityThanParameterlessConstructor() + { + var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithMessageSink)); + + await runner.AfterTestAssemblyStartingAsync_Public(); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.True(fixture.ConstructorWithMessageSinkCalled); + Assert.False(fixture.ParameterlessConstructorCalled); + } + + [Fact] + public async Task CalledInExpectedOrder_SuccessWithDispose() + { + var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TextAssemblyFixtureWithDispose)); + + var runSummary = await runner.RunAsync(); + + Assert.NotNull(runSummary); + Assert.Equal(0, runSummary.Failed); + Assert.Equal(0, runSummary.Skipped); + Assert.Equal(1, runSummary.Total); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.Equal(NotCalled, fixture.ReportTestFailureCalledAt); + Assert.Equal(0, fixture.DisposeCalledAt); + } + + [Fact] + public async Task CalledInExpectedOrder_FailedWithDispose() + { + var runner = TestableAspNetTestAssemblyRunner.Create( + typeof(TextAssemblyFixtureWithDispose), + failTestCase: true); + + var runSummary = await runner.RunAsync(); + + Assert.NotNull(runSummary); + Assert.Equal(1, runSummary.Failed); + Assert.Equal(0, runSummary.Skipped); + Assert.Equal(1, runSummary.Total); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.Equal(0, fixture.ReportTestFailureCalledAt); + Assert.Equal(1, fixture.DisposeCalledAt); + } + + [Fact] + public async Task CalledInExpectedOrder_SuccessWithAsyncDispose() + { + var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithAsyncDispose)); + + var runSummary = await runner.RunAsync(); + + Assert.NotNull(runSummary); + Assert.Equal(0, runSummary.Failed); + Assert.Equal(0, runSummary.Skipped); + Assert.Equal(1, runSummary.Total); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.Equal(0, fixture.InitializeAsyncCalledAt); + Assert.Equal(NotCalled, fixture.ReportTestFailureCalledAt); + Assert.Equal(1, fixture.AsyncDisposeCalledAt); + } + + [Fact] + public async Task CalledInExpectedOrder_FailedWithAsyncDispose() + { + var runner = TestableAspNetTestAssemblyRunner.Create( + typeof(TestAssemblyFixtureWithAsyncDispose), + failTestCase: true); + + var runSummary = await runner.RunAsync(); + + Assert.NotNull(runSummary); + Assert.Equal(1, runSummary.Failed); + Assert.Equal(0, runSummary.Skipped); + Assert.Equal(1, runSummary.Total); + + Assert.NotNull(runner.Fixtures); + var fixtureObject = Assert.Single(runner.Fixtures); + var fixture = Assert.IsType(fixtureObject); + Assert.Equal(0, fixture.InitializeAsyncCalledAt); + Assert.Equal(1, fixture.ReportTestFailureCalledAt); + Assert.Equal(2, fixture.AsyncDisposeCalledAt); + } + + private class TestAssemblyFixtureWithAll + { + private TestAssemblyFixtureWithAll(bool forAssemblyCalled) + { + ForAssemblyCalled = forAssemblyCalled; + } + + public TestAssemblyFixtureWithAll() + { + ParameterlessConstructorCalled = true; + } + + public TestAssemblyFixtureWithAll(IMessageSink messageSink) + { + ConstructorWithMessageSinkCalled = true; + } + + public static TestAssemblyFixtureWithAll ForAssembly(Assembly assembly) + { + return new TestAssemblyFixtureWithAll(forAssemblyCalled: true); + } + + public bool ParameterlessConstructorCalled { get; } + + public bool ConstructorWithMessageSinkCalled { get; } + + public bool ForAssemblyCalled { get; } + } + + private class TestAssemblyFixtureWithMessageSink + { + public TestAssemblyFixtureWithMessageSink() + { + ParameterlessConstructorCalled = true; + } + + public TestAssemblyFixtureWithMessageSink(IMessageSink messageSink) + { + ConstructorWithMessageSinkCalled = true; + } + + public bool ParameterlessConstructorCalled { get; } + + public bool ConstructorWithMessageSinkCalled { get; } + } + + private class TextAssemblyFixtureWithDispose : IAcceptFailureReports, IDisposable + { + private int _position; + + public int ReportTestFailureCalledAt { get; private set; } = NotCalled; + + public int DisposeCalledAt { get; private set; } = NotCalled; + + void IAcceptFailureReports.ReportTestFailure() + { + ReportTestFailureCalledAt = _position++; + } + + void IDisposable.Dispose() + { + DisposeCalledAt = _position++; + } + } + + private class TestAssemblyFixtureWithAsyncDispose : IAcceptFailureReports, IAsyncLifetime + { + private int _position; + + public int InitializeAsyncCalledAt { get; private set; } = NotCalled; + + public int ReportTestFailureCalledAt { get; private set; } = NotCalled; + + public int AsyncDisposeCalledAt { get; private set; } = NotCalled; + + Task IAsyncLifetime.InitializeAsync() + { + InitializeAsyncCalledAt = _position++; + return Task.CompletedTask; + } + + void IAcceptFailureReports.ReportTestFailure() + { + ReportTestFailureCalledAt = _position++; + } + + Task IAsyncLifetime.DisposeAsync() + { + AsyncDisposeCalledAt = _position++; + return Task.CompletedTask; + } + } +} diff --git a/src/Testing/test/AssemblyTestLogTests.cs b/src/Testing/test/AssemblyTestLogTests.cs index 57c3b87b1681..bd3bbb1b8e29 100644 --- a/src/Testing/test/AssemblyTestLogTests.cs +++ b/src/Testing/test/AssemblyTestLogTests.cs @@ -4,21 +4,17 @@ using System; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing.Tests; using Xunit; -namespace Microsoft.Extensions.Logging.Testing.Tests +namespace Microsoft.AspNetCore.Testing { public class AssemblyTestLogTests : LoggedTest { - private static readonly Assembly ThisAssembly = typeof(AssemblyTestLogTests).GetTypeInfo().Assembly; - private static readonly string ThisAssemblyName = ThisAssembly.GetName().Name; - private static readonly string TFM = ThisAssembly.GetCustomAttributes().OfType().FirstOrDefault().TargetFramework; - [Fact] public void FunctionalLogs_LogsPreservedFromNonQuarantinedTest() { @@ -35,15 +31,52 @@ public void FunctionalLogs_LogsPreservedFromQuarantinedTest() public void ForAssembly_ReturnsSameInstanceForSameAssembly() { Assert.Same( - AssemblyTestLog.ForAssembly(ThisAssembly), - AssemblyTestLog.ForAssembly(ThisAssembly)); + AssemblyTestLog.ForAssembly(TestableAssembly.ThisAssembly), + AssemblyTestLog.ForAssembly(TestableAssembly.ThisAssembly)); } + [Fact] + public Task ForAssemblyWritesToAssemblyBaseDirectory() => + RunTestLogFunctionalTest((tempDir) => + { + var logger = LoggerFactory.CreateLogger("Test"); + + var assembly = TestableAssembly.Create(typeof(AssemblyTestLog), logDirectory: tempDir); + var assemblyName = assembly.GetName().Name; + var testName = $"{TestableAssembly.TestClassName}.{TestableAssembly.TestMethodName}"; + + var tfmPath = Path.Combine(tempDir, assemblyName, TestableAssembly.TFM); + var globalLogPath = Path.Combine(tfmPath, "global.log"); + var testLog = Path.Combine(tfmPath, TestableAssembly.TestClassName, $"{testName}.log"); + + using var testAssemblyLog = AssemblyTestLog.ForAssembly(assembly); + testAssemblyLog.OnCI = true; + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{assemblyName}.{TestableAssembly.TestClassName}", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: testName)) + { + var testLogger = testLoggerFactory.CreateLogger("TestLogger"); + testLogger.LogInformation("Information!"); + testLogger.LogTrace("Trace!"); + } + + Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist."); + Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist."); + + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + }); + [Fact] public void TestLogWritesToITestOutputHelper() { var output = new TestTestOutputHelper(); - var assemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: null); + + using var assemblyLog = AssemblyTestLog.Create(TestableAssembly.ThisAssembly, baseDirectory: null); using (assemblyLog.StartTestLog(output, "NonExistant.Test.Class", out var loggerFactory)) { @@ -69,11 +102,19 @@ public Task TestLogEscapesIllegalFileNames() => { var illegalTestName = "T:e/s//t"; var escapedTestName = "T_e_s_t"; - using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: tempDir)) - using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, out var _, testName: illegalTestName)) - { - Assert.Equal(escapedTestName, resolvedTestname); - } + + using var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir); + using var disposable = testAssemblyLog.StartTestLog( + output: null, + className: "FakeTestAssembly.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + resolvedTestName: out var resolvedTestname, + out var _, + testName: illegalTestName); + Assert.Equal(escapedTestName, resolvedTestname); }); [Fact] @@ -84,11 +125,19 @@ public Task TestLogWritesToGlobalLogFile() => // but it's also testing the test logging facility. So this is pretty meta ;) var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir)) { + testAssemblyLog.OnCI = false; logger.LogInformation("Created test log in {baseDirectory}", tempDir); - using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: "FakeTestName")) { var testLogger = testLoggerFactory.CreateLogger("TestLogger"); testLogger.LogInformation("Information!"); @@ -98,8 +147,17 @@ public Task TestLogWritesToGlobalLogFile() => logger.LogInformation("Finished test log in {baseDirectory}", tempDir); - var globalLogPath = Path.Combine(tempDir, ThisAssemblyName, TFM, "global.log"); - var testLog = Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"); + var globalLogPath = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "global.log"); + var testLog = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "FakeTestClass", + "FakeTestName.log"); Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist"); Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist"); @@ -120,31 +178,139 @@ [OFFSET] [TestLifetime] [Information] Finished test FakeTestName in DURATION ", testLogContent, ignoreLineEndingDifferences: true); }); + [Fact] + public Task TestLogCleansLogFiles_AfterSuccessfulRun() => + RunTestLogFunctionalTest((tempDir) => + { + var logger = LoggerFactory.CreateLogger("Test"); + var globalLogPath = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "global.log"); + var testLog = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "FakeTestClass", + "FakeTestName.log"); + + using (var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir)) + { + testAssemblyLog.OnCI = true; + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: "FakeTestName")) + { + var testLogger = testLoggerFactory.CreateLogger("TestLogger"); + testLogger.LogInformation("Information!"); + testLogger.LogTrace("Trace!"); + } + + Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist."); + Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist."); + } + + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + Assert.True(!File.Exists(globalLogPath), $"Expected no global log file {globalLogPath} to exist."); + Assert.True(!File.Exists(testLog), $"Expected no test log file {testLog} to exist."); + }); + + [Fact] + public Task TestLogDoesNotCleanLogFiles_AfterFailedRun() => + RunTestLogFunctionalTest((tempDir) => + { + var logger = LoggerFactory.CreateLogger("Test"); + var globalLogPath = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "global.log"); + var testLog = Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "FakeTestClass", + "FakeTestName.log"); + + using (var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir)) + { + testAssemblyLog.OnCI = true; + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: "FakeTestName")) + { + var testLogger = testLoggerFactory.CreateLogger("TestLogger"); + testLogger.LogInformation("Information!"); + testLogger.LogTrace("Trace!"); + } + + testAssemblyLog.ReportTestFailure(); + } + + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist."); + Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist."); + }); + [Fact] public Task TestLogTruncatesTestNameToAvoidLongPaths() => RunTestLogFunctionalTest((tempDir) => { - var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + new string('3', 50) + new string('4', 50); + var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + + new string('3', 50) + new string('4', 50); var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir)) { + testAssemblyLog.OnCI = false; logger.LogInformation("Created test log in {baseDirectory}", tempDir); - using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName)) + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: longTestName)) { testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); } } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); - var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass")).EnumerateFiles(); + var testLogFiles = new DirectoryInfo( + Path.Combine(tempDir, TestableAssembly.ThisAssemblyName, TestableAssembly.TFM, "FakeTestClass")) + .EnumerateFiles(); var testLog = Assert.Single(testLogFiles); var testFileName = Path.GetFileNameWithoutExtension(testLog.Name); // The first half of the file comes from the beginning of the test name passed to the logger - Assert.Equal(longTestName.Substring(0, testFileName.Length / 2), testFileName.Substring(0, testFileName.Length / 2)); + Assert.Equal( + longTestName.Substring(0, testFileName.Length / 2), + testFileName.Substring(0, testFileName.Length / 2)); + // The last half of the file comes from the ending of the test name passed to the logger - Assert.Equal(longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2), testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2)); + Assert.Equal( + longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2), + testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2)); }); [Fact] @@ -152,27 +318,46 @@ public Task TestLogEnumerateFilenamesToAvoidCollisions() => RunTestLogFunctionalTest((tempDir) => { var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create( + TestableAssembly.ThisAssembly, + baseDirectory: tempDir)) { + testAssemblyLog.OnCI = false; logger.LogInformation("Created test log in {baseDirectory}", tempDir); for (var i = 0; i < 10; i++) { - using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + using (testAssemblyLog.StartTestLog( + output: null, + className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass", + loggerFactory: out var testLoggerFactory, + minLogLevel: LogLevel.Trace, + testName: "FakeTestName")) { testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); } } } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); // The first log file exists - Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"))); + Assert.True(File.Exists(Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "FakeTestClass", + "FakeTestName.log"))); // Subsequent files exist for (var i = 0; i < 9; i++) { - Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", $"FakeTestName.{i}.log"))); + Assert.True(File.Exists(Path.Combine( + tempDir, + TestableAssembly.ThisAssemblyName, + TestableAssembly.TFM, + "FakeTestClass", + $"FakeTestName.{i}.log"))); } }); diff --git a/src/Testing/test/TestableAspNetTestAssemblyRunner.cs b/src/Testing/test/TestableAspNetTestAssemblyRunner.cs new file mode 100644 index 000000000000..17f9373b34c2 --- /dev/null +++ b/src/Testing/test/TestableAspNetTestAssemblyRunner.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using Moq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing; + +public class TestableAspNetTestAssemblyRunner : AspNetTestAssemblyRunner +{ + private TestableAspNetTestAssemblyRunner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) : base( + testAssembly, + testCases, + diagnosticMessageSink, + executionMessageSink, + executionOptions) + { + } + + public static TestableAspNetTestAssemblyRunner Create(Type fixtureType, bool failTestCase = false) + { + var assembly = TestableAssembly.Create(fixtureType, failTestCase: failTestCase); + var testAssembly = GetTestAssembly(assembly); + var testCase = GetTestCase(assembly, testAssembly); + + return new TestableAspNetTestAssemblyRunner( + testAssembly, + new[] { testCase }, + diagnosticMessageSink: new NullMessageSink(), + executionMessageSink: new NullMessageSink(), + executionOptions: Mock.Of()); + + // Do not call Xunit.Sdk.Reflector.Wrap(assembly) because it uses GetTypes() and that method + // throws NotSupportedException for a dynamic assembly. + IAssemblyInfo GetAssemblyInfo(Assembly assembly) + { + var testAssemblyName = assembly.GetName().Name; + var assemblyInfo = new Mock(); + assemblyInfo.SetupGet(r => r.Assembly).Returns(assembly); + assemblyInfo.SetupGet(r => r.Name).Returns(testAssemblyName); + assemblyInfo + .SetupGet(r => r.AssemblyPath) + .Returns(Path.Combine(Directory.GetCurrentDirectory(), $"{testAssemblyName}.dll")); + + foreach (var attribute in CustomAttributeData.GetCustomAttributes(assembly)) + { + var attributeInfo = Reflector.Wrap(attribute); + var attributeName = attribute.AttributeType.AssemblyQualifiedName; + assemblyInfo + .Setup(r => r.GetCustomAttributes(attributeName)) + .Returns(new[] { attributeInfo }); + } + + var typeInfo = Reflector.Wrap(assembly.GetType(TestableAssembly.TestClassName)); + assemblyInfo.Setup(r => r.GetType(TestableAssembly.TestClassName)).Returns(typeInfo); + assemblyInfo.Setup(r => r.GetTypes(It.IsAny())).Returns(new[] { typeInfo }); + + return assemblyInfo.Object; + } + + ITestAssembly GetTestAssembly(Assembly assembly) + { + var assemblyInfo = GetAssemblyInfo(assembly); + + return new TestAssembly(assemblyInfo); + } + + IXunitTestCase GetTestCase(Assembly assembly, ITestAssembly testAssembly) + { + var testAssemblyName = assembly.GetName().Name; + var testCollection = new TestCollection( + testAssembly, + collectionDefinition: null, + displayName: $"Mock collection for '{testAssemblyName}'."); + + var type = assembly.GetType(TestableAssembly.TestClassName); + var testClass = new TestClass(testCollection, Reflector.Wrap(type)); + var method = type.GetMethod(TestableAssembly.TestMethodName); + var methodInfo = Reflector.Wrap(method); + var testMethod = new TestMethod(testClass, methodInfo); + + return new XunitTestCase( + diagnosticMessageSink: new NullMessageSink(), + defaultMethodDisplay: TestMethodDisplay.ClassAndMethod, + defaultMethodDisplayOptions: TestMethodDisplayOptions.None, + testMethod: testMethod); + } + } + + public Task AfterTestAssemblyStartingAsync_Public() + { + return base.AfterTestAssemblyStartingAsync(); + } +} diff --git a/src/Testing/test/TestableAssembly.cs b/src/Testing/test/TestableAssembly.cs new file mode 100644 index 000000000000..fd4b557cca12 --- /dev/null +++ b/src/Testing/test/TestableAssembly.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Xunit; + +namespace Microsoft.AspNetCore.Testing; + +/* Creates a very simple dynamic assembly containing + * + * [Assembly: TestFramework( + * typeName: "Microsoft.AspNetCore.Testing.AspNetTestFramework", + * assemblyName: "Microsoft.AspNetCore.Testing")] + * [assembly: AssemblyFixture(typeof({fixtureType}))] + * [assembly: TestOutputDirectory( + * preserveExistingLogsInOutput: "false", + * targetFramework: TFM, + * baseDirectory: logDirectory)] // logdirectory is passed into Create(...). + * + * public class MyTestClass + * { + * public MyTestClass() { } + * + * [Fact] + * public MyTestMethod() + * { + * if (failTestCase) // Not exactly; condition checked during generation. + * { + * Assert.True(condition: false); + * } + * } + * } + */ +public static class TestableAssembly +{ + public static readonly Assembly ThisAssembly = typeof(TestableAssembly).GetTypeInfo().Assembly; + public static readonly string ThisAssemblyName = ThisAssembly.GetName().Name; + + private static readonly TestOutputDirectoryAttribute ThisOutputDirectoryAttribute = + ThisAssembly.GetCustomAttributes().OfType().FirstOrDefault(); + public static readonly string BaseDirectory = ThisOutputDirectoryAttribute.BaseDirectory; + public static readonly string TFM = ThisOutputDirectoryAttribute.TargetFramework; + + public const string TestClassName = "MyTestClass"; + public const string TestMethodName = "MyTestMethod"; + + public static Assembly Create(Type fixtureType, string logDirectory = null, bool failTestCase = false) + { + var frameworkConstructor = typeof(TestFrameworkAttribute) + .GetConstructor(new[] { typeof(string), typeof(string) }); + var frameworkBuilder = new CustomAttributeBuilder( + frameworkConstructor, + new[] { "Microsoft.AspNetCore.Testing.AspNetTestFramework", "Microsoft.AspNetCore.Testing" }); + + var fixtureConstructor = typeof(AssemblyFixtureAttribute).GetConstructor(new[] { typeof(Type) }); + var fixtureBuilder = new CustomAttributeBuilder(fixtureConstructor, new[] { fixtureType }); + + var outputConstructor = typeof(TestOutputDirectoryAttribute).GetConstructor( + new[] { typeof(string), typeof(string), typeof(string) }); + var outputBuilder = new CustomAttributeBuilder(outputConstructor, new[] { "false", TFM, logDirectory }); + + var testAssemblyName = $"Test{Guid.NewGuid():n}"; + var assemblyName = new AssemblyName(testAssemblyName); + var assembly = AssemblyBuilder.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.Run, + new[] { frameworkBuilder, fixtureBuilder, outputBuilder }); + + var module = assembly.DefineDynamicModule(testAssemblyName); + var type = module.DefineType(TestClassName, TypeAttributes.Public); + type.DefineDefaultConstructor(MethodAttributes.Public); + + var method = type.DefineMethod(TestMethodName, MethodAttributes.Public); + var factConstructor = typeof(FactAttribute).GetConstructor(Array.Empty()); + var factBuilder = new CustomAttributeBuilder(factConstructor, Array.Empty()); + method.SetCustomAttribute(factBuilder); + + var generator = method.GetILGenerator(); + if (failTestCase) + { + // Assert.True(condition: false); + generator.Emit(OpCodes.Ldc_I4_0); + var trueInfo = typeof(Assert).GetMethod("True", new[] { typeof(bool) }); + generator.EmitCall(OpCodes.Call, trueInfo, optionalParameterTypes: null); + } + + generator.Emit(OpCodes.Ret); + type.CreateType(); + + return assembly; + } +} From bcac6ee6f5cd34d17f8a3bfe026ec53681027d01 Mon Sep 17 00:00:00 2001 From: William Godbe Date: Tue, 5 Apr 2022 15:59:25 -0700 Subject: [PATCH 05/21] [release/6.0] Build ProjectTemplates in Source-Build (#40840) * Revert "Revert "[release/6.0] Build ProjectTemplates in Source-Build (#40650)" (#40805)" This reverts commit a351c437ac7bc9f9be96de51832608438a7a37df. * Update Microsoft.Authentication.WebAssembly.Msal.csproj * Update Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj * Update Microsoft.Authentication.WebAssembly.Msal.csproj * Update Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj * Workaround --- Directory.Build.props | 3 +++ Directory.Build.targets | 9 +++++++-- .../src/Microsoft.Authentication.WebAssembly.Msal.csproj | 9 ++++++++- ...pNetCore.Components.WebAssembly.Authentication.csproj | 9 ++++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8f0ac5942284..721f099a7734 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,6 +30,9 @@ $(MSBuildProjectName.EndsWith('.Test')) OR $(MSBuildProjectName.EndsWith('.FunctionalTest')) ) ">true true + true true true true + Condition="'$(ExcludeFromSourceBuild)' == '' and + '$(DotNetBuildFromSource)' == 'true' and + '$(IsAspNetCoreApp)' != 'true' and + '$(MSBuildProjectName)' != '$(TargetingPackName)' and + '$(IsAnalyzersProject)' != 'true' and + '$(IsProjectTemplateProject)' != 'true'">true diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj index 8ae5d1c43d7b..1db0368b882c 100644 --- a/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj +++ b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj @@ -1,6 +1,6 @@  - + $(DefaultNetCoreTargetFramework) @@ -25,6 +25,7 @@ $(MSBuildThisFileDirectory)Interop\ + CheckForSourceBuild; CompileInterop; IncludeCompileInteropOutput; $(ResolveStaticWebAssetsInputsDependsOn) @@ -91,5 +92,11 @@ + + + + + + diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj index 8d6a000d74f0..35c79a73eb8b 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj @@ -1,6 +1,6 @@ - + $(DefaultNetCoreTargetFramework) @@ -26,6 +26,7 @@ $(MSBuildThisFileDirectory)Interop\ + CheckForSourceBuild; CompileInterop; IncludeCompileInteropOutput; $(ResolveStaticWebAssetsInputsDependsOn) @@ -93,4 +94,10 @@ + + + + + + From a8ed5610fef2f9d6394d014d5775e047ac5cc222 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Tue, 5 Apr 2022 15:59:44 -0700 Subject: [PATCH 06/21] Allow leading CR/LF in HTTP request lines in Kestrel. (#40859) --- .../Core/src/Internal/Http/HttpParser.cs | 8 +++ .../Kestrel/Core/test/StartLineTests.cs | 50 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 064da12cb634..3c66964cafd4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -38,6 +38,14 @@ public HttpParser(bool showErrorDetails) public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader reader) { + // Skip any leading \r or \n on the request line. This is not technically allowed, + // but apparently there are enough clients relying on this that it's worth allowing. + // Peek first as a minor performance optimization; it's a quick inlined check. + if (reader.TryPeek(out byte b) && (b == ByteCR || b == ByteLF)) + { + reader.AdvancePastAny(ByteCR, ByteLF); + } + if (reader.TryReadTo(out ReadOnlySpan requestLine, ByteLF, advancePastDelimiter: true)) { ParseRequestLine(handler, requestLine); diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs index 4c65611e95e4..59fdf6dd0cc1 100644 --- a/src/Servers/Kestrel/Core/test/StartLineTests.cs +++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs @@ -6,6 +6,7 @@ using System.IO.Pipelines; using System.Text; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -516,6 +517,55 @@ public void AuthorityForms(string rawTarget, string path, string query) DifferentFormsWorkTogether(); } + public static IEnumerable GetCrLfAndMethodCombinations() + { + // HTTP methods to test + var methods = new string[] { + HttpMethods.Connect, + HttpMethods.Delete, + HttpMethods.Get, + HttpMethods.Head, + HttpMethods.Options, + HttpMethods.Patch, + HttpMethods.Post, + HttpMethods.Put, + HttpMethods.Trace + }; + + // Prefixes to test + var crLfPrefixes = new string[] { + "\r", + "\n", + "\r\r\r\r\r", + "\r\n", + "\n\r" + }; + + foreach (var method in methods) + { + foreach (var prefix in crLfPrefixes) + { + yield return new object[] { prefix, method }; + } + } + } + + [Theory] + [MemberData(nameof(GetCrLfAndMethodCombinations))] + public void LeadingCrLfAreAllowed(string startOfRequestLine, string httpMethod) + { + var rawTarget = "http://localhost/path1?q=123&w=xyzw"; + Http1Connection.Reset(); + // RawTarget, Path, QueryString are null after reset + Assert.Null(Http1Connection.RawTarget); + Assert.Null(Http1Connection.Path); + Assert.Null(Http1Connection.QueryString); + + var ros = new ReadOnlySequence(Encoding.ASCII.GetBytes($"{startOfRequestLine}{httpMethod} {rawTarget} HTTP/1.1\r\n")); + var reader = new SequenceReader(ros); + Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader)); + } + public StartLineTests() { MemoryPool = PinnedBlockMemoryPoolFactory.Create(); From bc1c2ada2265a0f26f2400a428f07cb739af9407 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 5 Apr 2022 16:00:10 -0700 Subject: [PATCH 07/21] Unquarantine test (#40888) --- src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs index e0b0ab828131..e6acee1a6d43 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs @@ -468,7 +468,6 @@ public async Task RemoveInProcessReference_FailedToFindRequestHandler() [ConditionalFact] [RequiresNewHandler] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/40036")] public async Task StartupTimeoutIsApplied() { // From what we can tell, this failure is due to ungraceful shutdown. From 7363a6c6a188f01d013de33fcc7ff9fdc159c028 Mon Sep 17 00:00:00 2001 From: Nolan Glore Date: Tue, 5 Apr 2022 16:02:07 -0700 Subject: [PATCH 08/21] Fix DelegationRule to work after receiver restarts (#40967) --- src/Servers/HttpSys/HttpSysServer.slnf | 2 + src/Servers/HttpSys/src/DelegationRule.cs | 8 +-- .../HttpSys/src/NativeInterop/RequestQueue.cs | 27 +++---- .../HttpSys/src/NativeInterop/UrlGroup.cs | 18 ----- .../src/RequestProcessing/RequestContext.cs | 5 +- .../src/ServerDelegationPropertyFeature.cs | 13 ++-- .../test/FunctionalTests/DelegateTests.cs | 72 +++++++++++++++++-- 7 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf index 3990e33925cb..c546d5797a4a 100644 --- a/src/Servers/HttpSys/HttpSysServer.slnf +++ b/src/Servers/HttpSys/HttpSysServer.slnf @@ -4,9 +4,11 @@ "projects": [ "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj", "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", + "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", + "src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", diff --git a/src/Servers/HttpSys/src/DelegationRule.cs b/src/Servers/HttpSys/src/DelegationRule.cs index 1f57f8298558..c454952eea9e 100644 --- a/src/Servers/HttpSys/src/DelegationRule.cs +++ b/src/Servers/HttpSys/src/DelegationRule.cs @@ -13,17 +13,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys public class DelegationRule : IDisposable { private readonly ILogger _logger; - private readonly UrlGroup _urlGroup; private readonly UrlGroup _sourceQueueUrlGroup; private bool _disposed; + /// /// The name of the Http.Sys request queue /// public string QueueName { get; } + /// /// The URL of the Http.Sys Url Prefix /// public string UrlPrefix { get; } + internal RequestQueue Queue { get; } internal DelegationRule(UrlGroup sourceQueueUrlGroup, string queueName, string urlPrefix, ILogger logger) @@ -32,8 +34,7 @@ internal DelegationRule(UrlGroup sourceQueueUrlGroup, string queueName, string u _logger = logger; QueueName = queueName; UrlPrefix = urlPrefix; - Queue = new RequestQueue(queueName, UrlPrefix, _logger, receiver: true); - _urlGroup = Queue.UrlGroup; + Queue = new RequestQueue(queueName, _logger); } /// @@ -51,7 +52,6 @@ public void Dispose() _sourceQueueUrlGroup.UnSetDelegationProperty(Queue, throwOnError: false); } catch (ObjectDisposedException) { /* Server may have been shutdown */ } - _urlGroup.Dispose(); Queue.Dispose(); } } diff --git a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs index 8e25a8c21c37..89638a222575 100644 --- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs +++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs @@ -19,25 +19,16 @@ internal class RequestQueue private readonly ILogger _logger; private bool _disposed; - internal RequestQueue(string requestQueueName, string urlPrefix, ILogger logger, bool receiver) - : this(urlGroup: null!, requestQueueName, RequestQueueMode.Attach, logger, receiver) + internal RequestQueue(string requestQueueName, ILogger logger) + : this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver: true) { - try - { - UrlGroup = new UrlGroup(this, UrlPrefix.Create(urlPrefix), logger); - } - catch - { - Dispose(); - throw; - } } internal RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger) : this(urlGroup, requestQueueName, mode, logger, false) { } - private RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver) + private RequestQueue(UrlGroup? urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver) { _mode = mode; UrlGroup = urlGroup; @@ -117,10 +108,15 @@ private RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMo internal SafeHandle Handle { get; } internal ThreadPoolBoundHandle BoundHandle { get; } - internal UrlGroup UrlGroup { get; } + internal UrlGroup? UrlGroup { get; } internal unsafe void AttachToUrlGroup() { + if (UrlGroup == null) + { + throw new NotSupportedException("Can't attach when UrlGroup is null"); + } + Debug.Assert(Created); CheckDisposed(); // Set the association between request queue and url group. After this, requests for registered urls will @@ -138,6 +134,11 @@ internal unsafe void AttachToUrlGroup() internal unsafe void DetachFromUrlGroup() { + if (UrlGroup == null) + { + throw new NotSupportedException("Can't detach when UrlGroup is null"); + } + Debug.Assert(Created); CheckDisposed(); // Break the association between request queue and url group. After this, requests for registered urls diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index d13264889ddd..59a67ca43195 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs @@ -41,24 +41,6 @@ internal unsafe UrlGroup(ServerSession serverSession, ILogger logger) Id = urlGroupId; } - internal unsafe UrlGroup(RequestQueue requestQueue, UrlPrefix url, ILogger logger) - { - _logger = logger; - - ulong urlGroupId = 0; - _created = false; - var statusCode = HttpApi.HttpFindUrlGroupId( - url.FullPrefix, requestQueue.Handle, &urlGroupId); - - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) - { - throw new HttpSysException((int)statusCode); - } - - Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup"); - Id = urlGroupId; - } - internal ulong Id { get; private set; } internal unsafe void SetMaxConnections(long maxConnections) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index fabae04d35e5..9c45a262dac8 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -289,10 +289,13 @@ internal unsafe void Delegate(DelegationRule destination) PropertyInfoLength = (uint)System.Text.Encoding.Unicode.GetByteCount(destination.UrlPrefix) }; + // Passing 0 for delegateUrlGroupId allows http.sys to find the right group for the + // URL passed in via the property above. If we passed in the receiver's URL group id + // instead of 0, then delegation would fail if the receiver restarted. statusCode = HttpApi.HttpDelegateRequestEx(source.Handle, destination.Queue.Handle, Request.RequestId, - destination.Queue.UrlGroup.Id, + delegateUrlGroupId: 0, propertyInfoSetSize: 1, &property); } diff --git a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs index 14aee50615c9..a97520a837d7 100644 --- a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs +++ b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs @@ -14,18 +14,23 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal class ServerDelegationPropertyFeature : IServerDelegationFeature { private readonly ILogger _logger; - private readonly RequestQueue _queue; + private readonly UrlGroup _urlGroup; public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger) { - _queue = queue; + if (queue.UrlGroup == null) + { + throw new ArgumentException($"{nameof(queue)}.UrlGroup can't be null"); + } + + _urlGroup = queue.UrlGroup; _logger = logger; } public DelegationRule CreateDelegationRule(string queueName, string uri) { - var rule = new DelegationRule(_queue.UrlGroup, queueName, uri, _logger); - _queue.UrlGroup.SetDelegationProperty(rule.Queue); + var rule = new DelegationRule(_urlGroup, queueName, uri, _logger); + _urlGroup.SetDelegationProperty(rule.Queue); return rule; } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs index ca9dcf3a0736..9e38c7da8b06 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs @@ -1,13 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.IO; using System.Net.Http; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Testing; -using Xunit; namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests { @@ -198,6 +196,72 @@ public async Task UpdateDelegationRuleTest() destination?.Dispose(); } + [ConditionalFact] + [DelegateSupportedCondition(true)] + public async Task DelegateAfterReceiverRestart() + { + var queueName = Guid.NewGuid().ToString(); + using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext => + { + await httpContext.Response.WriteAsync(_expectedResponseString); + }, + options => + { + options.RequestQueueName = queueName; + }); + + DelegationRule destination = default; + using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext => + { + var delegateFeature = httpContext.Features.Get(); + delegateFeature.DelegateRequest(destination); + return Task.CompletedTask; + }); + + var delegationProperty = delegator.Features.Get(); + destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress); + + var responseString = await SendRequestAsync(delegatorAddress); + Assert.Equal(_expectedResponseString, responseString); + + // Stop the receiver + receiver?.Dispose(); + + // Start the receiver again but this time we need to attach to the existing queue. + // Due to https://github.com/dotnet/aspnetcore/issues/40359, we have to manually + // register URL prefixes and attach the server's queue to them. + using var receiverRestarted = (MessagePump)Utilities.CreateHttpServer(out receiverAddress, async httpContext => + { + await httpContext.Response.WriteAsync(_expectedResponseString); + }, + options => + { + options.RequestQueueName = queueName; + options.RequestQueueMode = RequestQueueMode.Attach; + options.UrlPrefixes.Clear(); + options.UrlPrefixes.Add(receiverAddress); + }); + AttachToUrlGroup(receiverRestarted.Listener.RequestQueue); + receiverRestarted.Listener.Options.UrlPrefixes.RegisterAllPrefixes(receiverRestarted.Listener.UrlGroup); + + responseString = await SendRequestAsync(delegatorAddress); + Assert.Equal(_expectedResponseString, responseString); + + destination?.Dispose(); + } + + private unsafe void AttachToUrlGroup(RequestQueue requestQueue) + { + var info = new HttpApiTypes.HTTP_BINDING_INFO(); + info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; + info.RequestQueueHandle = requestQueue.Handle.DangerousGetHandle(); + + var infoptr = new IntPtr(&info); + + requestQueue.UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, + infoptr, (uint)Marshal.SizeOf()); + } + private async Task SendRequestAsync(string uri) { using var client = new HttpClient(); From 40c035ddd937fa5d507ad2726b62f17b59f43831 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:06:46 -0700 Subject: [PATCH 09/21] [release/6.0] (deps): Bump src/submodules/googletest (#41000) Bumps [src/submodules/googletest](https://github.com/google/googletest) from `c9461a9` to `af29db7`. - [Release notes](https://github.com/google/googletest/releases) - [Commits](https://github.com/google/googletest/compare/c9461a9b55ba954df0489bab6420eb297bed846b...af29db7ec28d6df1c7f0f745186884091e602e07) --- updated-dependencies: - dependency-name: src/submodules/googletest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/submodules/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/submodules/googletest b/src/submodules/googletest index c9461a9b55ba..af29db7ec28d 160000 --- a/src/submodules/googletest +++ b/src/submodules/googletest @@ -1 +1 @@ -Subproject commit c9461a9b55ba954df0489bab6420eb297bed846b +Subproject commit af29db7ec28d6df1c7f0f745186884091e602e07 From de41c582fe8c0ed3afb53ebfc59086251c9da554 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:07:24 -0700 Subject: [PATCH 10/21] Update dependencies from https://github.com/dotnet/arcade build 20220328.5 (#41052) Microsoft.DotNet.Build.Tasks.Templating , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 6.0.0-beta.22161.1 -> To Version 6.0.0-beta.22178.5 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 16 ++++++++-------- eng/Versions.props | 4 ++-- eng/common/templates/steps/source-build.yml | 4 ++-- global.json | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f3e02194d096..1be6aca6040b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -280,22 +280,22 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime c24d9a9c91c5d04b7b4de71f1a9f33ac35e09663 - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 diff --git a/eng/Versions.props b/eng/Versions.props index bb2938612641..c88cf62def19 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -131,8 +131,8 @@ 6.0.3 6.0.3 - 6.0.0-beta.22161.1 - 6.0.0-beta.22161.1 + 6.0.0-beta.22178.5 + 6.0.0-beta.22178.5 - <_TarArchiveOutputPath>$(TarArchiveOutputPath) - <_TarArchiveOutputPath - Condition="Exists('$(repoRoot)\.tools\tar.fromGit')">/$(TarArchiveOutputPath.Replace('\','/').Replace(':','')) - - + Outputs="$(ArchiveOutputPath)"> - - - - - - + DestinationFile="$(ArchiveOutputPath)" + Overwrite="true" + Condition="'$(ArchiveExtension)' == '.zip'" /> + + - $(TargetingPackInstallerBaseName)-$(TargetingPackVersion).deb + $(TargetingPackInstallerBaseName)-$(TargetingPackVersion)-$(TargetArchitecture).deb $(TargetDir)$(TargetFileName) $(TargetingPackVersionPrefix) diff --git a/src/Installers/Windows/TargetingPack/TargetingPack.wixproj b/src/Installers/Windows/TargetingPack/TargetingPack.wixproj index 27894df71808..dc20b17662f0 100644 --- a/src/Installers/Windows/TargetingPack/TargetingPack.wixproj +++ b/src/Installers/Windows/TargetingPack/TargetingPack.wixproj @@ -66,7 +66,7 @@ $(InstallersOutputPath) $(TargetingPackVersionPrefix) $(TargetingPackZipVersion)-$(VersionSuffix) - $(TargetingPackHarvestRoot)aspnetcore-targeting-pack-$(TargetingPackZipVersion).zip + $(TargetingPackHarvestRoot)aspnetcore-targeting-pack-$(TargetingPackZipVersion)-$(TargetRuntimeIdentifier).zip From 8ca8c2cd7c96e03b2c41f07573cfa86de9cd6cc9 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 6 Apr 2022 05:43:12 +0000 Subject: [PATCH 13/21] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 12 ++++++++++++ eng/Version.Details.xml | 28 ++++++++++++++-------------- eng/Versions.props | 14 +++++++------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/NuGet.config b/NuGet.config index 76ccfd79eeee..74d2f8b546d3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,9 +4,15 @@ + + + + + + @@ -28,9 +34,15 @@ + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 640bbfa0defb..0c2de08f4a17 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -177,9 +177,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 https://github.com/dotnet/runtime @@ -245,33 +245,33 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 diff --git a/eng/Versions.props b/eng/Versions.props index 72ca8c5f0047..a6cf369374e3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -63,12 +63,12 @@ 6.0.0 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4-servicing.22164.4 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5-servicing.22205.9 6.0.0 6.0.1 6.0.0 @@ -103,7 +103,7 @@ 6.0.0 6.0.0 6.0.0 - 6.0.4-servicing.22164.4 + 6.0.5-servicing.22205.9 6.0.0 6.0.0 6.0.1 From 29e093cf1b89e2b43b4dc752ff52c50cf8ec4996 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Wed, 6 Apr 2022 11:46:12 -0700 Subject: [PATCH 14/21] Backport UseProgramMain template option (#40945) * Add option to project templates to use Program.Main instead of top-level statements (#40886) Fixes #40944 * Update spa templates submodule --- .editorconfig | 6 +- .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 6 + .../.template.config/template.json | 22 ++ .../BlazorServerWeb-CSharp/Program.Main.cs | 169 ++++++++++++++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 4 + .../.template.config/template.json | 25 ++ .../Client/Program.Main.cs | 69 ++++++ .../Server/Program.Main.cs | 125 ++++++++++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 6 + .../.template.config/template.json | 22 ++ .../content/EmptyWeb-CSharp/Program.Main.cs | 14 ++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 8 +- .../.template.config/template.json | 22 ++ .../GrpcService-CSharp/Program.Main.cs | 25 ++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 5 +- .../.template.config/template.json | 22 ++ .../RazorPagesWeb-CSharp/Program.Main.cs | 155 +++++++++++++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 5 +- .../.template.config/template.json | 22 ++ .../content/StarterWeb-CSharp/Program.Main.cs | 159 +++++++++++++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 4 + .../.template.config/template.json | 22 ++ .../content/WebApi-CSharp/Program.Main.cs | 95 ++++++++ .../.template.config/dotnetcli.host.json | 4 + .../.template.config/ide.host.json | 6 + .../.template.config/template.json | 22 ++ .../content/Worker-CSharp/Program.Main.cs | 18 ++ .../Run-AngularProgramMain-Locally.ps1 | 12 + .../scripts/Run-BlazorProgramMain-Locally.ps1 | 13 ++ .../Run-BlazorWasmProgramMain-Locally.ps1 | 13 ++ .../Run-EmptyWebProgramMain-Locally.ps1 | 12 + .../scripts/Run-RazorProgramMain-Locally.ps1 | 9 + .../scripts/Run-ReactProgramMain-Locally.ps1 | 12 + .../Run-StarterwebProgramMain-Locally.ps1 | 12 + .../scripts/Run-WebApiProgamMain-Locally.ps1 | 12 + .../scripts/Run-WorkerProgramMain-Locally.ps1 | 12 + .../scripts/Test-Template.ps1 | 4 +- .../test/BlazorServerTemplateTest.cs | 14 +- .../test/BlazorWasmTemplateTest.cs | 3 + .../test/EmptyWebTemplateTest.cs | 11 +- src/ProjectTemplates/test/GrpcTemplateTest.cs | 9 +- src/ProjectTemplates/test/MvcTemplateTest.cs | 218 ++++++++---------- .../test/RazorPagesTemplateTest.cs | 23 +- .../test/WebApiTemplateTest.cs | 22 +- .../test/WorkerTemplateTest.cs | 9 +- src/submodules/spa-templates | 2 +- 53 files changed, 1365 insertions(+), 147 deletions(-) create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs create mode 100644 src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 create mode 100644 src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 diff --git a/.editorconfig b/.editorconfig index d7ec0b83dec4..8583569cfc7d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -208,7 +208,7 @@ dotnet_diagnostic.IDE0044.severity = warning dotnet_diagnostic.IDE0073.severity = warning file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. -[**/{test,samples,perf}/**.{cs,vb}] +[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,scripts}/**.cs}] # CA1018: Mark attributes with AttributeUsageAttribute dotnet_diagnostic.CA1018.severity = suggestion # CA1507: Use nameof to express symbol names @@ -241,6 +241,10 @@ dotnet_diagnostic.CA1844.severity = suggestion dotnet_diagnostic.CA1845.severity = suggestion # CA1846: Prefer AsSpan over Substring dotnet_diagnostic.CA1846.severity = suggestion +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = suggestion +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = suggestion # CA2008: Do not create tasks without passing a TaskScheduler dotnet_diagnostic.CA2008.severity = suggestion # CA2012: Use ValueTask correctly diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json index 3c34f96f85e3..02abad32e7eb 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json index e891e5f2df97..947fd0caa52b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json @@ -43,6 +43,12 @@ "useHttps": true } ], + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ], "disableHttpsSymbol": "NoHttps", "supportsDocker": true } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json index 254598a9cfe2..4b203fdd52b6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json @@ -34,6 +34,21 @@ "wwwroot/**" ], "modifiers": [ + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, { "condition": "(!IndividualLocalAuth || UseLocalDB)", "exclude": [ @@ -490,6 +505,13 @@ "datatype": "bool", "description": "If specified, skips the automatic restore of the project on create.", "defaultValue": "false" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs new file mode 100644 index 000000000000..92eb45d80a91 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs @@ -0,0 +1,169 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (OrganizationalAuth) +#if (MultiOrgAuth) +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +#endif +using Microsoft.AspNetCore.Authorization; +#endif +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if(MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (IndividualLocalAuth) +using BlazorServerWeb_CSharp.Areas.Identity; +#endif +using BlazorServerWeb_CSharp.Data; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddControllersWithViews() + .AddMicrosoftIdentityUI(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy + options.FallbackPolicy = options.DefaultPolicy; + }); + + #elif (WindowsAuth) + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + + #endif + builder.Services.AddRazorPages(); + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddServerSideBlazor() + .AddMicrosoftIdentityConsentHandler(); + #else + builder.Services.AddServerSideBlazor(); + #endif + #if (IndividualLocalAuth) + builder.Services.AddScoped>(); + #endif + builder.Services.AddSingleton(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + + #endif + + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + app.UseAuthorization(); + + #endif + #if (OrganizationalAuth || IndividualAuth) + app.MapControllers(); + #endif + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json index 93b2c5a3bc03..3d2007dc5868 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json @@ -95,6 +95,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json index 9005a4d171a3..2ed03203a457 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json @@ -47,6 +47,10 @@ "text": "_Progressive Web Application" }, "isVisible": "true" + }, + { + "id": "UseProgramMain", + "isVisible": true } ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json index cd44728a4894..9b261b76d232 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json @@ -94,6 +94,24 @@ "Client/wwwroot/icon-512.png" ] }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Server/Program.Main.cs", + "Client/Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Server/Program.cs", + "Client/Program.cs" + ], + "rename": { + "Server/Program.Main.cs": "Server/Program.cs", + "Client/Program.Main.cs": "Client/Program.cs" + } + }, { "condition": "(!IndividualLocalAuth || UseLocalDB)", "exclude": [ @@ -591,6 +609,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "tags": { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs new file mode 100644 index 000000000000..8b870e5dc9d3 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Components.Web; +#if (!NoAuth && Hosted) +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +#endif +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +#if (Hosted) +using ComponentsWebAssembly_CSharp.Client; +#else +using ComponentsWebAssembly_CSharp; +#endif + +namespace Company.WebApplication1; + +public class Program +{ + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("#app"); + builder.RootComponents.Add("head::after"); + + #if (!Hosted || NoAuth) + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + #else + builder.Services.AddHttpClient("ComponentsWebAssembly_CSharp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler(); + + // Supply HttpClient instances that include access tokens when making requests to the server project + builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("ComponentsWebAssembly_CSharp.ServerAPI")); + #endif + #if(!NoAuth) + + #endif + #if (IndividualLocalAuth) + #if (Hosted) + builder.Services.AddApiAuthorization(); + #else + builder.Services.AddOidcAuthentication(options => + { + #if(MissingAuthority) + // Configure your authentication provider options here. + // For more information, see https://aka.ms/blazor-standalone-auth + #endif + builder.Configuration.Bind("Local", options.ProviderOptions); + }); + #endif + #endif + #if (IndividualB2CAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication); + #if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("https://qualified.domain.name/api.id.uri/api-scope"); + #endif + }); + #endif + #if(OrganizationalAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); + #if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/api-scope"); + #endif + }); + #endif + + await builder.Build().RunAsync(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs new file mode 100644 index 000000000000..31835439cd28 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs @@ -0,0 +1,125 @@ +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth) +using Microsoft.AspNetCore.Authentication; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication.JwtBearer; +#endif +using Microsoft.AspNetCore.ResponseCompression; +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +#endif +#if (IndividualLocalAuth) +using ComponentsWebAssembly_CSharp.Server.Data; +using ComponentsWebAssembly_CSharp.Server.Models; +#endif + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + + builder.Services.AddIdentityServer() + .AddApiAuthorization(); + + builder.Services.AddAuthentication() + .AddIdentityServerJwt(); + #endif + #if (OrganizationalAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + + builder.Services.AddControllersWithViews(); + builder.Services.AddRazorPages(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + #if (IndividualLocalAuth) + app.UseMigrationsEndPoint(); + #endif + app.UseWebAssemblyDebugging(); + } + else + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + #endif + } + + #if (RequiresHttps) + app.UseHttpsRedirection(); + + #endif + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + + #if (IndividualLocalAuth) + app.UseIdentityServer(); + #endif + #if (OrganizationalAuth || IndividualAuth) + app.UseAuthentication(); + #endif + #if (!NoAuth) + app.UseAuthorization(); + + #endif + + app.MapRazorPages(); + app.MapControllers(); + app.MapFallbackToFile("index.html"); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json index d97858472b80..6f4f4fb8936d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json @@ -27,6 +27,10 @@ "NoHttps": { "longName": "no-https", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json index 751a95c5b2ff..f30c4753e2a8 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json @@ -20,5 +20,11 @@ "useHttps": true } ], + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json index 3b7edb411a86..c85130f420f9 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json @@ -29,6 +29,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -156,6 +171,13 @@ "datatype": "bool", "defaultValue": "false", "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth." + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs new file mode 100644 index 000000000000..6a106499828e --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs @@ -0,0 +1,14 @@ +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + var app = builder.Build(); + + app.MapGet("/", () => "Hello World!"); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json index 684bc1e734f3..ded3cbf35f23 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json @@ -14,6 +14,10 @@ "ExcludeLaunchSettings": { "longName": "exclude-launch-settings", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json index 2cb41909519c..5c3a869512fd 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json @@ -2,5 +2,11 @@ "$schema": "http://json.schemastore.org/vs-2017.3.host", "order": 500, "icon": "ide/gRPC.png", - "supportsDocker": true + "supportsDocker": true, + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json index 2fd14ec9251f..fc4288d87384 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json @@ -26,6 +26,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -102,6 +117,13 @@ "fallbackVariableName": "kestrelHttpsPortGenerated" }, "replaces": "5001" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs new file mode 100644 index 000000000000..ec1af1a7e9c6 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs @@ -0,0 +1,25 @@ +using GrpcService_CSharp.Services; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Additional configuration is required to successfully run gRPC on macOS. + // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 + + // Add services to the container. + builder.Services.AddGrpc(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + app.MapGrpcService(); + app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + + app.Run(); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json index 6ce1fbe6eb27..a00ae64f281e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json index f176b8df2a9c..0dc542e09b82 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json @@ -45,7 +45,10 @@ } ], "symbolInfo": [ - + { + "id": "UseProgramMain", + "isVisible": true + } ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json index d7226586d37d..989dbb8ab86b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json @@ -94,6 +94,21 @@ "exclude": [ "Data/SqlServer/**" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -406,6 +421,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs new file mode 100644 index 000000000000..3a1a1d68cc06 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs @@ -0,0 +1,155 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Identity; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (IndividualLocalAuth) +using Company.WebApplication1.Data; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth || MultiOrgAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth) + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #elif (IndividualB2CAuth) + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #elif (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages(); + #else + builder.Services.AddRazorPages(); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + #endif + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapRazorPages(); + #if (IndividualB2CAuth || OrganizationalAuth) + app.MapControllers(); + #endif + + app.Run(); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json index 3c34f96f85e3..02abad32e7eb 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json index 12bb6ec5db17..995a75bea9a0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json @@ -45,7 +45,10 @@ } ], "symbolInfo": [ - + { + "id": "UseProgramMain", + "isVisible": true + } ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json index b67fe4f719ef..558a4e818d35 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json @@ -90,6 +90,21 @@ "exclude": [ "Data/SqlServer/**" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -402,6 +417,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs new file mode 100644 index 000000000000..5fda9092d06a --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs @@ -0,0 +1,159 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Identity; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (IndividualLocalAuth) +using Company.WebApplication1.Data; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth || MultiOrgAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth) + + builder.Services.AddControllersWithViews(options => + { + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + options.Filters.Add(new AuthorizeFilter(policy)); + }); + #else + builder.Services.AddControllersWithViews(); + #endif + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #endif + #if (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages(); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Home/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + #endif + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + #if (OrganizationalAuth || IndividualAuth) + app.MapRazorPages(); + #endif + + app.Run(); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json index 9b97a6182066..79217f16803f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "DisableOpenAPI": { "longName": "no-openapi", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json index c8fdd64b6acd..19355a4eecac 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json @@ -55,6 +55,10 @@ "invertBoolean": true, "isVisible": true, "defaultValue": true + }, + { + "id": "UseProgramMain", + "isVisible": true } ], "disableHttpsSymbol": "NoHttps" diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index ee1f9c886b38..5c9b53aedcd1 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -37,6 +37,21 @@ "Properties/launchSettings.json" ] }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain && !UseMinimalAPIs)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, { "condition": "(UseMinimalAPIs)", "exclude": [ @@ -364,6 +379,13 @@ "UseControllers": { "type": "computed", "value": "(!UseMinimalAPIs)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs new file mode 100644 index 000000000000..a0c9ad67e817 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs @@ -0,0 +1,95 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (OrganizationalAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + + builder.Services.AddControllers(); + #if (EnableOpenAPI) + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + #endif + #if (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (EnableOpenAPI) + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + #endif + #if (RequiresHttps) + + app.UseHttpsRedirection(); + #endif + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json index b1cf98e39bb8..36493a3a4ac7 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json @@ -11,6 +11,10 @@ "ExcludeLaunchSettings": { "longName": "exclude-launch-settings", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json index 13a025d034fa..59f260a583df 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json @@ -3,4 +3,10 @@ "order": 300, "icon": "ide/Worker.png", "supportsDocker": true, + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json index 19358bf86a30..d2789afed3ff 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json @@ -30,6 +30,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -67,6 +82,13 @@ "datatype": "bool", "description": "If specified, skips the automatic restore of the project on create.", "defaultValue": "false" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs new file mode 100644 index 000000000000..d69747f38d51 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs @@ -0,0 +1,18 @@ +using Company.Application1; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddHostedService(); + }) + .Build(); + + host.Run(); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 new file mode 100644 index 000000000000..93127bb08be0 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "angular" "angular --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 new file mode 100644 index 000000000000..3d9fdd64a70d --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 @@ -0,0 +1,13 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +# This script packages, installs and creates a template to help with rapid iteration in the templating area. +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "blazorserver" "blazorserver --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 new file mode 100644 index 000000000000..7c8755a8bba3 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 @@ -0,0 +1,13 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +# This script packages, installs and creates a template to help with rapid iteration in the templating area. +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "blazorwasm" "blazorwasm --use-program-main --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 new file mode 100644 index 000000000000..7453063baf21 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "web" "web --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 new file mode 100644 index 000000000000..4224cf985dd2 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 @@ -0,0 +1,9 @@ +#!/usr/bin/env powershell +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "webapp" "webapp -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 new file mode 100644 index 000000000000..df61a5a11740 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "react" "react --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 new file mode 100644 index 000000000000..076106d3e861 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "mvc" "mvc -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 new file mode 100644 index 000000000000..41f794b7eaaf --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "webapi" "webapi --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 new file mode 100644 index 000000000000..9e0aa3d4607b --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "worker" "worker --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Test-Template.ps1 b/src/ProjectTemplates/scripts/Test-Template.ps1 index 349174ad3d06..05cf0ef7128b 100644 --- a/src/ProjectTemplates/scripts/Test-Template.ps1 +++ b/src/ProjectTemplates/scripts/Test-Template.ps1 @@ -64,7 +64,9 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isBlazorWa if ($isBlazorWasmHosted) { Push-Location Server } - dotnet.exe ef migrations add mvc + if ($templateArgs -match '-au') { + dotnet.exe ef migrations add mvc + } dotnet.exe publish --configuration Release Set-Location .\bin\Release\net6.0\publish if ($isBlazorWasm -eq $false) { diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index 432c85043bd7..104f2304020d 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -26,18 +26,26 @@ public BlazorServerTemplateTest(ProjectFactoryFixture projectFactory) [Fact] public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync("blazorservernoauth"); + [Fact] + public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync("blazorservernoauth", args: new [] { "--use-program-main" }); + [Theory] - [InlineData(true)] - [InlineData(false)] + [InlineData(true, null)] + [InlineData(true, new string[] { "--use-program-main" })] + [InlineData(false, null)] + [InlineData(false, new string[] { "--use-program-main" })] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] - public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "")); + public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB, string[] args) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "", args: args)); [Theory] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main", "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index 97a580dddd8b..dae6148d213a 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -37,6 +37,9 @@ public async Task BlazorWasmStandaloneTemplateCanCreateBuildPublish() [Fact] public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--hosted" }, serverProject: true); + [Fact] + public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--use-program-main", "--hosted" }, serverProject: true); + [Fact] public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { "--pwa" }); diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index e90ccbf5b94c..7468a587b41e 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -38,17 +38,24 @@ public async Task EmptyWebTemplateCSharp() await EmtpyTemplateCore(languageOverride: null); } + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task EmptyWebTemplateProgramMainCSharp() + { + await EmtpyTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + } + [Fact] public async Task EmptyWebTemplateFSharp() { await EmtpyTemplateCore("F#"); } - private async Task EmtpyTemplateCore(string languageOverride) + private async Task EmtpyTemplateCore(string languageOverride, string[] args = null) { var project = await ProjectFactory.GetOrCreateProject("empty" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); - var createResult = await project.RunDotNetNewAsync("web", language: languageOverride); + var createResult = await project.RunDotNetNewAsync("web", args: args, language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs index 585ac0bfb457..d26d0f8623a6 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -34,14 +34,17 @@ public ITestOutputHelper Output } } - [ConditionalFact] + [ConditionalTheory] [SkipOnHelix("Not supported queues", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [SkipOnAlpine("https://github.com/grpc/grpc/issues/18338")] - public async Task GrpcTemplate() + [InlineData(true)] + [InlineData(false)] + public async Task GrpcTemplate(bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("grpc", Output); - var createResult = await project.RunDotNetNewAsync("grpc"); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("grpc", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(); diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 47045d6972aa..a5d6bec38066 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -43,11 +43,15 @@ public ITestOutputHelper Output [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task MvcTemplate_NoAuthCSharp() => await MvcTemplateCore(languageOverride: null); - private async Task MvcTemplateCore(string languageOverride) + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task MvcTemplate_ProgramMainNoAuthCSharp() => await MvcTemplateCore(languageOverride: null, new [] { "--use-program-main" }); + + private async Task MvcTemplateCore(string languageOverride, string[] args = null) { var project = await ProjectFactory.GetOrCreateProject("mvcnoauth" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); - var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride); + var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectExtension = languageOverride == "F#" ? "fsproj" : "csproj"; @@ -75,10 +79,10 @@ private async Task MvcTemplateCore(string languageOverride) Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); IEnumerable menuLinks = new List { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyFullUrl - }; + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyFullUrl + }; var footerLinks = new string[] { PageUrls.PrivacyFullUrl }; @@ -116,14 +120,17 @@ private async Task MvcTemplateCore(string languageOverride) } [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] + [InlineData(true, false)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task MvcTemplate_IndividualAuth(bool useLocalDB) + public async Task MvcTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output); - var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectFileContents = project.ReadFile($"{project.ProjectName}.csproj"); @@ -148,72 +155,72 @@ public async Task MvcTemplate_IndividualAuth(bool useLocalDB) // Note: if any links are updated here, RazorPagesTemplateTest.cs should be updated as well var pages = new List { - new Page - { - Url = PageUrls.ForgotPassword, - Links = new string [] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.HomeUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.DocsUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.PrivacyFullUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.LoginUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.ForgotPassword, - PageUrls.RegisterUrl, - PageUrls.ResendEmailConfirmation, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl } - }, - new Page - { - Url = PageUrls.RegisterUrl, - Links = new string [] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl + new Page + { + Url = PageUrls.ForgotPassword, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.HomeUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.DocsUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.PrivacyFullUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.LoginUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.ForgotPassword, + PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl } + }, + new Page + { + Url = PageUrls.RegisterUrl, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl + } } - } - }; + }; using (var aspNetProcess = project.StartBuiltProjectAsync()) { @@ -234,67 +241,44 @@ public async Task MvcTemplate_IndividualAuth(bool useLocalDB) } } - [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/25103")] - [SkipOnHelix("cert failure", Queues = "All.OSX")] + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] // Running these requires the rid-specific runtime pack to be available which is not consistent in all our platform builds. + [SkipOnHelix("cert failure", Queues = "All.OSX;" + HelixConstants.Windows10Arm64)] public async Task MvcTemplate_SingleFileExe() { // This test verifies publishing an MVC app as a single file exe works. We'll limit testing // this to a few operating systems to make our lives easier. - string runtimeIdentifer; - if (OperatingSystem.IsWindows()) - { - runtimeIdentifer = "win-x64"; - } - else if (OperatingSystem.IsLinux()) - { - runtimeIdentifer = "linux-x64"; - } - else - { - return; - } - + var runtimeIdentifer = "win-x64"; var project = await ProjectFactory.GetOrCreateProject("mvcsinglefileexe", Output); project.RuntimeIdentifier = runtimeIdentifer; - var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: true); + var createResult = await project.RunDotNetNewAsync("mvc"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(additionalArgs: $"/p:PublishSingleFile=true -r {runtimeIdentifer}", noRestore: false); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - var pages = new[] + var menuLinks = new[] + { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyFullUrl + }; + + var footerLinks = new[] { PageUrls.PrivacyFullUrl }; + + var pages = new List { new Page { - // Verify a view from the app works Url = PageUrls.HomeUrl, - Links = new [] - { - PageUrls.HomeUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.DocsUrl, - PageUrls.PrivacyUrl - } + Links = menuLinks.Append(PageUrls.DocsUrl).Concat(footerLinks), }, new Page { - // Verify a view from a RCL (in this case IdentityUI) works - Url = PageUrls.RegisterUrl, - Links = new [] - { - PageUrls.HomeUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl - } - }, + Url = PageUrls.PrivacyFullUrl, + Links = menuLinks.Concat(footerLinks), + } }; using var aspNetProcess = project.StartPublishedProjectAsync(usePublishedAppHost: true); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 1af0a68366ff..3412233f5dd5 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -34,13 +34,16 @@ public ITestOutputHelper Output } } - [ConditionalFact] + [ConditionalTheory] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_NoAuth() + [InlineData(true)] + [InlineData(false)] + public async Task RazorPagesTemplate_NoAuth(bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output); - var createResult = await project.RunDotNetNewAsync("razor"); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("razor", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("razor", project, createResult)); var projectFileContents = ReadFile(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); @@ -104,14 +107,17 @@ public async Task RazorPagesTemplate_NoAuth() } [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) + public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output); - var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectFileContents = ReadFile(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); @@ -226,12 +232,15 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); [ConditionalTheory] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes_WithSingleOrg(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); private async Task BuildAndPublishRazorPagesTemplate(string auth, string[] args) diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index ddc6e185320e..f727abcc92c0 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -35,10 +35,15 @@ public ITestOutputHelper Output [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] + [InlineData("IndividualB2C", new string[] { "--use-program-main" })] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] + [InlineData("SingleOrg", new string[] { "--use-program-main" })] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task WebApiTemplateCSharp_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); [Fact] @@ -50,11 +55,18 @@ public ITestOutputHelper Output [ConditionalFact] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task WebApiTemplateCSharp_WithoutOpenAPI() + public Task WebApiTemplateProgramMainCSharp() => WebApiTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task WebApiTemplateCSharp_WithoutOpenAPI(bool useProgramMain) { var project = await FactoryFixture.GetOrCreateProject("webapinoopenapi", Output); - var createResult = await project.RunDotNetNewAsync("webapi", args: new[] { "--no-openapi" }); + var args = useProgramMain ? new[] { "--use-program-main --no-openapi" } : new[] { "--no-openapi" }; + var createResult = await project.RunDotNetNewAsync("webapi", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var buildResult = await project.RunDotNetBuildAsync(); @@ -68,7 +80,7 @@ public async Task WebApiTemplateCSharp_WithoutOpenAPI() await aspNetProcess.AssertNotFound("swagger"); } - private async Task PublishAndBuildWebApiTemplate(string languageOverride, string auth, string[] args) + private async Task PublishAndBuildWebApiTemplate(string languageOverride, string auth, string[] args = null) { var project = await FactoryFixture.GetOrCreateProject("webapi" + (languageOverride == "F#" ? "fsharp" : "csharp") + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); @@ -94,9 +106,9 @@ private async Task PublishAndBuildWebApiTemplate(string languageOverrid return project; } - private async Task WebApiTemplateCore(string languageOverride) + private async Task WebApiTemplateCore(string languageOverride, string[] args = null) { - var project = await PublishAndBuildWebApiTemplate(languageOverride, null, null); + var project = await PublishAndBuildWebApiTemplate(languageOverride, null, args); // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 if (languageOverride != null) diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index 80e67c18738e..ba82b5073c0e 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -32,16 +32,17 @@ public ITestOutputHelper Output [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux, SkipReason = "https://github.com/dotnet/sdk/issues/12831")] - [InlineData("C#")] - [InlineData("F#")] + [InlineData("C#", null)] + [InlineData("C#", new string[] { "--use-program-main" })] + [InlineData("F#", null)] [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25404")] - public async Task WorkerTemplateAsync(string language) + public async Task WorkerTemplateAsync(string language, string[] args) { var project = await ProjectFactory.GetOrCreateProject( $"worker-{ language.ToLowerInvariant()[0] }sharp", Output); - var createResult = await project.RunDotNetNewAsync("worker", language: language); + var createResult = await project.RunDotNetNewAsync("worker", language: language, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(); diff --git a/src/submodules/spa-templates b/src/submodules/spa-templates index 3d16ec44cf4b..08a4e743ccec 160000 --- a/src/submodules/spa-templates +++ b/src/submodules/spa-templates @@ -1 +1 @@ -Subproject commit 3d16ec44cf4bc171531cefb0a81ae72b8bf0c478 +Subproject commit 08a4e743ccec97c24aec8b7a719655f08e827d8e From 1e1c891386335c31f25f54b378976407749af928 Mon Sep 17 00:00:00 2001 From: "Chris Ross (ASP.NET)" Date: Wed, 6 Apr 2022 21:55:49 +0000 Subject: [PATCH 15/21] Merged PR 21247: Fix partial chunked cookies 70242 # Fix partial chunked cookies MSRC # 70242: Fix exceptions and allocations when the cookie chunk count is not accurate ## Description Browsers have limits on how long cookies can be, as low as 4kb. It's common for TempData and CookieAuth to get above that limit, so cookies are split into chunks with the following format: MyCookie=chunks-3 MyCookieC1=(Base64EncodedData) MyCookieC2=(Base64EncodedData) MyCookieC3=(Base64EncodedData) Fixes MSRC # 70242 ## Customer Impact A malicious client could send `MyCookie=chunks-2147483647` without the actual cookie chunks and cause large allocations, exceptions, and excess CPU utilization on the server when it tried to read or delete that many chunks. This flaw comes from the original implementation in Microsoft.Owin, but is much worse in AspNetCore when adopted by TempData due to it automatically calling Delete if reading the cookie fails. I'll backport this to 5.0, 3.1, 2.1, and Microsoft.Owin once reviewed. ## Regression? - [ ] Yes - [x] No ## Risk - [ ] High - [ ] Medium - [x] Low Easy to reproduce and test. ## Verification - [x] Manual (required) - [x] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A --- .../CookiePolicy/test/CookieChunkingTests.cs | 35 ++++++++++++++++++- .../ChunkingCookieManager.cs | 17 ++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Security/CookiePolicy/test/CookieChunkingTests.cs b/src/Security/CookiePolicy/test/CookieChunkingTests.cs index 735d7e23d554..5c958fe0abcd 100644 --- a/src/Security/CookiePolicy/test/CookieChunkingTests.cs +++ b/src/Security/CookiePolicy/test/CookieChunkingTests.cs @@ -129,7 +129,7 @@ public void GetLargeChunkedCookieWithMissingChunk_ThrowingDisabled_NotReassemble public void DeleteChunkedCookieWithOptions_AllDeleted() { HttpContext context = new DefaultHttpContext(); - context.Request.Headers.Append("Cookie", "TestCookie=chunks-7"); + context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2;TestCookieC3=3;TestCookieC4=4;TestCookieC5=5;TestCookieC6=6;TestCookieC7=7"); new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true }); var cookies = context.Response.Headers["Set-Cookie"]; @@ -147,7 +147,40 @@ public void DeleteChunkedCookieWithOptions_AllDeleted() }, cookies); } + [Fact] + public void DeleteChunkedCookieWithMissingRequestCookies_OnlyPresentCookiesDeleted() + { + HttpContext context = new DefaultHttpContext(); + context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2"); + + new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true }); + var cookies = context.Response.Headers["Set-Cookie"]; + Assert.Equal(3, cookies.Count); + Assert.Equal(new[] + { + "TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + "TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + "TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + }, cookies); + } + [Fact] + public void DeleteChunkedCookieWithMissingRequestCookies_StopsAtMissingChunk() + { + HttpContext context = new DefaultHttpContext(); + // C3 is missing so we don't try to delete C4 either. + context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2;TestCookieC4=4"); + + new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true }); + var cookies = context.Response.Headers["Set-Cookie"]; + Assert.Equal(3, cookies.Count); + Assert.Equal(new[] + { + "TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + "TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + "TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure", + }, cookies); + } [Fact] public void DeleteChunkedCookieWithOptionsAndResponseCookies_AllDeleted() diff --git a/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs b/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs index 30ec3c66ed63..9a66071ee5d4 100644 --- a/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs +++ b/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs @@ -103,7 +103,7 @@ private static int ParseChunksCount(string? value) var chunksCount = ParseChunksCount(value); if (chunksCount > 0) { - var chunks = new string[chunksCount]; + var chunks = new List(10); // chunksCount may be wrong, don't trust it. for (var chunkId = 1; chunkId <= chunksCount; chunkId++) { var chunk = requestCookies[key + ChunkKeySuffix + chunkId.ToString(CultureInfo.InvariantCulture)]; @@ -128,7 +128,7 @@ private static int ParseChunksCount(string? value) return value; } - chunks[chunkId - 1] = chunk; + chunks.Add(chunk); } return string.Join(string.Empty, chunks); @@ -254,13 +254,22 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options) key + "=" }; - var requestCookie = context.Request.Cookies[key]; - var chunks = ParseChunksCount(requestCookie); + var requestCookies = context.Request.Cookies; + var requestCookie = requestCookies[key]; + long chunks = ParseChunksCount(requestCookie); if (chunks > 0) { for (var i = 1; i <= chunks + 1; i++) { var subkey = key + ChunkKeySuffix + i.ToString(CultureInfo.InvariantCulture); + + // Only delete cookies we received. We received the chunk count cookie so we should have received the others too. + if (string.IsNullOrEmpty(requestCookies[subkey])) + { + chunks = i - 1; + break; + } + keys.Add(subkey + "="); } } From 108c22182e1ada7052e969add7d2d128f786c37b Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 8 Apr 2022 01:40:24 +0000 Subject: [PATCH 16/21] Merged PR 21240: [6.0] MSRC 70023 - ASP.Net FormFeature.cs - DenialOfService # ASP.Net FormFeature.cs - DenialOfService When parsing multi-part form data with FormFeature.cs, we do not honor ValueCountLimit when the content disposition is of an unknown type. Therefore an attacker could send multi-part form data where very part has invalid content disposition, and make us read indefinitely. ## Description When parsing multi-part form data with FormFeature.cs, we do not honor ValueCountLimit when the content disposition is of an unknown type. Therefore an attacker could send multi-part form data where very part has invalid content disposition, and make us read indefinitely. ## Customer Impact Prevents a potential Denial-of-service attack. ## Regression? - [ ] Yes - [x] No ## Risk - [ ] High - [x] Medium - [ ] Low We could have missed another potential version of this vulnerability ## Verification - [x] Manual (required) - [x] Automated Added a test, plus confirmed with a local repro that the pre-existing slowdown goes away after the change. ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A ---- ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props --- src/Http/Http/src/Features/FormFeature.cs | 7 ++++- .../Http/test/Features/FormFeatureTests.cs | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Http/Http/src/Features/FormFeature.cs b/src/Http/Http/src/Features/FormFeature.cs index e758b1b0354c..173904e8c194 100644 --- a/src/Http/Http/src/Features/FormFeature.cs +++ b/src/Http/Http/src/Features/FormFeature.cs @@ -184,6 +184,7 @@ private async Task InnerReadFormAsync(CancellationToken cancell else if (HasMultipartFormContentType(contentType)) { var formAccumulator = new KeyValueAccumulator(); + var nonFormOrFileContentDispositionCount = 0; var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit); var multipartReader = new MultipartReader(boundary, _request.Body) @@ -259,7 +260,11 @@ private async Task InnerReadFormAsync(CancellationToken cancell } else { - System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); + if (nonFormOrFileContentDispositionCount++ >= _options.ValueCountLimit) + { + throw new InvalidDataException($"Unrecognized Content-Disposition. Form value count limit {_options.ValueCountLimit} exceeded."); + + } } section = await multipartReader.ReadNextSectionAsync(cancellationToken); diff --git a/src/Http/Http/test/Features/FormFeatureTests.cs b/src/Http/Http/test/Features/FormFeatureTests.cs index 9426ce6dd1b2..67873e223c4d 100644 --- a/src/Http/Http/test/Features/FormFeatureTests.cs +++ b/src/Http/Http/test/Features/FormFeatureTests.cs @@ -165,6 +165,12 @@ private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature InvalidContentDispositionValue + "\r\n" + "\r\n" + +"Foo\r\n"; + + private const string MultipartFormFileNonFormOrFileContentDispositionValue = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + +"Content-Disposition:x" + +"\r\n" + +"\r\n" + "Foo\r\n"; private const string MultipartFormWithField = @@ -468,6 +474,30 @@ public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest Assert.Equal("Form value count limit 2 exceeded.", exception.Message); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ReadFormAsync_NonFormOrFieldContentDisposition_ValueCountLimitExceeded_Throw(bool bufferRequest) + { + var formContent = new List(); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd)); + + var context = new DefaultHttpContext(); + var responseFeature = new FakeResponseFeature(); + context.Features.Set(responseFeature); + context.Request.ContentType = MultipartContentType; + context.Request.Body = new NonSeekableReadStream(formContent.ToArray()); + + IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 }); + context.Features.Set(formFeature); + + var exception = await Assert.ThrowsAsync(() => context.Request.ReadFormAsync()); + Assert.Equal("Unrecognized Content-Disposition. Form value count limit 2 exceeded.", exception.Message); + } + [Theory] [InlineData(true)] [InlineData(false)] From e71384f9b8d0f950d88adbdcc49d85f54b949107 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:53:04 -0700 Subject: [PATCH 17/21] Update dependencies from https://github.com/dotnet/arcade build 20220406.7 (#41090) Microsoft.DotNet.Build.Tasks.Templating , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 6.0.0-beta.22178.5 -> To Version 6.0.0-beta.22206.7 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 16 ++++++++-------- eng/Versions.props | 4 ++-- global.json | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1be6aca6040b..4e4a2e201fa0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -280,22 +280,22 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime c24d9a9c91c5d04b7b4de71f1a9f33ac35e09663 - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 diff --git a/eng/Versions.props b/eng/Versions.props index c88cf62def19..5fc7bdc4148a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -131,8 +131,8 @@ 6.0.3 6.0.3 - 6.0.0-beta.22178.5 - 6.0.0-beta.22178.5 + 6.0.0-beta.22206.7 + 6.0.0-beta.22206.7 - 6.0.0-beta.22206.7 - 6.0.0-beta.22206.7 + 6.0.0-beta.22212.5 + 6.0.0-beta.22212.5 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - - + + + @@ -34,120 +34,120 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 @@ -155,114 +155,114 @@ - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - - - + + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - + + @@ -270,7 +270,7 @@ - 6.0.3 + 6.0.4 @@ -278,50 +278,50 @@ - 6.0.3 + 6.0.4 - + - + - + - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - - + + @@ -331,8 +331,8 @@ - - + + @@ -340,8 +340,8 @@ - - + + @@ -352,58 +352,58 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 @@ -411,71 +411,71 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - + - + - + - 6.0.3 + 6.0.4 - - + + - + - - + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 @@ -491,195 +491,195 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - - + + - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - - + + - - + + - - + + - 6.0.3 + 6.0.4 - - + + - - + + - - + + - - + + - 6.0.3 + 6.0.4 - + - + - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - - - - + + + + - 6.0.3 + 6.0.4 @@ -688,69 +688,69 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 @@ -769,7 +769,7 @@ - 6.0.3 + 6.0.4 @@ -788,7 +788,7 @@ - 6.0.3 + 6.0.4 @@ -804,46 +804,46 @@ - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - - - + + + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 @@ -853,7 +853,7 @@ - 6.0.3 + 6.0.4 @@ -862,73 +862,73 @@ - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - + - + - + - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 @@ -957,11 +957,11 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 @@ -979,13 +979,13 @@ - 6.0.3 + 6.0.4 - 6.0.3 + 6.0.4 - + \ No newline at end of file diff --git a/eng/Baseline.xml b/eng/Baseline.xml index 903713249197..e20bb4bb51a8 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -4,111 +4,111 @@ This file contains a list of all the packages and their versions which were rele Update this list when preparing for a new patch. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/Versions.props b/eng/Versions.props index ffd24731dff1..b8bce19da302 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,7 +9,7 @@ 6 0 5 - false + true @@ -212,10 +212,10 @@ 2.1.1 2.2.0 - 3.1.23-servicing-22123-12 + 3.1.24-servicing-22180-6 $(MicrosoftAspNetCoreAzureAppServicesSiteExtension31Version) $(MicrosoftAspNetCoreAzureAppServicesSiteExtension31Version) - 5.0.15-servicing-22116-16 + 5.0.16-servicing-22167-6 $(MicrosoftAspNetCoreAzureAppServicesSiteExtension50Version) $(MicrosoftAspNetCoreAzureAppServicesSiteExtension50Version) diff --git a/eng/helix/helix.proj b/eng/helix/helix.proj index f1f7e4a3d42e..b9b16f9f858f 100644 --- a/eng/helix/helix.proj +++ b/eng/helix/helix.proj @@ -10,7 +10,7 @@ not HelixCorrelationPayload. --> - false + true diff --git a/global.json b/global.json index 9dd54a00ab2a..30a17d2cce7a 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ "$(MicrosoftNETCoreBrowserDebugHostTransportVersion)" ], "aspnetcore/x64": [ - "3.1.23" + "3.1.24" ] }, "Git": "2.22.0", From dd34abdadac5668452e0496cfadb6cf985ec3d3f Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Fri, 15 Apr 2022 00:21:07 +0000 Subject: [PATCH 20/21] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-efcore dnceng/internal/dotnet-runtime - Updated helix.proj to IsPublicRuntime=false --- NuGet.config | 20 +++---------- eng/Version.Details.xml | 64 ++++++++++++++++++++--------------------- eng/Versions.props | 26 ++++++++--------- eng/helix/helix.proj | 2 +- 4 files changed, 50 insertions(+), 62 deletions(-) diff --git a/NuGet.config b/NuGet.config index 74d2f8b546d3..f2c2ca6dd386 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,16 +4,10 @@ - - - - + - - - - + @@ -33,16 +27,10 @@ - - - - + - - - - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c43656f4f296..83ba53dc8a68 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,37 +9,37 @@ --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - aca50aef8604cc23910d18edce820e0fa7c61910 + 4fb38f68c827f1264dd4bad0799ce4db26cca470 https://github.com/dotnet/runtime @@ -177,9 +177,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://github.com/dotnet/runtime @@ -193,9 +193,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 839cdfb0ecca5e0be3dbccd926e7651ef50fdf10 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 839cdfb0ecca5e0be3dbccd926e7651ef50fdf10 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://github.com/dotnet/runtime @@ -217,9 +217,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - - https://github.com/dotnet/runtime - 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://github.com/dotnet/runtime @@ -233,9 +233,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - be98e88c760526452df94ef452fff4602fb5bded + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://github.com/dotnet/runtime @@ -247,15 +247,15 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - d66e32ff6f54236c5bea85ca4e0d99f0fc7293e7 + 70ae3df4a6f3c92fb6b315afc405edd10ff38579 diff --git a/eng/Versions.props b/eng/Versions.props index e0556d31d601..2ba62764d9b6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -68,7 +68,7 @@ 6.0.5 6.0.5 6.0.5 - 6.0.5-servicing.22205.9 + 6.0.5-servicing.22213.9 6.0.0 6.0.1 6.0.0 @@ -103,33 +103,33 @@ 6.0.0 6.0.0 6.0.0 - 6.0.5-servicing.22205.9 + 6.0.5-servicing.22213.9 6.0.0 6.0.0 6.0.1 - 6.0.2 + 6.0.3 6.0.0 6.0.1 6.0.1 6.0.0 6.0.0 - 6.0.0 + 6.0.1 6.0.0 6.0.0 6.0.0 - 6.0.3 + 6.0.4 6.0.0 6.0.3 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 - 6.0.4 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 + 6.0.5 6.0.0-beta.22212.5 6.0.0-beta.22212.5 diff --git a/eng/helix/helix.proj b/eng/helix/helix.proj index b9b16f9f858f..f1f7e4a3d42e 100644 --- a/eng/helix/helix.proj +++ b/eng/helix/helix.proj @@ -10,7 +10,7 @@ not HelixCorrelationPayload. --> - true + false From e5f183b656a0e8bc087108130a5a9b54ae94494e Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Mon, 18 Apr 2022 11:47:19 +0000 Subject: [PATCH 21/21] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-efcore --- NuGet.config | 4 ++-- eng/Version.Details.xml | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/NuGet.config b/NuGet.config index f2c2ca6dd386..06e49041de2f 100644 --- a/NuGet.config +++ b/NuGet.config @@ -7,7 +7,7 @@ - + @@ -27,7 +27,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 83ba53dc8a68..67ad201ccdf6 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -11,35 +11,35 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - 4fb38f68c827f1264dd4bad0799ce4db26cca470 + 9b03633bb18b617088b32260065ee385bf9c4491 https://github.com/dotnet/runtime