From f7038892c99dcef95cb5483515429ee71e509e8a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 19 Mar 2021 16:48:19 -0700 Subject: [PATCH] Suppress generating source checksums when configured Hot reload / EnC does not like it when type level attributes are modified. Razor views and Pages include a RazorSourceChecksumAttribute that includes a checksum of all of it's inputs (current cshtml file, and all _ViewImports that contribute to it). It's used used by runtime compilation to tell if the compiled view is current compared to it's inputs. However, it gets in the way with enc since editing a file updates the checksum. We'll disable this feature by default in RazorSourceGenerator, and enable it using an MSBuild switch that's configured by runtime compilation --- ...tCore.Mvc.Razor.RuntimeCompilation.targets | 3 + .../src/AllowedChildTagDescriptor.cs | 2 +- ...efaultRazorCodeGenerationOptionsBuilder.cs | 5 +- .../src/Extensions/MetadataAttributePass.cs | 8 ++- .../src/RazorCodeGenerationOptions.cs | 9 +++ .../src/RazorCodeGenerationOptionsBuilder.cs | 9 +++ .../Extensions/MetadataAttributePassTest.cs | 56 ++++++++++++++++++- 7 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/targets/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets b/src/Mvc/Mvc.Razor.RuntimeCompilation/src/targets/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets index f12a6463918d..41beb9d60429 100644 --- a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/targets/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets +++ b/src/Mvc/Mvc.Razor.RuntimeCompilation/src/targets/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets @@ -10,5 +10,8 @@ $(RazorUpToDateReloadFileTypes.Replace('.cshtml', '')) false + + + true diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/AllowedChildTagDescriptor.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/AllowedChildTagDescriptor.cs index 84a83b3e8c76..3e33f1230018 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/AllowedChildTagDescriptor.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/AllowedChildTagDescriptor.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs index 1422b410ff41..2d16ab6b5ab8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs @@ -52,7 +52,10 @@ public override RazorCodeGenerationOptions Build() SuppressMetadataAttributes, SuppressPrimaryMethodBody, SuppressNullabilityEnforcement, - OmitMinimizedComponentAttributeValues); + OmitMinimizedComponentAttributeValues) + { + SuppressMetadataSourceChecksumAttributes = SuppressMetadataSourceChecksumAttributes, + }; } public override void SetDesignTime(bool designTime) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/MetadataAttributePass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/MetadataAttributePass.cs index 91bc9ec7b4e4..4ae8cad42c51 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/MetadataAttributePass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/MetadataAttributePass.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -92,6 +92,12 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte return; } + if (documentNode.Options.SuppressMetadataSourceChecksumAttributes) + { + // Checksum attributes are turned off (or options not populated), nothing to do. + return; + } + // Checksum of the main source var checksum = codeDocument.Source.GetChecksum(); var checksumAlgorithm = codeDocument.Source.GetChecksumAlgorithm(); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs index b481dfeb07a6..0bd804a21d53 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs @@ -107,6 +107,15 @@ public static RazorCodeGenerationOptions CreateDesignTime(Action public virtual bool SuppressMetadataAttributes { get; protected set; } + /// + /// Gets a value that indicates whether to suppress the RazorSourceChecksumAttribute. + /// + /// Used by default in .NET 6 apps since including a type-level attribute that changes on every + /// edit are treated as rude edits by hot reload. + /// + /// + internal bool SuppressMetadataSourceChecksumAttributes { get; set; } + /// /// Gets or sets a value that determines if an empty body is generated for the primary method. /// diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs index 48d725b6145f..d8896d3050d5 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs @@ -49,6 +49,15 @@ public abstract class RazorCodeGenerationOptionsBuilder /// public virtual bool SuppressMetadataAttributes { get; set; } + /// + /// Gets a value that indicates whether to suppress the RazorSourceChecksumAttribute. + /// + /// Used by default in .NET 6 apps since including a type-level attribute that changes on every + /// edit are treated as rude edits by hot reload. + /// + /// + internal bool SuppressMetadataSourceChecksumAttributes { get; set; } + /// /// Gets or sets a value that determines if an empty body is generated for the primary method. /// diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Extensions/MetadataAttributePassTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Extensions/MetadataAttributePassTest.cs index db50c00268fc..fa911f58d17c 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Extensions/MetadataAttributePassTest.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Extensions/MetadataAttributePassTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Razor.Language.Components; @@ -381,6 +381,60 @@ public void Execute_HasRequiredInfo_AndImport_AddsItemAndSourceChecksum() Assert.Equal("/Foo/Import.cshtml", checksum.Identifier); } + [Fact] + public void Execute_SuppressMetadataSourceChecksumAttributes_DoesNotGenerateSourceChecksumAttributes() + { + // Arrange + var engine = CreateEngine(); + var pass = new MetadataAttributePass() + { + Engine = engine, + }; + + var sourceDocument = TestRazorSourceDocument.Create("", new RazorSourceDocumentProperties(null, "Foo\\Bar.cshtml")); + var import = TestRazorSourceDocument.Create("@using System", new RazorSourceDocumentProperties(null, "Foo\\Import.cshtml")); + var codeDocument = RazorCodeDocument.Create(sourceDocument, new[] { import, }); + + var irDocument = new DocumentIntermediateNode() + { + DocumentKind = "test", + Options = RazorCodeGenerationOptions.Create(o => o.SuppressMetadataSourceChecksumAttributes = true), + }; + var builder = IntermediateNodeBuilder.Create(irDocument); + var @namespace = new NamespaceDeclarationIntermediateNode + { + Annotations = + { + [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace, + }, + Content = "Some.Namespace" + }; + builder.Push(@namespace); + var @class = new ClassDeclarationIntermediateNode + { + Annotations = + { + [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, + }, + ClassName = "Test", + }; + builder.Add(@class); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal(2, irDocument.Children.Count); + + var item = Assert.IsType(irDocument.Children[0]); + Assert.Equal("/Foo/Bar.cshtml", item.Identifier); + Assert.Equal("test", item.Kind); + Assert.Equal("Some.Namespace.Test", item.TypeName); + + var child = Assert.Single(@namespace.Children); + Assert.IsType(child); + } + private static RazorEngine CreateEngine() { return RazorProjectEngine.Create(b =>