From 7f6b05149de4bf024f91eb49f8d5b131c600992a Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Wed, 11 Jul 2018 15:31:37 -0700 Subject: [PATCH] Generate deterministic unique ids for tag helpers --- .../DefaultTagHelperTargetExtension.cs | 12 +++++++++- .../IntegrationTests/BuildIntegrationTest.cs | 23 ++++++++++++++++++- .../DefaultTagHelperTargetExtensionTest.cs | 14 +++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs index ad85ebf807..884ef07df5 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions var uniqueId = (string)context.Items[CodeRenderingContext.SuppressUniqueIds]; if (uniqueId == null) { - uniqueId = Guid.NewGuid().ToString("N"); + uniqueId = GetDeterministicId(context); } context.CodeWriter.WriteStringLiteral(node.TagName) @@ -637,6 +637,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions return builder.ToString(); } + // Internal for testing + internal static string GetDeterministicId(CodeRenderingContext context) + { + // Use the file checksum along with the absolute position in the generated code to create a unique id for each tag helper call site. + var checksum = Checksum.BytesToString(context.SourceDocument.GetChecksum()); + var uniqueId = checksum + context.CodeWriter.Location.AbsoluteIndex; + + return uniqueId; + } + private static string GetPropertyAccessor(DefaultTagHelperPropertyIntermediateNode node) { var propertyAccessor = $"{node.FieldName}.{node.PropertyName}"; diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs index 030f55dd7b..878f5bba42 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; @@ -568,6 +567,28 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.Views.pdb"); } + [Fact] + [InitializeTestProject("SimpleMvc")] + public async Task Build_WithDeterministicFlagSet_OutputsDeterministicViewsAssembly() + { + // Build 1 + var result = await DotnetMSBuild("Build", $"/p:Deterministic=true"); + + Assert.BuildPassed(result); + Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.Views.dll"); + var filePath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath, "SimpleMvc.Views.dll"); + var firstAssemblyBytes = File.ReadAllBytes(filePath); + + // Build 2 + result = await DotnetMSBuild("Rebuild", $"/p:Deterministic=true"); + + Assert.BuildPassed(result); + Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.Views.dll"); + var secondAssemblyBytes = File.ReadAllBytes(filePath); + + Assert.Equal(firstAssemblyBytes, secondAssemblyBytes); + } + private static DependencyContext ReadDependencyContext(string depsFilePath) { var reader = new DependencyContextJsonReader(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs index 6372657d9c..ccf5e5ca7e 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/DefaultTagHelperTargetExtensionTest.cs @@ -1118,6 +1118,20 @@ private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeMana ignoreLineEndingDifferences: true); } + [Fact] + public void GetDeterministicId_IsDeterministic() + { + // Arrange + var context = TestCodeRenderingContext.CreateRuntime(suppressUniqueIds: null); + + // Act + var firstId = DefaultTagHelperTargetExtension.GetDeterministicId(context); + var secondId = DefaultTagHelperTargetExtension.GetDeterministicId(context); + + // Assert + Assert.Equal(firstId, secondId); + } + private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node) { ((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);