Decouple tooling from MVC assemblies

This commit is contained in:
Ryan Nowak 2018-02-06 22:41:34 -08:00
parent 5008c7803c
commit 0b777dad3f
51 changed files with 1458 additions and 236 deletions

View File

@ -10,14 +10,12 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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")]

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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()
{

View File

@ -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<INamedTypeSymbol> _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> 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);
}

View File

@ -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<RazorProjectEngineBuilder> 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);
}
}
}

View File

@ -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
{
}
}

View File

@ -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;
}

View File

@ -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")]

View File

@ -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<RazorProjectEngineBuilder> configure);
public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project);
public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project);
public abstract RazorProjectEngine Create(ProjectSnapshot project, Action<RazorProjectEngineBuilder> configure);
public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure);
public abstract RazorProjectEngine Create(string directoryPath, Action<RazorProjectEngineBuilder> configure);
}
}

View File

@ -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<TagHelperDescriptor>(), Array.Empty<RazorDiagnostic>());
public TagHelperResolutionResult(IReadOnlyList<TagHelperDescriptor> descriptors, IReadOnlyList<RazorDiagnostic> diagnostics)
{
Descriptors = descriptors;

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken);
public abstract Task<TagHelperResolutionResult> GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default);
protected virtual async Task<TagHelperResolutionResult> 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<ITagHelperDescriptorProvider>().ToArray();
if (providers.Length == 0)
{
return TagHelperResolutionResult.Empty;
}
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>());
}
}
}

View File

@ -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<TagHelperDescriptor>();
var providers = new ITagHelperDescriptorProvider[]
{
new DefaultTagHelperDescriptorProvider() { DesignTime = DesignTime, },
new ViewComponentTagHelperDescriptorProvider(),
};
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>();
var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
return resolutionResult;
}
public override async Task<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return GetTagHelpers(compilation);
}
}
}

View File

@ -7,12 +7,13 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs" />
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.Language\Microsoft.AspNetCore.Razor.Language.csproj" />
</ItemGroup>

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
public async Task<TagHelperResolutionResult> 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<IEnumerable<DirectiveDescriptor>> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))

View File

@ -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<ProjectSnapshot> 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; }
}
}
}

View File

@ -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; }
}
}

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<TagHelperResolutionResult> 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<RazorProjectEngineBuilder> 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));
}
}
}

View File

@ -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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] _customFactories;
public DefaultProjectEngineFactoryService(ProjectSnapshotManager projectManager)
public DefaultProjectEngineFactoryService(
ProjectSnapshotManager projectManager,
IFallbackProjectEngineFactory defaultFactory,
Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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;
}

View File

@ -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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] _customFactories;
private readonly IFallbackProjectEngineFactory _fallbackFactory;
[ImportingConstructor]
public DefaultProjectEngineFactoryServiceFactory(
IFallbackProjectEngineFactory fallbackFactory,
[ImportMany] IEnumerable<Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>> 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<ProjectSnapshotManager>());
return new DefaultProjectEngineFactoryService(
languageServices.GetRequiredService<ProjectSnapshotManager>(),
_fallbackFactory,
_customFactories);
}
}
}

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
public DefaultTagHelperResolver(RazorProjectEngineFactoryService engineFactory)
{
if (engineFactory == null)
{
throw new ArgumentNullException(nameof(engineFactory));
}
_engineFactory = engineFactory;
}
public override Task<TagHelperResolutionResult> 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<TagHelperDescriptor>();
var providers = new ITagHelperDescriptorProvider[]
if (project.Configuration == null || project.WorkspaceProject == null)
{
new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
new ViewComponentTagHelperDescriptorProvider() { ForceEnabled = ForceEnableViewComponentDiscovery },
};
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>();
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);
}
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
return new DefaultTagHelperResolver();
return new DefaultTagHelperResolver(languageServices.GetRequiredService<RazorProjectEngineFactoryService>());
}
}
}

View File

@ -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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -14,8 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
</ItemGroup>
</Project>

View File

@ -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<TagHelperResolutionResult> 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<ProjectSnapshotManager>();
var projectSnapshot = projectManager.GetProjectWithFilePath(project.FilePath);
if (projectSnapshot == null)
{
return Task.FromResult(TagHelperResolutionResult.Empty);
}
var resolver = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<TagHelperResolver>();
return resolver.GetTagHelpersAsync(projectSnapshot);
}
}
}

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
public override async Task<TagHelperResolutionResult> 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<JObject>(
"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<TagHelperResolutionResult> 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<JObject>("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<TagHelperResolutionResult> 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())
{

View File

@ -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<RazorProjectEngineFactoryService>(),
languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>(),
languageServices.WorkspaceServices.Workspace);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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<string>();
var configuration = obj[nameof(ProjectSnapshotHandle.Configuration)].ToObject<RazorConfiguration>(serializer);
var id = obj[nameof(ProjectSnapshotHandle.WorkspaceProjectId)].Value<string>();
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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<string>();
var languageVersion = obj[nameof(RazorConfiguration.LanguageVersion)].Value<string>();
var extensions = obj[nameof(RazorConfiguration.Extensions)].ToObject<RazorExtension[]>(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();
}
}
}

View File

@ -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
{

View File

@ -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<string>();
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();
}
}
}

View File

@ -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; }
}
}

View File

@ -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
{

View File

@ -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<RazorExtension>()));
HostProject_For_UnknownConfiguration = new HostProject(
"/TestPath/SomePath/Test.csproj",
new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty<RazorExtension>()));
CustomFactories = new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[]
{
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_1_0(),
typeof(LegacyProjectEngineFactory_1_0).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_1_1(),
typeof(LegacyProjectEngineFactory_1_1).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_2_0(),
typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_2_1(),
typeof(LegacyProjectEngineFactory_2_1).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
};
FallbackFactory = new FallbackProjectEngineFactory();
}
private Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
@ -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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>());
}
@ -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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
Assert.Empty(engine.Engine.Features.OfType<DefaultTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
private class MyCoolNewFeature : IRazorEngineFeature

View File

@ -10,6 +10,8 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor.Test.Common\Microsoft.VisualStudio.Editor.Razor.Test.Common.csproj" />
</ItemGroup>

View File

@ -18,6 +18,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor\Microsoft.AspNetCore.Razor.csproj" />

View File

@ -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<RazorExtension>()));
CustomFactories = new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[]
{
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_2_0(),
typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
// 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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => 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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<IProjectEngineFactory, ProjectSnapshot, Task<TagHelperResolutionResult>> OnResolveOutOfProcess { get; set; }
public Func<ProjectSnapshot, Task<TagHelperResolutionResult>> OnResolveInProcess { get; set; }
protected override Task<TagHelperResolutionResult> ResolveTagHelpersOutOfProcessAsync(IProjectEngineFactory factory, ProjectSnapshot project)
{
Assert.NotNull(OnResolveOutOfProcess);
return OnResolveOutOfProcess(factory, project);
}
protected override Task<TagHelperResolutionResult> ResolveTagHelpersInProcessAsync(ProjectSnapshot project)
{
Assert.NotNull(OnResolveInProcess);
return OnResolveInProcess(project);
}
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
public TestProjectSnapshotManager(Workspace workspace)
: base(
Mock.Of<ForegroundDispatcher>(),
Mock.Of<ErrorReporter>(),
Mock.Of<ProjectSnapshotWorker>(),
Enumerable.Empty<ProjectSnapshotChangeTrigger>(),
workspace)
{
}
protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
}
}
}
}

View File

@ -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<ProjectSnapshotHandle>(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<ProjectSnapshotHandle>(json, Converters);
// Assert
Assert.Equal(snapshot.FilePath, obj.FilePath);
Assert.Null(obj.Configuration);
Assert.Null(obj.WorkspaceProjectId);
}
}
}

View File

@ -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<RazorConfiguration>(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);
}
}
}

View File

@ -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<RazorExtension>(json, Converters);
// Assert
Assert.Equal(extension.ExtensionName, obj.ExtensionName);
}
}
}

View File

@ -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;

View File

@ -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);