public void ProjectSnapshot_CachesTagHelperTask() { // Arrange TagHelperResolver.CompletionSource = new TaskCompletionSource <TagHelperResolutionResult>(); try { var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); var snapshot = new DefaultProjectSnapshot(state); // Act var task1 = snapshot.GetTagHelpersAsync(); var task2 = snapshot.GetTagHelpersAsync(); // Assert Assert.Same(task1, task2); } finally { TagHelperResolver.CompletionSource.SetCanceled(); } }
private async Task <(RazorCodeDocument, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionCoreAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. // // First find the versions that are the inputs: // - The project + computed state // - The imports // - This document // // All of these things are cached, so no work is wasted if we do need to generate the code. var computedStateVersion = await project.State.GetComputedStateVersionAsync(project).ConfigureAwait(false); var documentCollectionVersion = project.State.DocumentCollectionVersion; var imports = await GetImportsAsync(project, document).ConfigureAwait(false); var documentVersion = await document.GetTextVersionAsync().ConfigureAwait(false); // OK now that have the previous output and all of the versions, we can see if anything // has changed that would require regenerating the code. var inputVersion = documentVersion; if (inputVersion.GetNewerVersion(computedStateVersion) == computedStateVersion) { inputVersion = computedStateVersion; } if (inputVersion.GetNewerVersion(documentCollectionVersion) == documentCollectionVersion) { inputVersion = documentCollectionVersion; } for (var i = 0; i < imports.Count; i++) { var importVersion = imports[i].Version; if (inputVersion.GetNewerVersion(importVersion) == importVersion) { inputVersion = importVersion; } } RazorCodeDocument olderOutput = null; var olderInputVersion = default(VersionStamp); var olderOutputVersion = default(VersionStamp); if (_older?.TaskUnsafe != null) { (olderOutput, olderInputVersion, olderOutputVersion) = await _older.TaskUnsafe.ConfigureAwait(false); if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion) { // Nothing has changed, we can use the cached result. lock (_lock) { TaskUnsafe = _older.TaskUnsafe; _older = null; return(olderOutput, olderInputVersion, olderOutputVersion); } } } // OK we have to generate the code. var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false); var importSources = new List <RazorSourceDocument>(); foreach (var item in imports) { var sourceDocument = await GetRazorSourceDocumentAsync(item.Document).ConfigureAwait(false); importSources.Add(sourceDocument); } var documentSource = await GetRazorSourceDocumentAsync(document).ConfigureAwait(false); var projectEngine = project.GetProjectEngine(); var codeDocument = projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers); var csharpDocument = codeDocument.GetCSharpDocument(); // OK now we've generated the code. Let's check if the output is actually different. This is // a valuable optimization for our use cases because lots of changes you could make require // us to run code generation, but don't change the result. // // Note that we're talking about the effect on the generated C# code here (not the other artifacts). // This is the reason why we have two versions associated with the output. // // The INPUT version is related the .cshtml files and tag helpers // The OUTPUT version is related to the generated C#. // // Examples: // // A change to a tag helper not used by this document - updates the INPUT version, but not // the OUTPUT version. // // A change in the HTML - updates the INPUT version, but not the OUTPUT version. // // // Razor IDE features should always retrieve the output and party on it regardless. Depending // on the use cases we may or may not need to synchronize the output. var outputVersion = inputVersion; if (olderOutput != null) { if (string.Equals( olderOutput.GetCSharpDocument().GeneratedCode, csharpDocument.GeneratedCode, StringComparison.Ordinal)) { outputVersion = olderOutputVersion; } } if (document is DefaultDocumentSnapshot defaultDocument) { defaultDocument.State.HostDocument.GeneratedCodeContainer.SetOutput( defaultDocument, csharpDocument, inputVersion, outputVersion); } return(codeDocument, inputVersion, outputVersion); }