diff --git a/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj b/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
index e63049a74f..790364d92e 100644
--- a/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
+++ b/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
@@ -10,14 +10,12 @@
+
-
- Shared\RazorDiagnosticJsonConverter.cs
-
-
- Shared\TagHelperDescriptorJsonConverter.cs
+
+ Serialization\%(FileName)%(Extension)
diff --git a/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
index bfee04164f..f1dcabe640 100644
--- a/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
+++ b/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
@@ -7,7 +7,7 @@ using System.IO;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Performance
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs
new file mode 100644
index 0000000000..51f16b552f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs
@@ -0,0 +1,29 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
+{
+ internal class ExtensionInitializer : RazorExtensionInitializer
+ {
+ public override void Initialize(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (builder.Configuration.ConfigurationName == "MVC-1.0")
+ {
+ RazorExtensions.Register(builder);
+ }
+ else if (builder.Configuration.ConfigurationName == "MVC-1.1")
+ {
+ RazorExtensions.Register(builder);
+ RazorExtensions.RegisterViewComponentTagHelpers(builder);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
index 8bed236c69..8233c62607 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
@@ -2,5 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
+using Microsoft.AspNetCore.Razor.Language;
+
+[assembly: ProvideRazorExtensionInitializer("MVC-1.0", typeof(ExtensionInitializer))]
+[assembly: ProvideRazorExtensionInitializer("MVC-1.1", typeof(ExtensionInitializer))]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
index e8b4f0018c..18bfe99a61 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
@@ -45,6 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
throw new ArgumentNullException(nameof(builder));
}
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
+
builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
}
@@ -88,6 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
EnsureDesignTime(builder);
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
index fe70827712..90cb1eedca 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
@@ -24,10 +24,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
var symbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (symbol != null)
{
- if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal) &&
- symbol.Identity.Version > ViewComponentTypes.AssemblyVersion)
+ if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal))
{
- enabled = true;
+ enabled = symbol.Identity.Version >= ViewComponentTypes.AssemblyVersion;
break;
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
index abf1b447f0..589798fffa 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
@@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
-
+
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
@@ -61,6 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
+
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
builder.AddTargetExtension(new TemplateTargetExtension()
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
index c26779a9c5..9b44634095 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
@@ -10,31 +10,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
internal class ViewComponentTypeVisitor : SymbolVisitor
{
- private static readonly Version SupportedVCTHMvcVersion = new Version(1, 1);
-
private readonly INamedTypeSymbol _viewComponentAttribute;
private readonly INamedTypeSymbol _nonViewComponentAttribute;
private readonly List _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List results)
{
- var enabled = false;
- foreach (var reference in compilation.References)
- {
- var symbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
- if (symbol != null)
- {
- if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal) &&
- symbol.Identity.Version > ViewComponentTypes.AssemblyVersion)
- {
- enabled = true;
- break;
- }
- }
- }
-
- var vcAttribute = enabled ? compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute) : null;
- var nonVCAttribute = enabled ? compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute) : null;
+ var vcAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
+ var nonVCAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute);
return new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, results);
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs
new file mode 100644
index 0000000000..855acedde2
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs
@@ -0,0 +1,37 @@
+// 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;
+using System.Composition;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ [Export(typeof(IFallbackProjectEngineFactory))]
+ internal class FallbackProjectEngineFactory : IFallbackProjectEngineFactory
+ {
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException(nameof(fileSystem));
+ }
+
+ // This is a very basic implementation that will provide reasonable support without crashing.
+ // If the user falls into this situation, ideally they can realize that something is wrong and take
+ // action.
+ //
+ // This has no support for:
+ // - Tag Helpers
+ // - Imports
+ // - Default Imports
+ // - and will have a very limited set of directives
+ return RazorProjectEngine.Create(configuration, fileSystem, configure);
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs
new file mode 100644
index 0000000000..ccdc1b80c1
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs
@@ -0,0 +1,10 @@
+// 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.
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ // Used to create the 'fallback' project engine when we don't have a custom implementation.
+ internal interface IFallbackProjectEngineFactory : IProjectEngineFactory
+ {
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
index 579a42ffcb..d6299717ca 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
for (var i = 0; i< projects.Count; i++)
{
var project = projects[i];
- if (string.Equals(filePath, project.WorkspaceProject.FilePath, StringComparison.OrdinalIgnoreCase))
+ if (FilePathComparer.Instance.Equals(filePath, project.FilePath))
{
return project;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
index a73900fa42..d6cf7bdc7e 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
-
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
index 0388de4671..4defb21947 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
@@ -4,11 +4,20 @@
using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class RazorProjectEngineFactoryService : ILanguageService
{
- public abstract RazorProjectEngine Create(string projectPath, Action configure);
+ public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project);
+
+ public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project);
+
+ public abstract RazorProjectEngine Create(ProjectSnapshot project, Action configure);
+
+ public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure);
+
+ public abstract RazorProjectEngine Create(string directoryPath, Action configure);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
index 21b553cde8..0e8c7b19ef 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
@@ -1,6 +1,7 @@
// 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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
@@ -8,6 +9,8 @@ namespace Microsoft.CodeAnalysis.Razor
{
public sealed class TagHelperResolutionResult
{
+ internal static TagHelperResolutionResult Empty = new TagHelperResolutionResult(Array.Empty(), Array.Empty());
+
public TagHelperResolutionResult(IReadOnlyList descriptors, IReadOnlyList diagnostics)
{
Descriptors = descriptors;
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
index 182273abfd..d17dc90157 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
@@ -1,14 +1,57 @@
// 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;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class TagHelperResolver : ILanguageService
{
- public abstract Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken);
+ public abstract Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default);
+
+ protected virtual async Task GetTagHelpersAsync(ProjectSnapshot project, RazorProjectEngine engine)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (engine == null)
+ {
+ throw new ArgumentNullException(nameof(engine));
+ }
+
+ if (project.WorkspaceProject == null)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ var providers = engine.Engine.Features.OfType().ToArray();
+ if (providers.Length == 0)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ var results = new List();
+ var context = TagHelperDescriptorProviderContext.Create(results);
+
+ var compilation = await project.WorkspaceProject.GetCompilationAsync().ConfigureAwait(false);
+ context.SetCompilation(compilation);
+
+ for (var i = 0; i < providers.Length; i++)
+ {
+ var provider = providers[i];
+ provider.Execute(context);
+ }
+
+ return new TagHelperResolutionResult(results, Array.Empty());
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs
deleted file mode 100644
index 4aca5c8f7e..0000000000
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc.Razor.Extensions;
-using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.CodeAnalysis.Razor;
-
-namespace Microsoft.CodeAnalysis.Remote.Razor
-{
- internal class DefaultTagHelperResolver : TagHelperResolver
- {
- public DefaultTagHelperResolver(bool designTime)
- {
- DesignTime = designTime;
- }
-
- public bool DesignTime { get; }
-
- private TagHelperResolutionResult GetTagHelpers(Compilation compilation)
- {
- var descriptors = new List();
-
- var providers = new ITagHelperDescriptorProvider[]
- {
- new DefaultTagHelperDescriptorProvider() { DesignTime = DesignTime, },
- new ViewComponentTagHelperDescriptorProvider(),
- };
-
- var results = new List();
- var context = TagHelperDescriptorProviderContext.Create(results);
- context.SetCompilation(compilation);
-
- for (var i = 0; i < providers.Length; i++)
- {
- var provider = providers[i];
- provider.Execute(context);
- }
-
- var diagnostics = new List();
- var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
-
- return resolutionResult;
- }
-
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
- {
- var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
- return GetTagHelpers(compilation);
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj b/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
index 7cf58777ef..369dd82020 100644
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
@@ -7,12 +7,13 @@
-
+
+ Serialization\%(FileName)%(Extension)
+
-
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
index 930abdee3a..f0bf9bdaf8 100644
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
@@ -10,34 +10,22 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Remote.Razor
{
- internal class RazorLanguageService : ServiceHubServiceBase
+ internal class RazorLanguageService : RazorServiceBase
{
public RazorLanguageService(Stream stream, IServiceProvider serviceProvider)
- : base(serviceProvider, stream)
+ : base(stream, serviceProvider)
{
- Rpc.JsonSerializer.Converters.Add(new RazorDiagnosticJsonConverter());
-
- // Due to this issue - https://github.com/dotnet/roslyn/issues/16900#issuecomment-277378950
- // We need to manually start the RPC connection. Otherwise we'd be opting ourselves into
- // race condition prone call paths.
- Rpc.StartListening();
}
- public async Task GetTagHelpersAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task GetTagHelpersAsync(ProjectSnapshotHandle projectHandle, string factoryTypeName, CancellationToken cancellationToken = default)
{
- var projectId = ProjectId.CreateFromSerialized(projectIdBytes, projectDebugName);
+ var project = await GetProjectSnapshotAsync(projectHandle, cancellationToken).ConfigureAwait(false);
- var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
- var project = solution.GetProject(projectId);
-
- var resolver = new DefaultTagHelperResolver(designTime: true);
- var result = await resolver.GetTagHelpersAsync(project, cancellationToken).ConfigureAwait(false);
-
- return result;
+ return await RazorServices.TagHelperResolver.GetTagHelpersAsync(project, factoryTypeName, cancellationToken);
}
public Task> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs
new file mode 100644
index 0000000000..6f93dbcdbf
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs
@@ -0,0 +1,67 @@
+// 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;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Remote.Razor
+{
+ internal abstract class RazorServiceBase : ServiceHubServiceBase
+ {
+ public RazorServiceBase(Stream stream, IServiceProvider serviceProvider)
+ : base(serviceProvider, stream)
+ {
+ RazorServices = new RazorServices();
+
+ Rpc.JsonSerializer.Converters.RegisterRazorConverters();
+
+ // Due to this issue - https://github.com/dotnet/roslyn/issues/16900#issuecomment-277378950
+ // We need to manually start the RPC connection. Otherwise we'd be opting ourselves into
+ // race condition prone call paths.
+ Rpc.StartListening();
+ }
+
+ protected RazorServices RazorServices { get; }
+
+ protected virtual async Task GetProjectSnapshotAsync(ProjectSnapshotHandle projectHandle, CancellationToken cancellationToken)
+ {
+ if (projectHandle == null)
+ {
+ throw new ArgumentNullException(nameof(projectHandle));
+ }
+
+ var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
+ var workspaceProject = solution.GetProject(projectHandle.WorkspaceProjectId);
+
+ return new SerializedProjectSnapshot(projectHandle.FilePath, projectHandle.Configuration, workspaceProject);
+ }
+
+ private class SerializedProjectSnapshot : ProjectSnapshot
+ {
+ public SerializedProjectSnapshot(string filePath, RazorConfiguration configuration, Project workspaceProject)
+ {
+ FilePath = filePath;
+ Configuration = configuration;
+ WorkspaceProject = workspaceProject;
+
+ IsInitialized = true;
+ Version = VersionStamp.Default;
+ }
+
+ public override RazorConfiguration Configuration { get; }
+
+ public override string FilePath { get; }
+
+ public override bool IsInitialized { get; }
+
+ public override VersionStamp Version { get; }
+
+ public override Project WorkspaceProject { get; }
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs
new file mode 100644
index 0000000000..d3a60a400c
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs
@@ -0,0 +1,22 @@
+// 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.
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ // Provides access to Razor language and workspace services that are avialable in the OOP host.
+ //
+ // Since we don't have access to the workspace we only have access to some specific things
+ // that we can construct directly.
+ internal class RazorServices
+ {
+ public RazorServices()
+ {
+ FallbackProjectEngineFactory = new FallbackProjectEngineFactory();
+ TagHelperResolver = new RemoteTagHelperResolver(FallbackProjectEngineFactory);
+ }
+
+ public IFallbackProjectEngineFactory FallbackProjectEngineFactory { get; }
+
+ public RemoteTagHelperResolver TagHelperResolver { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs
new file mode 100644
index 0000000000..5c8697db7f
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs
@@ -0,0 +1,84 @@
+// 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;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal class RemoteTagHelperResolver : TagHelperResolver
+ {
+ private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_0;
+
+ private readonly IFallbackProjectEngineFactory _fallbackFactory;
+
+ public RemoteTagHelperResolver(IFallbackProjectEngineFactory fallbackFactory)
+ {
+ if (fallbackFactory == null)
+ {
+ throw new ArgumentNullException(nameof(fallbackFactory));
+ }
+
+ _fallbackFactory = fallbackFactory;
+ }
+
+ public override Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetTagHelpersAsync(ProjectSnapshot project, string factoryTypeName, CancellationToken cancellationToken = default)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (project.Configuration == null || project.WorkspaceProject == null)
+ {
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ }
+
+ var engine = CreateProjectEngine(project, factoryTypeName);
+ return GetTagHelpersAsync(project, engine);
+ }
+
+ internal RazorProjectEngine CreateProjectEngine(ProjectSnapshot project, string factoryTypeName)
+ {
+ // This section is really similar to the code DefaultProjectEngineFactoryService
+ // but with a few differences that are significant in the remote scenario
+ //
+ // Most notably, we are going to find the Tag Helpers using a compilation, and we have
+ // no editor settings.
+ Action configure = (b) =>
+ {
+ b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true });
+ };
+
+ // The default configuration currently matches MVC-2.0. Beyond MVC-2.0 we added SDK support for
+ // properly detecting project versions, so that's a good version to assume when we can't find a
+ // configuration.
+ var configuration = project?.Configuration ?? DefaultConfiguration;
+
+ // If there's no factory to handle the configuration then fall back to a very basic configuration.
+ //
+ // This will stop a crash from happening in this case (misconfigured project), but will still make
+ // it obvious to the user that something is wrong.
+ var factory = CreateFactory(configuration, factoryTypeName) ?? _fallbackFactory;
+ return factory.Create(configuration, RazorProjectFileSystem.Empty, configure);
+ }
+
+ private IProjectEngineFactory CreateFactory(RazorConfiguration configuration, string factoryTypeName)
+ {
+ if (factoryTypeName == null)
+ {
+ return null;
+ }
+
+ return (IProjectEngineFactory)Activator.CreateInstance(Type.GetType(factoryTypeName, throwOnError: true));
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
index 4f949d7ecd..69034198bd 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
@@ -6,8 +6,6 @@ using System.IO;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
-using Mvc1_X = Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
-using MvcLatest = Microsoft.AspNetCore.Mvc.Razor.Extensions;
namespace Microsoft.VisualStudio.Editor.Razor
{
@@ -16,58 +14,127 @@ namespace Microsoft.VisualStudio.Editor.Razor
private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_0;
private readonly ProjectSnapshotManager _projectManager;
+ private readonly IFallbackProjectEngineFactory _defaultFactory;
+ private readonly Lazy[] _customFactories;
- public DefaultProjectEngineFactoryService(ProjectSnapshotManager projectManager)
+ public DefaultProjectEngineFactoryService(
+ ProjectSnapshotManager projectManager,
+ IFallbackProjectEngineFactory defaultFactory,
+ Lazy[] customFactories)
{
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
}
+ if (defaultFactory == null)
+ {
+ throw new ArgumentNullException(nameof(defaultFactory));
+ }
+
+ if (customFactories == null)
+ {
+ throw new ArgumentNullException(nameof(customFactories));
+ }
+
_projectManager = projectManager;
+ _defaultFactory = defaultFactory;
+ _customFactories = customFactories;
}
- public override RazorProjectEngine Create(string projectPath, Action configure)
+ public override IProjectEngineFactory FindFactory(ProjectSnapshot project)
{
- if (projectPath == null)
+ if (project == null)
{
- throw new ArgumentNullException(nameof(projectPath));
+ throw new ArgumentNullException(nameof(project));
}
- // In 15.5 we expect projectPath to be a directory, NOT the path to the csproj.
- var project = FindProject(projectPath);
- var configuration = project?.Configuration ?? DefaultConfiguration;
- var fileSystem = RazorProjectFileSystem.Create(projectPath);
-
- RazorProjectEngine projectEngine;
- if (configuration.LanguageVersion.Major == 1)
- {
- projectEngine = RazorProjectEngine.Create(configuration, fileSystem, b =>
- {
- configure?.Invoke(b);
-
- Mvc1_X.RazorExtensions.Register(b);
-
- if (configuration.LanguageVersion.Minor >= 1)
- {
- Mvc1_X.RazorExtensions.RegisterViewComponentTagHelpers(b);
- }
- });
- }
- else
- {
- projectEngine = RazorProjectEngine.Create(configuration, fileSystem, b =>
- {
- configure?.Invoke(b);
-
- MvcLatest.RazorExtensions.Register(b);
- });
- }
-
- return projectEngine;
+ return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: false);
}
- private ProjectSnapshot FindProject(string directory)
+ public override IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: true);
+ }
+
+ public override RazorProjectEngine Create(ProjectSnapshot project, Action configure)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ return CreateCore(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure);
+ }
+
+ public override RazorProjectEngine Create(string directoryPath, Action configure)
+ {
+ if (directoryPath == null)
+ {
+ throw new ArgumentNullException(nameof(directoryPath));
+ }
+
+ var project = FindProjectByDirectory(directoryPath);
+ return CreateCore(project, RazorProjectFileSystem.Create(directoryPath), configure);
+ }
+
+ public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException(nameof(fileSystem));
+ }
+
+ return CreateCore(project, fileSystem, configure);
+ }
+
+ private RazorProjectEngine CreateCore(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // When we're running in the editor, the editor provides a configure delegate that will include
+ // the editor settings and tag helpers.
+ //
+ // This service is only used in process in Visual Studio, and any other callers should provide these
+ // things also.
+ configure = configure ?? ((b) => { });
+
+ // The default configuration currently matches MVC-2.0. Beyond MVC-2.0 we added SDK support for
+ // properly detecting project versions, so that's a good version to assume when we can't find a
+ // configuration.
+ var configuration = project?.Configuration ?? DefaultConfiguration;
+
+ // If there's no factory to handle the configuration then fall back to a very basic configuration.
+ //
+ // This will stop a crash from happening in this case (misconfigured project), but will still make
+ // it obvious to the user that something is wrong.
+ var factory = SelectFactory(configuration) ?? _defaultFactory;
+ return factory.Create(configuration, fileSystem, configure);
+ }
+
+ private IProjectEngineFactory SelectFactory(RazorConfiguration configuration, bool requireSerializable = false)
+ {
+ for (var i = 0; i < _customFactories.Length; i++)
+ {
+ var factory = _customFactories[i];
+ if (string.Equals(configuration.ConfigurationName, factory.Metadata.ConfigurationName))
+ {
+ return requireSerializable && !factory.Metadata.SupportsSerialization ? null : factory.Value;
+ }
+ }
+
+ return null;
+ }
+
+ private ProjectSnapshot FindProjectByDirectory(string directory)
{
directory = NormalizeDirectoryPath(directory);
@@ -75,9 +142,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
for (var i = 0; i < projects.Count; i++)
{
var project = projects[i];
- if (project.WorkspaceProject?.FilePath != null)
+ if (project.FilePath != null)
{
- if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.WorkspaceProject.FilePath)), StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.FilePath)), StringComparison.OrdinalIgnoreCase))
{
return project;
}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
index babed97a5c..13617d03b5 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
@@ -1,6 +1,10 @@
// 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;
+using System.Collections.Generic;
+using System.Composition;
+using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
@@ -11,9 +15,34 @@ namespace Microsoft.VisualStudio.Editor.Razor
[ExportLanguageServiceFactory(typeof(RazorProjectEngineFactoryService), RazorLanguage.Name, ServiceLayer.Default)]
internal class DefaultProjectEngineFactoryServiceFactory : ILanguageServiceFactory
{
+ private readonly Lazy[] _customFactories;
+ private readonly IFallbackProjectEngineFactory _fallbackFactory;
+
+ [ImportingConstructor]
+ public DefaultProjectEngineFactoryServiceFactory(
+ IFallbackProjectEngineFactory fallbackFactory,
+ [ImportMany] IEnumerable> customFactories)
+ {
+ if (fallbackFactory == null)
+ {
+ throw new ArgumentNullException(nameof(fallbackFactory));
+ }
+
+ if (customFactories == null)
+ {
+ throw new ArgumentNullException(nameof(customFactories));
+ }
+
+ _fallbackFactory = fallbackFactory;
+ _customFactories = customFactories.ToArray();
+ }
+
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultProjectEngineFactoryService(languageServices.GetRequiredService());
+ return new DefaultProjectEngineFactoryService(
+ languageServices.GetRequiredService(),
+ _fallbackFactory,
+ _customFactories);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
index 7547bde441..b303f7da60 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
@@ -2,59 +2,45 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.Editor.Razor
{
internal class DefaultTagHelperResolver : TagHelperResolver
{
- // Hack for testability. The view component visitor will normally just no op if we're not referencing
- // an appropriate version of MVC.
- internal bool ForceEnableViewComponentDiscovery { get; set; }
+ private readonly RazorProjectEngineFactoryService _engineFactory;
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
+ public DefaultTagHelperResolver(RazorProjectEngineFactoryService engineFactory)
+ {
+ if (engineFactory == null)
+ {
+ throw new ArgumentNullException(nameof(engineFactory));
+ }
+
+ _engineFactory = engineFactory;
+ }
+
+ public override Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
- var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
- var result = GetTagHelpers(compilation);
- return result;
- }
-
- // Internal for testing
- internal TagHelperResolutionResult GetTagHelpers(Compilation compilation)
- {
- var descriptors = new List();
-
- var providers = new ITagHelperDescriptorProvider[]
+ if (project.Configuration == null || project.WorkspaceProject == null)
{
- new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
- new ViewComponentTagHelperDescriptorProvider() { ForceEnabled = ForceEnableViewComponentDiscovery },
- };
-
- var results = new List();
- var context = TagHelperDescriptorProviderContext.Create(results);
- context.SetCompilation(compilation);
-
- for (var i = 0; i < providers.Length; i++)
- {
- var provider = providers[i];
- provider.Execute(context);
+ return Task.FromResult(TagHelperResolutionResult.Empty);
}
- var diagnostics = new List();
- var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
-
- return resolutionResult;
+ var engine = _engineFactory.Create(project, RazorProjectFileSystem.Empty, b =>
+ {
+ b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true, });
+ });
+ return GetTagHelpersAsync(project, engine);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
index 50e2e96179..1a238ad473 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
@@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultTagHelperResolver();
+ return new DefaultTagHelperResolver(languageServices.GetRequiredService());
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs
new file mode 100644
index 0000000000..9b3b92eb89
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs
@@ -0,0 +1,32 @@
+// 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;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-1.0", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_1_0 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
+
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_1_0).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs
new file mode 100644
index 0000000000..d01e6999a8
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs
@@ -0,0 +1,32 @@
+// 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;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-1.1", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_1_1 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
+
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_1_1).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs
new file mode 100644
index 0000000000..ebf9e144f5
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs
@@ -0,0 +1,31 @@
+// 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;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-2.0", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_2_0 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions";
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_2_0).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs
new file mode 100644
index 0000000000..df8d04667c
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs
@@ -0,0 +1,33 @@
+// 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;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ // Currently we provide a fixed configuration for 2.1, but this is a point-in-time issue. We plan
+ // to make the 2.1 configuration more flexible and less hardcoded.
+ [ExportCustomProjectEngineFactory("MVC-2.1", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_2_1 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions";
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_2_1).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj b/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
index 32b484f542..445cb0e354 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
+++ b/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
@@ -14,8 +14,6 @@
-
-
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/LegacyTagHelperResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/LegacyTagHelperResolver.cs
index 54b67052e2..ecd4d9e256 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/LegacyTagHelperResolver.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/LegacyTagHelperResolver.cs
@@ -1,11 +1,12 @@
// 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;
using System.ComponentModel.Composition;
-using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
@@ -14,23 +15,42 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
// use TagHelperResolver.
// ----------------------------------------------------------------------------------------------------
[Export(typeof(ITagHelperResolver))]
- internal class LegacyTagHelperResolver : OOPTagHelperResolver, ITagHelperResolver
+ internal class LegacyTagHelperResolver : ITagHelperResolver
{
+ private readonly Workspace _workspace;
+
[ImportingConstructor]
- public LegacyTagHelperResolver(
- [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
- : base(workspace)
+ public LegacyTagHelperResolver([Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ _workspace = workspace;
}
public Task GetTagHelpersAsync(Project project)
{
if (project == null)
{
- throw new System.ArgumentNullException(nameof(project));
+ throw new ArgumentNullException(nameof(project));
}
- return base.GetTagHelpersAsync(project, CancellationToken.None);
+ if (project.FilePath == null)
+ {
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ }
+
+ var projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService();
+ var projectSnapshot = projectManager.GetProjectWithFilePath(project.FilePath);
+ if (projectSnapshot == null)
+ {
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ }
+
+ var resolver = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService();
+ return resolver.GetTagHelpersAsync(projectSnapshot);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
index f0e81aeece..130d148baa 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -15,53 +16,67 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
internal class OOPTagHelperResolver : TagHelperResolver
{
private readonly DefaultTagHelperResolver _defaultResolver;
+ private readonly RazorProjectEngineFactoryService _engineFactory;
+ private readonly ErrorReporter _errorReporter;
private readonly Workspace _workspace;
- public OOPTagHelperResolver(Workspace workspace)
+ public OOPTagHelperResolver(RazorProjectEngineFactoryService engineFactory, ErrorReporter errorReporter, Workspace workspace)
{
+ if (engineFactory == null)
+ {
+ throw new ArgumentNullException(nameof(engineFactory));
+ }
+
+ if (errorReporter == null)
+ {
+ throw new ArgumentNullException(nameof(errorReporter));
+ }
+
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
+ _engineFactory = engineFactory;
+ _errorReporter = errorReporter;
_workspace = workspace;
- _defaultResolver = new DefaultTagHelperResolver();
+
+ _defaultResolver = new DefaultTagHelperResolver(_engineFactory);
}
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
+ public override async Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
+ if (project.Configuration == null || project.WorkspaceProject == null)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ // Not every custom factory supports the OOP host. Our priority system should work like this:
+ //
+ // 1. Use custom factory out of process
+ // 2. Use custom factory in process
+ // 3. Use fallback factory in process
+ //
+ // Calling into RazorTemplateEngineFactoryService.Create will accomplish #2 and #3 in one step.
+ var factory = _engineFactory.FindSerializableFactory(project);
+
try
{
TagHelperResolutionResult result = null;
-
- // We're being defensive here because the OOP host can return null for the client/session/operation
- // when it's disconnected (user stops the process).
- var client = await RazorLanguageServiceClientFactory.CreateAsync(_workspace, cancellationToken);
- if (client != null)
+ if (factory != null)
{
- using (var session = await client.CreateSessionAsync(project.Solution))
- {
- if (session != null)
- {
- var jsonObject = await session.InvokeAsync(
- "GetTagHelpersAsync",
- new object[] { project.Id.Id, "Foo", },
- cancellationToken).ConfigureAwait(false);
-
- result = GetTagHelperResolutionResult(jsonObject);
- }
- }
+ result = await ResolveTagHelpersOutOfProcessAsync(factory, project);
}
if (result == null)
{
// Was unable to get tag helpers OOP, fallback to default behavior.
- result = await _defaultResolver.GetTagHelpersAsync(project, cancellationToken);
+ result = await ResolveTagHelpersInProcessAsync(project);
}
return result;
@@ -76,11 +91,61 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
}
}
- private TagHelperResolutionResult GetTagHelperResolutionResult(JObject jsonObject)
+ protected virtual async Task ResolveTagHelpersOutOfProcessAsync(IProjectEngineFactory factory, ProjectSnapshot project)
+ {
+ // We're being overly defensive here because the OOP host can return null for the client/session/operation
+ // when it's disconnected (user stops the process).
+ //
+ // This will change in the future to an easier to consume API but for VS RTM this is what we have.
+ try
+ {
+ var client = await RazorLanguageServiceClientFactory.CreateAsync(_workspace, CancellationToken.None);
+ if (client != null)
+ {
+ using (var session = await client.CreateSessionAsync(project.WorkspaceProject.Solution))
+ {
+ if (session != null)
+ {
+ var args = new object[]
+ {
+ Serialize(project),
+ factory == null ? null : factory.GetType().AssemblyQualifiedName,
+ };
+
+ var json = await session.InvokeAsync("GetTagHelpersAsync", args, CancellationToken.None).ConfigureAwait(false);
+ return Deserialize(json);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // We silence exceptions from the OOP host because we don't want to bring down VS for an OOP failure.
+ // We will retry all failures in process anyway, so if there's a real problem that isn't unique to OOP
+ // then it will report a crash in VS.
+ _errorReporter.ReportError(ex, project);
+ }
+
+ return null;
+ }
+
+ protected virtual Task ResolveTagHelpersInProcessAsync(ProjectSnapshot project)
+ {
+ return _defaultResolver.GetTagHelpersAsync(project);
+ }
+
+ private JObject Serialize(ProjectSnapshot snapshot)
{
var serializer = new JsonSerializer();
- serializer.Converters.Add(TagHelperDescriptorJsonConverter.Instance);
- serializer.Converters.Add(RazorDiagnosticJsonConverter.Instance);
+ serializer.Converters.RegisterRazorConverters();
+
+ return JObject.FromObject(snapshot, serializer);
+ }
+
+ private TagHelperResolutionResult Deserialize(JObject jsonObject)
+ {
+ var serializer = new JsonSerializer();
+ serializer.Converters.RegisterRazorConverters();
using (var reader = jsonObject.CreateReader())
{
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
index d3265f7778..b6b4e93d52 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
@@ -14,8 +14,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- var workspace = languageServices.WorkspaceServices.Workspace;
- return new OOPTagHelperResolver(workspace);
+ return new OOPTagHelperResolver(
+ languageServices.GetRequiredService(),
+ languageServices.WorkspaceServices.GetRequiredService(),
+ languageServices.WorkspaceServices.Workspace);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs
new file mode 100644
index 0000000000..1f008fd63d
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs
@@ -0,0 +1,27 @@
+// 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;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal static class JsonConverterCollectionExtensions
+ {
+ public static void RegisterRazorConverters(this JsonConverterCollection collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ collection.Add(TagHelperDescriptorJsonConverter.Instance);
+ collection.Add(RazorDiagnosticJsonConverter.Instance);
+ collection.Add(RazorExtensionJsonConverter.Instance);
+ collection.Add(RazorConfigurationJsonConverter.Instance);
+ collection.Add(ProjectSnapshotJsonConverter.Instance);
+ collection.Add(ProjectSnapshotHandleJsonConverter.Instance);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs
new file mode 100644
index 0000000000..d186db2ec8
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs
@@ -0,0 +1,29 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal sealed class ProjectSnapshotHandle
+ {
+ public ProjectSnapshotHandle(string filePath, RazorConfiguration configuration, ProjectId workspaceProjectId)
+ {
+ if (filePath == null)
+ {
+ throw new ArgumentNullException(nameof(filePath));
+ }
+
+ FilePath = filePath;
+ Configuration = configuration;
+ WorkspaceProjectId = workspaceProjectId;
+ }
+
+ public RazorConfiguration Configuration { get; }
+
+ public string FilePath { get; }
+
+ public ProjectId WorkspaceProjectId { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs
new file mode 100644
index 0000000000..5b72763912
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs
@@ -0,0 +1,73 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class ProjectSnapshotHandleJsonConverter : JsonConverter
+ {
+ public static readonly ProjectSnapshotHandleJsonConverter Instance = new ProjectSnapshotHandleJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(ProjectSnapshotHandle).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var filePath = obj[nameof(ProjectSnapshotHandle.FilePath)].Value();
+ var configuration = obj[nameof(ProjectSnapshotHandle.Configuration)].ToObject(serializer);
+
+ var id = obj[nameof(ProjectSnapshotHandle.WorkspaceProjectId)].Value();
+ var workspaceProjectId = id == null ? null : ProjectId.CreateFromSerialized(Guid.Parse(id));
+
+ return new ProjectSnapshotHandle(filePath, configuration, workspaceProjectId);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var handle = (ProjectSnapshotHandle)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.FilePath));
+ writer.WriteValue(handle.FilePath);
+
+ if (handle.Configuration == null)
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.Configuration));
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.Configuration));
+ serializer.Serialize(writer, handle.Configuration);
+ }
+
+ if (handle.WorkspaceProjectId == null)
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.WorkspaceProjectId));
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.WorkspaceProjectId));
+ writer.WriteValue(handle.WorkspaceProjectId.Id);
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs
new file mode 100644
index 0000000000..988d42b44d
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs
@@ -0,0 +1,40 @@
+// 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;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ // We can't truly serialize a snapshot because it has access to a Workspace Project\
+ //
+ // Instead we serialize to a ProjectSnapshotHandle and then use that to re-create the snapshot
+ // inside the remote host.
+ internal class ProjectSnapshotJsonConverter : JsonConverter
+ {
+ public static readonly ProjectSnapshotJsonConverter Instance = new ProjectSnapshotJsonConverter();
+
+ public override bool CanRead => false;
+
+ public override bool CanWrite => true;
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(ProjectSnapshot).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var project = (ProjectSnapshot)value;
+ var handle = new ProjectSnapshotHandle(project.FilePath, project.Configuration, project.WorkspaceProject?.Id);
+
+ ProjectSnapshotHandleJsonConverter.Instance.WriteJson(writer, handle, serializer);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs
new file mode 100644
index 0000000000..26be8c52b7
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs
@@ -0,0 +1,53 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class RazorConfigurationJsonConverter : JsonConverter
+ {
+ public static readonly RazorConfigurationJsonConverter Instance = new RazorConfigurationJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(RazorConfiguration).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var configurationName = obj[nameof(RazorConfiguration.ConfigurationName)].Value();
+ var languageVersion = obj[nameof(RazorConfiguration.LanguageVersion)].Value();
+ var extensions = obj[nameof(RazorConfiguration.Extensions)].ToObject(serializer);
+
+ return RazorConfiguration.Create(RazorLanguageVersion.Parse(languageVersion), configurationName, extensions);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var configuration = (RazorConfiguration)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(RazorConfiguration.ConfigurationName));
+ writer.WriteValue(configuration.ConfigurationName);
+
+ writer.WritePropertyName(nameof(RazorConfiguration.LanguageVersion));
+ writer.WriteValue(configuration.LanguageVersion.ToString());
+
+ writer.WritePropertyName(nameof(RazorConfiguration.Extensions));
+ serializer.Serialize(writer, configuration.Extensions);
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
similarity index 97%
rename from src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs
rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
index 85d0a4096b..003dbf9164 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace Microsoft.VisualStudio.LanguageServices.Razor
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{
internal class RazorDiagnosticJsonConverter : JsonConverter
{
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs
new file mode 100644
index 0000000000..0d84b8c939
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs
@@ -0,0 +1,45 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class RazorExtensionJsonConverter : JsonConverter
+ {
+ public static readonly RazorExtensionJsonConverter Instance = new RazorExtensionJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(RazorExtension).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var extensionName = obj[nameof(RazorExtension.ExtensionName)].Value();
+
+ return new SerializedRazorExtension(extensionName);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var extension = (RazorExtension)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(RazorExtension.ExtensionName));
+ writer.WriteValue(extension.ExtensionName);
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs
new file mode 100644
index 0000000000..0f0292534f
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs
@@ -0,0 +1,23 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class SerializedRazorExtension : RazorExtension
+ {
+ public SerializedRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
similarity index 99%
rename from src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs
rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
index 406ea9a85f..271af9e1fd 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace Microsoft.VisualStudio.LanguageServices.Razor
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{
internal class TagHelperDescriptorJsonConverter : JsonConverter
{
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
index 12ecf74efe..1f418b2855 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Reflection;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
@@ -31,28 +32,62 @@ namespace Microsoft.VisualStudio.Editor.Razor
HostProject_For_1_0 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_1_0);
HostProject_For_1_1 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_1_1);
HostProject_For_2_0 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_2_0);
+
+ HostProject_For_2_1 = new HostProject(
+ "/TestPath/SomePath/Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "MVC-2.1", Array.Empty()));
+ HostProject_For_UnknownConfiguration = new HostProject(
+ "/TestPath/SomePath/Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty()));
+
+
+ CustomFactories = new Lazy[]
+ {
+ new Lazy(
+ () => new LegacyProjectEngineFactory_1_0(),
+ typeof(LegacyProjectEngineFactory_1_0).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_1_1(),
+ typeof(LegacyProjectEngineFactory_1_1).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_0(),
+ typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_1(),
+ typeof(LegacyProjectEngineFactory_2_1).GetCustomAttribute()),
+ };
+
+ FallbackFactory = new FallbackProjectEngineFactory();
}
+ private Lazy[] CustomFactories { get; }
+
+ private IFallbackProjectEngineFactory FallbackFactory { get; }
+
private HostProject HostProject_For_1_0 { get; }
private HostProject HostProject_For_1_1 { get; }
private HostProject HostProject_For_2_0 { get; }
+ private HostProject HostProject_For_2_1 { get; }
+
+ private HostProject HostProject_For_UnknownConfiguration { get; }
+
// We don't actually look at the project, we rely on the ProjectStateManager
private Project WorkspaceProject { get; }
private Workspace Workspace { get; }
[Fact]
- public void Create_CreatesTemplateEngine_ForLatest()
+ public void Create_CreatesDesignTimeTemplateEngine_ForVersion2_1()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.HostProjectAdded(HostProject_For_2_1);
projectManager.WorkspaceProjectAdded(WorkspaceProject);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -62,6 +97,30 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ }
+
+ [Fact]
+ public void Create_CreatesDesignTimeTemplateEngine_ForVersion2_0()
+ {
+ // Arrange
+ var projectManager = new TestProjectSnapshotManager(Workspace);
+ projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
+
+ // Act
+ var engine = factoryService.Create("/TestPath/SomePath/", b =>
+ {
+ b.Features.Add(new MyCoolNewFeature());
+ });
+
+ // Assert
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
}
@@ -74,7 +133,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
projectManager.HostProjectAdded(HostProject_For_1_1);
projectManager.WorkspaceProjectAdded(WorkspaceProject);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -84,6 +143,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
}
@@ -96,7 +156,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
projectManager.HostProjectAdded(HostProject_For_1_0);
projectManager.WorkspaceProjectAdded(WorkspaceProject);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -106,17 +166,18 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
}
[Fact]
- public void Create_UnknownProjectPath_UsesLatest()
+ public void Create_UnknownProject_UsesVersion2_0()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/DifferentPath/", b =>
@@ -126,30 +187,33 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
}
[Fact]
- public void Create_MvcReferenceNotFound_UsesLatest()
+ public void Create_ForUnknownConfiguration_UsesFallbackFactory()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.HostProjectAdded(HostProject_For_UnknownConfiguration);
projectManager.WorkspaceProjectAdded(WorkspaceProject);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
- var engine = factoryService.Create("/TestPath/DifferentPath/", b =>
+ var engine = factoryService.Create("/TestPath/SomePath/", b =>
{
b.Features.Add(new MyCoolNewFeature());
});
// Assert
Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
}
private class MyCoolNewFeature : IRazorEngineFeature
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj b/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
index 6f4876aec0..e40eebd7e7 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
@@ -10,6 +10,8 @@
+
+
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
index 09ab7e7662..a4b7553241 100644
--- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
@@ -18,6 +18,8 @@
+
+
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs
new file mode 100644
index 0000000000..cb9a4e8a6b
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs
@@ -0,0 +1,177 @@
+// 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;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Microsoft.VisualStudio.Editor.Razor;
+using Moq;
+using Xunit;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor
+{
+ public class OOPTagHelperResolverTest
+ {
+ public OOPTagHelperResolverTest()
+ {
+ HostProject_For_2_0 = new HostProject("Test.csproj", FallbackRazorConfiguration.MVC_2_0);
+ HostProject_For_NonSerializableConfiguration = new HostProject(
+ "Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty()));
+
+ CustomFactories = new Lazy[]
+ {
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_0(),
+ typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute()),
+
+ // We don't really use this factory, we just use it to ensure that the call is going to go out of process.
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_1(),
+ new ExportCustomProjectEngineFactoryAttribute("Blazor-0.1") { SupportsSerialization = false, }),
+ };
+
+ FallbackFactory = new FallbackProjectEngineFactory();
+
+ Workspace = new AdhocWorkspace();
+
+ var info = ProjectInfo.Create(ProjectId.CreateNewId("Test"), VersionStamp.Default, "Test", "Test", LanguageNames.CSharp, filePath: "Test.csproj");
+ WorkspaceProject = Workspace.CurrentSolution.AddProject(info).GetProject(info.Id);
+
+ ErrorReporter = new DefaultErrorReporter();
+ ProjectManager = new TestProjectSnapshotManager(Workspace);
+ EngineFactory = new DefaultProjectEngineFactoryService(ProjectManager, FallbackFactory, CustomFactories);
+ }
+
+ private ErrorReporter ErrorReporter { get; }
+
+ private RazorProjectEngineFactoryService EngineFactory { get; }
+
+ private Lazy[] CustomFactories { get; }
+
+ private IFallbackProjectEngineFactory FallbackFactory { get; }
+
+ private HostProject HostProject_For_2_0 { get; }
+
+ private HostProject HostProject_For_NonSerializableConfiguration { get; }
+
+ private ProjectSnapshotManagerBase ProjectManager { get; }
+
+ private Project WorkspaceProject { get; }
+
+ private Workspace Workspace { get; }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithNonInitializedProject_Noops()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_2_0);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace);
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+ }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithSerializableCustomFactory_GoesOutOfProcess()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_2_0);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace)
+ {
+ OnResolveOutOfProcess = (f, p) =>
+ {
+ Assert.Same(CustomFactories[0].Value, f);
+ Assert.Same(project, p);
+
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ },
+ };
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+ }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithNonSerializableCustomFactory_StaysInProcess()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_NonSerializableConfiguration);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace)
+ {
+ OnResolveInProcess = (p) =>
+ {
+ Assert.Same(project, p);
+
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ },
+ };
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+
+ }
+
+ private class TestTagHelperResolver : OOPTagHelperResolver
+ {
+ public TestTagHelperResolver(RazorProjectEngineFactoryService engineFactory, ErrorReporter errorReporter, Workspace workspace)
+ : base(engineFactory, errorReporter, workspace)
+ {
+ }
+
+ public Func> OnResolveOutOfProcess { get; set; }
+
+ public Func> OnResolveInProcess { get; set; }
+
+ protected override Task ResolveTagHelpersOutOfProcessAsync(IProjectEngineFactory factory, ProjectSnapshot project)
+ {
+ Assert.NotNull(OnResolveOutOfProcess);
+ return OnResolveOutOfProcess(factory, project);
+ }
+
+ protected override Task ResolveTagHelpersInProcessAsync(ProjectSnapshot project)
+ {
+ Assert.NotNull(OnResolveInProcess);
+ return OnResolveInProcess(project);
+ }
+ }
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(Workspace workspace)
+ : base(
+ Mock.Of(),
+ Mock.Of(),
+ Mock.Of(),
+ Enumerable.Empty(),
+ workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/ProjectSnapshotHandleSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/ProjectSnapshotHandleSerializationTest.cs
new file mode 100644
index 0000000000..347c533f8a
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/ProjectSnapshotHandleSerializationTest.cs
@@ -0,0 +1,73 @@
+// 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;
+using System.Linq;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ public class ProjectSnapshotHandleSerializationTest
+ {
+ public ProjectSnapshotHandleSerializationTest()
+ {
+ var converters = new JsonConverterCollection();
+ converters.RegisterRazorConverters();
+ Converters = converters.ToArray();
+ }
+
+ public JsonConverter[] Converters { get; }
+
+ [Fact]
+ public void ProjectSnapshotHandleJsonConverter_Serialization_CanKindaRoundTrip()
+ {
+ // Arrange
+ var snapshot = new ProjectSnapshotHandle(
+ "Test.csproj",
+ new ProjectSystemRazorConfiguration(
+ RazorLanguageVersion.Version_1_1,
+ "Test",
+ new[]
+ {
+ new ProjectSystemRazorExtension("Test-Extension1"),
+ new ProjectSystemRazorExtension("Test-Extension2"),
+ }),
+ ProjectId.CreateFromSerialized(Guid.NewGuid(), "Test"));
+
+ // Act
+ var json = JsonConvert.SerializeObject(snapshot, Converters);
+ var obj = JsonConvert.DeserializeObject(json, Converters);
+
+ // Assert
+ Assert.Equal(snapshot.FilePath, obj.FilePath);
+ Assert.Equal(snapshot.Configuration.ConfigurationName, obj.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("Test-Extension1", e.ExtensionName),
+ e => Assert.Equal("Test-Extension2", e.ExtensionName));
+ Assert.Equal(snapshot.Configuration.LanguageVersion, obj.Configuration.LanguageVersion);
+ Assert.Equal(snapshot.WorkspaceProjectId.Id, obj.WorkspaceProjectId.Id);
+ }
+
+ [Fact]
+ public void ProjectSnapshotHandleJsonConverter_SerializationWithNulls_CanKindaRoundTrip()
+ {
+ // Arrange
+ var snapshot = new ProjectSnapshotHandle("Test.csproj", null, null);
+
+ // Act
+ var json = JsonConvert.SerializeObject(snapshot, Converters);
+ var obj = JsonConvert.DeserializeObject(json, Converters);
+
+ // Assert
+ Assert.Equal(snapshot.FilePath, obj.FilePath);
+ Assert.Null(obj.Configuration);
+ Assert.Null(obj.WorkspaceProjectId);
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorConfigurationSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorConfigurationSerializationTest.cs
new file mode 100644
index 0000000000..2b084a8531
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorConfigurationSerializationTest.cs
@@ -0,0 +1,50 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ public class RazorConfigurationSerializationTest
+ {
+ public RazorConfigurationSerializationTest()
+ {
+ var converters = new JsonConverterCollection();
+ converters.RegisterRazorConverters();
+ Converters = converters.ToArray();
+ }
+
+ public JsonConverter[] Converters { get; }
+
+ [Fact]
+ public void RazorConfigurationJsonConverter_Serialization_CanRoundTrip()
+ {
+ // Arrange
+ var configuration = new ProjectSystemRazorConfiguration(
+ RazorLanguageVersion.Version_1_1,
+ "Test",
+ new[]
+ {
+ new ProjectSystemRazorExtension("Test-Extension1"),
+ new ProjectSystemRazorExtension("Test-Extension2"),
+ });
+
+ // Act
+ var json = JsonConvert.SerializeObject(configuration, Converters);
+ var obj = JsonConvert.DeserializeObject(json, Converters);
+
+ // Assert
+ Assert.Equal(configuration.ConfigurationName, obj.ConfigurationName);
+ Assert.Collection(
+ configuration.Extensions,
+ e => Assert.Equal("Test-Extension1", e.ExtensionName),
+ e => Assert.Equal("Test-Extension2", e.ExtensionName));
+ Assert.Equal(configuration.LanguageVersion, obj.LanguageVersion);
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorExtensionSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorExtensionSerializationTest.cs
new file mode 100644
index 0000000000..0f93fb5bd7
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/RazorExtensionSerializationTest.cs
@@ -0,0 +1,38 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ public class RazorExtensionSerializationTest
+ {
+ public RazorExtensionSerializationTest()
+ {
+ var converters = new JsonConverterCollection();
+ converters.RegisterRazorConverters();
+ Converters = converters.ToArray();
+ }
+
+ public JsonConverter[] Converters { get; }
+
+ [Fact]
+ public void RazorExensionJsonConverter_Serialization_CanRoundTrip()
+ {
+ // Arrange
+ var extension = new ProjectSystemRazorExtension("Test");
+
+ // Act
+ var json = JsonConvert.SerializeObject(extension, Converters);
+ var obj = JsonConvert.DeserializeObject(json, Converters);
+
+ // Assert
+ Assert.Equal(extension.ExtensionName, obj.ExtensionName);
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs
similarity index 99%
rename from test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs
rename to test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs
index 4e37727804..bf799a094a 100644
--- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
using Xunit;
diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs
index 7efaa43199..82054dbabf 100644
--- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs
+++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs
@@ -177,7 +177,8 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
.Select(reference => reference.Display)
.Select(filter => Path.GetFileNameWithoutExtension(filter));
var projectFilters = project.AllProjectReferences.Select(filter => solution.GetProject(filter.ProjectId).AssemblyName);
- var resolutionResult = await _tagHelperResolver.GetTagHelpersAsync(project, CancellationToken.None);
+
+ var resolutionResult = await _tagHelperResolver.GetTagHelpersAsync(projectViewModel.Snapshot.Project);
var files = GetCshtmlDocuments(project);