Merge pull request #2141 from aspnet/dev

Merging changes from dev
This commit is contained in:
Artak 2018-03-07 16:39:04 -08:00 committed by GitHub
commit 8a1d04e0bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
172 changed files with 4952 additions and 1129 deletions

View File

@ -1,17 +0,0 @@
init:
- git config --global core.autocrlf true
branches:
only:
- dev
- /^release\/.*$/
- /^(.*\/)?ci-.*$/
build_script:
- ps: .\run.ps1 default-build
clone_depth: 1
environment:
global:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: 1
test: 'off'
deploy: 'off'
os: Visual Studio 2017 Preview

View File

@ -1,8 +1,6 @@
Razor
=====
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/olbc8ur2jna0v27j/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/razor/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/Razor.svg?branch=dev)](https://travis-ci.org/aspnet/Razor)
The Razor syntax provides a fast, terse, clean and lightweight way to combine server code with HTML to create dynamic web content. This repo contains the parser and the C# code generator for the Razor syntax.

View File

@ -1,6 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27107.3000
VisualStudioVersion = 15.0.27130.2036
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C0D6505-79B3-49D0-B4C3-176F0F1836ED}"
ProjectSection(SolutionItems) = preProject
@ -92,9 +93,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Razo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Tools", "src\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj", "{3E7F2D49-3B45-45A8-9893-F73EC1EEBAAB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.NET.Sdk.Razor", "src\Microsoft.NET.Sdk.Razor\Microsoft.NET.Sdk.Razor.csproj", "{7D9ECCEE-71D1-4A42-ABEE-876AFA1B4FC9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Razor", "src\Microsoft.NET.Sdk.Razor\Microsoft.NET.Sdk.Razor.csproj", "{7D9ECCEE-71D1-4A42-ABEE-876AFA1B4FC9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Razor.Tools.Test", "test\Microsoft.AspNetCore.Razor.Tools.Test\Microsoft.AspNetCore.Razor.Tools.Test.csproj", "{6EA56B2B-89EC-4C38-A384-97D203375B06}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Tools.Test", "test\Microsoft.AspNetCore.Razor.Tools.Test\Microsoft.AspNetCore.Razor.Tools.Test.csproj", "{6EA56B2B-89EC-4C38-A384-97D203375B06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib", "test\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj", "{72E89155-86C7-454E-BDD9-39F497F2F61B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -398,6 +401,14 @@ Global
{6EA56B2B-89EC-4C38-A384-97D203375B06}.Release|Any CPU.Build.0 = Release|Any CPU
{6EA56B2B-89EC-4C38-A384-97D203375B06}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{6EA56B2B-89EC-4C38-A384-97D203375B06}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.Release|Any CPU.Build.0 = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -440,6 +451,7 @@ Global
{3E7F2D49-3B45-45A8-9893-F73EC1EEBAAB} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{7D9ECCEE-71D1-4A42-ABEE-876AFA1B4FC9} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{6EA56B2B-89EC-4C38-A384-97D203375B06} = {92463391-81BE-462B-AC3C-78C6C760741F}
{72E89155-86C7-454E-BDD9-39F497F2F61B} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}

View File

@ -2,7 +2,6 @@
// 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.IO;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
@ -21,33 +20,22 @@ namespace Microsoft.AspNetCore.Razor.Performance
}
var root = current;
var engine = RazorEngine.Create(b => { RazorExtensions.Register(b); });
var fileSystem = RazorProjectFileSystem.Create(root.FullName);
ProjectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, b => RazorExtensions.Register(b)); ;
DesignTimeTemplateEngine = new MvcRazorTemplateEngine(RazorEngine.CreateDesignTime(b => { RazorExtensions.Register(b); }), fileSystem);
RuntimeTemplateEngine = new MvcRazorTemplateEngine(RazorEngine.Create(b => { RazorExtensions.Register(b); }), fileSystem);
var codeDocument = RuntimeTemplateEngine.CreateCodeDocument(Path.Combine(root.FullName, "MSN.cshtml"));
Imports = codeDocument.Imports;
MSN = codeDocument.Source;
MSN = fileSystem.GetItem(Path.Combine(root.FullName, "MSN.cshtml"));
}
public RazorTemplateEngine DesignTimeTemplateEngine { get; }
public RazorProjectEngine ProjectEngine { get; }
public RazorTemplateEngine RuntimeTemplateEngine { get; }
public IReadOnlyList<RazorSourceDocument> Imports { get; }
public RazorSourceDocument MSN { get; }
public RazorProjectItem MSN { get; }
[Benchmark(Description = "Razor Design Time Code Generation of MSN.com")]
public void CodeGeneration_DesignTime_LargeStaticFile()
{
var codeDocument = RazorCodeDocument.Create(MSN, Imports);
var generated = DesignTimeTemplateEngine.GenerateCode(codeDocument);
var codeDocument = ProjectEngine.ProcessDesignTime(MSN);
var generated = codeDocument.GetCSharpDocument();
if (generated.Diagnostics.Count != 0)
{
@ -58,8 +46,8 @@ namespace Microsoft.AspNetCore.Razor.Performance
[Benchmark(Description = "Razor Runtime Code Generation of MSN.com")]
public void CodeGeneration_Runtime_LargeStaticFile()
{
var codeDocument = RazorCodeDocument.Create(MSN, Imports);
var generated = RuntimeTemplateEngine.GenerateCode(codeDocument);
var codeDocument = ProjectEngine.Process(MSN);
var generated = codeDocument.GetCSharpDocument();
if (generated.Diagnostics.Count != 0)
{

View File

@ -4,23 +4,23 @@
</PropertyGroup>
<PropertyGroup Label="Package Versions">
<BenchmarkDotNetPackageVersion>0.10.11</BenchmarkDotNetPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview2-15698</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview2-30077</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.1.0-preview2-30077</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview2-30077</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftBuildFrameworkPackageVersion>15.3.409</MicrosoftBuildFrameworkPackageVersion>
<MicrosoftBuildPackageVersion>15.3.409</MicrosoftBuildPackageVersion>
<MicrosoftBuildUtilitiesCorePackageVersion>15.3.409</MicrosoftBuildUtilitiesCorePackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview2-15726</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview2-30230</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.1.0-preview2-30230</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview2-30230</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftBuildFrameworkPackageVersion>15.6.82</MicrosoftBuildFrameworkPackageVersion>
<MicrosoftBuildPackageVersion>15.6.82</MicrosoftBuildPackageVersion>
<MicrosoftBuildUtilitiesCorePackageVersion>15.6.82</MicrosoftBuildUtilitiesCorePackageVersion>
<MicrosoftCodeAnalysisCommonPackageVersion>2.6.1</MicrosoftCodeAnalysisCommonPackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>2.6.1</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.1.0-preview2-30077</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.1.0-preview2-30077</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.1.0-preview2-30230</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.1.0-preview2-30230</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
<MicrosoftExtensionsDependencyModelPackageVersion>2.1.0-preview2-25711-01</MicrosoftExtensionsDependencyModelPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.1.0-preview2-30077</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview2-30077</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.1.0-preview2-30230</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview2-30230</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26130-04</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.3.0</MicrosoftNETTestSdkPackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26225-03</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
<MicrosoftVisualStudioComponentModelHostPackageVersion>15.0.26606</MicrosoftVisualStudioComponentModelHostPackageVersion>
<MicrosoftVisualStudioEditorPackageVersion>15.6.161-preview</MicrosoftVisualStudioEditorPackageVersion>
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>15.6.161-preview</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
@ -42,8 +42,8 @@
<NETStandardLibrary20PackageVersion>2.0.1</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion>
<StreamJsonRpcPackageVersion>1.1.92</StreamJsonRpcPackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0-preview2-26130-01</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemValueTuplePackageVersion>4.5.0-preview2-26130-01</SystemValueTuplePackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0-preview2-26224-02</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemValueTuplePackageVersion>4.5.0-preview2-26224-02</SystemValueTuplePackageVersion>
<VisualStudio_NewtonsoftJsonPackageVersion>9.0.1</VisualStudio_NewtonsoftJsonPackageVersion>
<VSIX_MicrosoftCodeAnalysisCommonPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCommonPackageVersion>
<VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>

View File

@ -1,10 +1,11 @@
<Project>
<Project>
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
<PropertyGroup Label="RestoreSources">
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
$(RestoreSources);
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
https://dotnet.myget.org/F/msbuild/api/v3/index.json;

View File

@ -1,2 +1,2 @@
version:2.1.0-preview2-15698
commithash:7216e5068cb1957e09d45fcbe58a744dd5c2de73
version:2.1.0-preview2-15726
commithash:599e691c41f502ed9e062b1822ce13b673fc916e

View File

@ -1,84 +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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
{
private const string ImportsFileName = "_ViewImports.cshtml";
public IReadOnlyList<RazorSourceDocument> GetImports(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var imports = new List<RazorSourceDocument>();
AddDefaultDirectivesImport(imports);
// We add hierarchical imports second so any default directive imports can be overridden.
AddHierarchicalImports(projectItem, imports);
return imports;
}
// Internal for testing
internal static void AddDefaultDirectivesImport(List<RazorSourceDocument> imports)
{
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream, Encoding.UTF8))
{
writer.WriteLine("@using System");
writer.WriteLine("@using System.Collections.Generic");
writer.WriteLine("@using System.Linq");
writer.WriteLine("@using System.Threading.Tasks");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
writer.Flush();
stream.Position = 0;
var defaultMvcImports = RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
imports.Add(defaultMvcImports);
}
}
// Internal for testing
internal void AddHierarchicalImports(RazorProjectItem projectItem, List<RazorSourceDocument> imports)
{
// We want items in descending order. FindHierarchicalItems returns items in ascending order.
var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
foreach (var importProjectItem in importProjectItems)
{
RazorSourceDocument importSourceDocument;
if (importProjectItem.Exists)
{
importSourceDocument = RazorSourceDocument.ReadFrom(importProjectItem);
}
else
{
// File doesn't exist on disk so just add a marker source document as an identifier for "there could be something here".
var sourceDocumentProperties = new RazorSourceDocumentProperties(importProjectItem.FilePath, importProjectItem.RelativePhysicalPath);
importSourceDocument = RazorSourceDocument.Create(string.Empty, sourceDocumentProperties);
}
imports.Add(importSourceDocument);
}
}
}
}

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
}
builder.AddDirective(Directive);
builder.Features.Add(new Pass(builder.Configuration.DesignTime));
builder.Features.Add(new Pass());
return builder;
}
@ -64,13 +64,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
private readonly bool _designTime;
public Pass(bool designTime)
{
_designTime = designTime;
}
// Runs after the @inherits directive
public override int Order => 5;
@ -79,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
var visitor = new Visitor();
var modelType = GetModelType(documentNode, visitor);
if (_designTime)
if (documentNode.Options.DesignTime)
{
// Alias the TModel token to a known type.
// This allows design time compilation to succeed for Razor files where the token isn't replaced.
@ -143,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
}
builder.AddDirective(Directive);
builder.Features.Add(new Pass(builder.DesignTime));
builder.Features.Add(new Pass());
return builder;
}
#endregion

View File

@ -0,0 +1,89 @@
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
internal class MvcImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
private const string ImportsFileName = "_ViewImports.cshtml";
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var imports = new List<RazorProjectItem>();
AddDefaultDirectivesImport(imports);
// We add hierarchical imports second so any default directive imports can be overridden.
AddHierarchicalImports(projectItem, imports);
return imports;
}
// Internal for testing
internal static void AddDefaultDirectivesImport(List<RazorProjectItem> imports)
{
imports.Add(DefaultDirectivesProjectItem.Instance);
}
// Internal for testing
internal void AddHierarchicalImports(RazorProjectItem projectItem, List<RazorProjectItem> imports)
{
// We want items in descending order. FindHierarchicalItems returns items in ascending order.
var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
imports.AddRange(importProjectItems);
}
private class DefaultDirectivesProjectItem : RazorProjectItem
{
private readonly byte[] _defaultImportBytes;
private DefaultDirectivesProjectItem()
{
var preamble = Encoding.UTF8.GetPreamble();
var content = @"
@using System
@using System.Collections.Generic
@using System.Linq
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Mvc
@using Microsoft.AspNetCore.Mvc.Rendering
@using Microsoft.AspNetCore.Mvc.ViewFeatures
@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html
@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json
@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component
@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url
@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider
@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
";
var contentBytes = Encoding.UTF8.GetBytes(content);
_defaultImportBytes = new byte[preamble.Length + contentBytes.Length];
preamble.CopyTo(_defaultImportBytes, 0);
contentBytes.CopyTo(_defaultImportBytes, preamble.Length);
}
public override string BasePath => null;
public override string FilePath => null;
public override string PhysicalPath => null;
public override bool Exists => true;
public static DefaultDirectivesProjectItem Instance { get; } = new DefaultDirectivesProjectItem();
public override Stream Read() => new MemoryStream(_defaultImportBytes);
}
}
}

View File

@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
throw new ArgumentNullException(nameof(builder));
}
EnsureDesignTime(builder);
InjectDirective.Register(builder);
ModelDirective.Register(builder);
@ -37,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
builder.Features.Add(new ModelExpressionPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());
builder.SetImportFeature(new DefaultMvcImportFeature());
builder.SetImportFeature(new MvcImportProjectFeature());
}
public static void RegisterViewComponentTagHelpers(RazorProjectEngineBuilder builder)
@ -47,22 +45,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
throw new ArgumentNullException(nameof(builder));
}
EnsureDesignTime(builder);
builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
}
private static void EnsureDesignTime(RazorProjectEngineBuilder builder)
{
if (builder.Configuration.DesignTime)
{
return;
}
throw new NotSupportedException(Resources.RuntimeCodeGenerationNotSupported);
}
#region Obsolete
public static void Register(IRazorEngineBuilder builder)
{

View File

@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (documentNode.Options.DesignTime)
{
return;
}
var @namespace = documentNode.FindPrimaryNamespace();
if (@namespace == null || string.IsNullOrEmpty(@namespace.Content))
{

View File

@ -1,86 +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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
{
private const string ImportsFileName = "_ViewImports.cshtml";
public IReadOnlyList<RazorSourceDocument> GetImports(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var imports = new List<RazorSourceDocument>();
AddDefaultDirectivesImport(imports);
// We add hierarchical imports second so any default directive imports can be overridden.
AddHierarchicalImports(projectItem, imports);
return imports;
}
// Internal for testing
internal static void AddDefaultDirectivesImport(List<RazorSourceDocument> imports)
{
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream, Encoding.UTF8))
{
writer.WriteLine("@using System");
writer.WriteLine("@using System.Collections.Generic");
writer.WriteLine("@using System.Linq");
writer.WriteLine("@using System.Threading.Tasks");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url");
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor");
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor");
writer.Flush();
stream.Position = 0;
var defaultMvcImports = RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
imports.Add(defaultMvcImports);
}
}
// Internal for testing
internal void AddHierarchicalImports(RazorProjectItem projectItem, List<RazorSourceDocument> imports)
{
// We want items in descending order. FindHierarchicalItems returns items in ascending order.
var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
foreach (var importProjectItem in importProjectItems)
{
RazorSourceDocument importSourceDocument;
if (importProjectItem.Exists)
{
importSourceDocument = RazorSourceDocument.ReadFrom(importProjectItem);
}
else
{
// File doesn't exist on disk so just add a marker source document as an identifier for "there could be something here".
var sourceDocumentProperties = new RazorSourceDocumentProperties(importProjectItem.FilePath, importProjectItem.RelativePhysicalPath);
importSourceDocument = RazorSourceDocument.Create(string.Empty, sourceDocumentProperties);
}
imports.Add(importSourceDocument);
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
internal class ExtensionInitializer : RazorExtensionInitializer
{
public override void Initialize(RazorProjectEngineBuilder builder)
{
RazorExtensions.Register(builder);
}
}
}

View File

@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (documentNode.Options.DesignTime)
{
return;
}
var walker = new Visitor();
walker.VisitDocument(documentNode);

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
builder.AddDirective(Directive);
builder.Features.Add(new Pass(builder.Configuration.DesignTime));
builder.Features.Add(new Pass());
return builder;
}
@ -71,13 +71,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
private readonly bool _designTime;
public Pass(bool designTime)
{
_designTime = designTime;
}
// Runs after the @inherits directive
public override int Order => 5;
@ -86,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var visitor = new Visitor();
var modelType = GetModelType(documentNode, visitor);
if (_designTime)
if (documentNode.Options.DesignTime)
{
// Alias the TModel token to a known type.
// This allows design time compilation to succeed for Razor files where the token isn't replaced.
@ -150,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
builder.AddDirective(Directive);
builder.Features.Add(new Pass(builder.DesignTime));
builder.Features.Add(new Pass());
return builder;
}
#endregion

View File

@ -0,0 +1,91 @@
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
internal class MvcImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
private const string ImportsFileName = "_ViewImports.cshtml";
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var imports = new List<RazorProjectItem>();
AddDefaultDirectivesImport(imports);
// We add hierarchical imports second so any default directive imports can be overridden.
AddHierarchicalImports(projectItem, imports);
return imports;
}
// Internal for testing
internal static void AddDefaultDirectivesImport(List<RazorProjectItem> imports)
{
imports.Add(DefaultDirectivesProjectItem.Instance);
}
// Internal for testing
internal void AddHierarchicalImports(RazorProjectItem projectItem, List<RazorProjectItem> imports)
{
// We want items in descending order. FindHierarchicalItems returns items in ascending order.
var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
imports.AddRange(importProjectItems);
}
private class DefaultDirectivesProjectItem : RazorProjectItem
{
private readonly byte[] _defaultImportBytes;
private DefaultDirectivesProjectItem()
{
var preamble = Encoding.UTF8.GetPreamble();
var content = @"
@using System
@using System.Collections.Generic
@using System.Linq
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Mvc
@using Microsoft.AspNetCore.Mvc.Rendering
@using Microsoft.AspNetCore.Mvc.ViewFeatures
@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html
@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json
@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component
@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url
@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider
@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
";
var contentBytes = Encoding.UTF8.GetBytes(content);
_defaultImportBytes = new byte[preamble.Length + contentBytes.Length];
preamble.CopyTo(_defaultImportBytes, 0);
contentBytes.CopyTo(_defaultImportBytes, preamble.Length);
}
public override string BasePath => null;
public override string FilePath => null;
public override string PhysicalPath => null;
public override bool Exists => true;
public static DefaultDirectivesProjectItem Instance { get; } = new DefaultDirectivesProjectItem();
public override Stream Read() => new MemoryStream(_defaultImportBytes);
}
}
}

View File

@ -2,6 +2,11 @@
// 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;
using Microsoft.AspNetCore.Razor.Language;
[assembly: ProvideRazorExtensionInitializer("MVC-2.0", typeof(ExtensionInitializer))]
[assembly: ProvideRazorExtensionInitializer("MVC-2.1", typeof(ExtensionInitializer))]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -25,6 +25,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()
{
@ -36,14 +38,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
builder.Features.Add(new ViewComponentTagHelperPass());
builder.Features.Add(new RazorPageDocumentClassifierPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());
builder.Features.Add(new AssemblyAttributeInjectionPass());
builder.Features.Add(new InstrumentationPass());
if (!builder.Configuration.DesignTime)
{
builder.Features.Add(new AssemblyAttributeInjectionPass());
builder.Features.Add(new InstrumentationPass());
}
builder.SetImportFeature(new DefaultMvcImportFeature());
builder.SetImportFeature(new MvcImportProjectFeature());
}
#region Obsolete

View File

@ -10,6 +10,9 @@
Set the primary configuration supported by this pacakge as the default configuration for Razor.
-->
<RazorDefaultConfiguration Condition="'$(RazorDefaultConfiguration)'==''">MVC-2.1</RazorDefaultConfiguration>
<!-- Override for testing. This path is only correct inside a nuget package. -->
<_MvcExtensionAssemblyPath Condition="'$(_MvcExtensionAssemblyPath)'==''">$(MSBuildThisFileDirectory)..\..\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll</_MvcExtensionAssemblyPath>
</PropertyGroup>
<ItemGroup>
@ -26,7 +29,7 @@
<ItemGroup>
<RazorExtension Include="MVC-2.1">
<AssemblyName>Microsoft.AspNetCore.Mvc.Razor.Extensions</AssemblyName>
<AssemblyFilePath>$(MSBuildThisFileDirectory)..\..\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll</AssemblyFilePath>
<AssemblyFilePath>$(_MvcExtensionAssemblyPath)</AssemblyFilePath>
</RazorExtension>
</ItemGroup>
</Project>

View File

@ -5,5 +5,10 @@
MVC will generally want to add support for runtime compilation, but only for applications.
-->
<GenerateRazorAssemblyInfo Condition="'$(GenerateRazorAssemblyInfo)'=='' and '$(OutputType)'=='Exe'">true</GenerateRazorAssemblyInfo>
<!--
Use the suffix .Views when producing compiled view assemblies. This matches the requirements for Mvc's ViewsFeatureProvider.
-->
<RazorTargetNameSuffix Condition="'$(RazorTargetNameSuffix)'==''">.Views</RazorTargetNameSuffix>
</PropertyGroup>
</Project>
</Project>

View File

@ -60,11 +60,11 @@
-->
<Touch
Files="$(_RazorTagHelperInputCache)"
AlwaysCreate="true">
<Output
TaskParameter="TouchedFiles"
ItemName="FileWrites" />
</Touch>
AlwaysCreate="true" />
<ItemGroup>
<FileWrites Include="$(_RazorTagHelperInputCache)" />
</ItemGroup>
<RazorTagHelper
Debug="$(_RazorDebugTagHelperTask)"
@ -73,6 +73,9 @@
UseServer="$(UseRazorBuildServer)"
ForceServer="$(_RazorForceBuildServer)"
PipeName="$(_RazorBuildServerPipeName)"
Version="$(RazorLangVersion)"
Configuration="@(ResolvedRazorConfiguration)"
Extensions="@(ResolvedRazorExtension)"
Assemblies="@(RazorReferencePath)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorTagHelperOutputCache)">
@ -121,6 +124,9 @@
UseServer="$(UseRazorBuildServer)"
ForceServer="$(_RazorForceBuildServer)"
PipeName="$(_RazorBuildServerPipeName)"
Version="$(RazorLangVersion)"
Configuration="@(ResolvedRazorConfiguration)"
Extensions="@(ResolvedRazorExtension)"
Sources="@(RazorGenerateWithTargetPath)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorTagHelperOutputCache)" />

View File

@ -0,0 +1,55 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
public class AssemblyExtension : RazorExtension
{
public AssemblyExtension(string extensionName, Assembly assembly)
{
if (extensionName == null)
{
throw new ArgumentNullException(nameof(extensionName));
}
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
ExtensionName = extensionName;
Assembly = assembly;
}
public override string ExtensionName { get; }
public Assembly Assembly { get; }
internal RazorExtensionInitializer CreateInitializer()
{
// It's not an error to have an assembly with no initializers. This is useful to specify a dependency
// that doesn't really provide any Razor configuration.
var attributes = Assembly.GetCustomAttributes<ProvideRazorExtensionInitializerAttribute>();
foreach (var attribute in attributes)
{
// Using extension names and requiring them to line up allows a single assembly to ship multiple
// extensions/initializers for different configurations.
if (!string.Equals(attribute.ExtensionName, ExtensionName, StringComparison.Ordinal))
{
continue;
}
// There's no real protection/exception handling here because this set isn't really user-extensible
// right now. This would be a great place to add some additional diagnostics and hardening in the
// future.
var initializer = (RazorExtensionInitializer)Activator.CreateInstance(attribute.InitializerType);
return initializer;
}
return null;
}
}
}

View File

@ -540,7 +540,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public CSharpCodeWritingScope(CodeWriter writer, int tabSize = 4, bool autoSpace = true)
{
_writer = writer;
_autoSpace = true;
_autoSpace = autoSpace;
_tabSize = tabSize;
_startIndent = -1; // Set in WriteStartScope

View File

@ -159,11 +159,35 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
Context.CodeWriter.Write(" ");
}
Context.CodeWriter
.Write(node.ReturnType)
.Write(" ")
.Write(node.MethodName)
.WriteLine("()");
Context.CodeWriter.Write(node.ReturnType);
Context.CodeWriter.Write(" ");
Context.CodeWriter.Write(node.MethodName);
Context.CodeWriter.Write("(");
for (var i = 0; i < node.Parameters.Count; i++)
{
var parameter = node.Parameters[i];
for (var j = 0; j < parameter.Modifiers.Count; j++)
{
Context.CodeWriter.Write(parameter.Modifiers[j]);
Context.CodeWriter.Write(" ");
}
Context.CodeWriter.Write(parameter.TypeName);
Context.CodeWriter.Write(" ");
Context.CodeWriter.Write(parameter.ParameterName);
if (i < node.Parameters.Count - 1)
{
Context.CodeWriter.Write(", ");
}
}
Context.CodeWriter.Write(")");
Context.CodeWriter.WriteLine();
using (Context.CodeWriter.BuildScope())
{

View File

@ -2,12 +2,30 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public abstract class DocumentWriter
{
public static DocumentWriter CreateDefault(CodeTarget codeTarget, RazorCodeGenerationOptions options)
{
if (codeTarget == null)
{
throw new ArgumentNullException(nameof(codeTarget));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return new DefaultDocumentWriter(codeTarget, options);
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method was intended to be static, use CreateDefault instead.")]
public DocumentWriter Create(CodeTarget codeTarget, RazorCodeGenerationOptions options)
{
if (codeTarget == null)

View File

@ -6,8 +6,8 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
internal class DefaultImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
public IReadOnlyList<RazorSourceDocument> GetImports(RazorProjectItem projectItem) => Array.Empty<RazorSourceDocument>();
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem) => Array.Empty<RazorProjectItem>();
}
}

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new InvalidOperationException(message);
}
var writer = new DefaultDocumentWriter(documentNode.Target, documentNode.Options);
var writer = DocumentWriter.CreateDefault(documentNode.Target, documentNode.Options);
var cSharpDocument = writer.WriteDocument(codeDocument, documentNode);
codeDocument.SetCSharpDocument(cSharpDocument);
}

View File

@ -1,16 +1,30 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorCodeGenerationOptionsBuilder : RazorCodeGenerationOptionsBuilder
{
public DefaultRazorCodeGenerationOptionsBuilder(bool designTime)
private bool _designTime;
public DefaultRazorCodeGenerationOptionsBuilder(RazorConfiguration configuration)
{
DesignTime = designTime;
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
}
public override bool DesignTime { get; }
public DefaultRazorCodeGenerationOptionsBuilder(bool designTime)
{
_designTime = designTime;
}
public override RazorConfiguration Configuration { get; }
public override bool DesignTime => _designTime;
public override int IndentSize { get; set; } = 4;
@ -22,5 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Language
{
return new DefaultRazorCodeGenerationOptions(IndentWithTabs, IndentSize, DesignTime, SuppressChecksum, SuppressMetadataAttributes);
}
public override void SetDesignTime(bool designTime)
{
_designTime = designTime;
}
}
}

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.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorCodeGenerationOptionsFactoryProjectFeature : RazorProjectEngineFeatureBase, IRazorCodeGenerationOptionsFactoryProjectFeature
{
private IConfigureRazorCodeGenerationOptionsFeature[] _configureOptions;
protected override void OnInitialized()
{
_configureOptions = ProjectEngine.EngineFeatures.OfType<IConfigureRazorCodeGenerationOptionsFeature>().ToArray();
}
public RazorCodeGenerationOptions Create(Action<RazorCodeGenerationOptionsBuilder> configure)
{
var builder = new DefaultRazorCodeGenerationOptionsBuilder(ProjectEngine.Configuration);
configure?.Invoke(builder);
for (var i = 0; i < _configureOptions.Length; i++)
{
_configureOptions[i].Configure(builder);
}
var options = builder.Build();
return options;
}
}
}

View File

@ -5,7 +5,9 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
#pragma warning disable CS0618 // Type or member is obsolete
internal class DefaultRazorCodeGenerationOptionsFeature : RazorEngineFeatureBase, IRazorCodeGenerationOptionsFeature
#pragma warning restore CS0618 // Type or member is obsolete
{
private readonly bool _designTime;
private IConfigureRazorCodeGenerationOptionsFeature[] _configureOptions;

View File

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Razor.Language.Legacy;
namespace Microsoft.AspNetCore.Razor.Language
{
#pragma warning disable CS0618 // Type or member is obsolete
internal class DefaultRazorIntermediateNodeLoweringPhase : RazorEnginePhaseBase, IRazorIntermediateNodeLoweringPhase
{
private IRazorCodeGenerationOptionsFeature _optionsFeature;
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
document.Options = _optionsFeature.GetOptions();
document.Options = codeDocument.GetCodeGenerationOptions() ?? _optionsFeature.GetOptions();
var namespaces = new Dictionary<string, SourceSpan?>(StringComparer.Ordinal);
@ -785,4 +786,5 @@ namespace Microsoft.AspNetCore.Razor.Language
private static bool IsMalformed(List<RazorDiagnostic> diagnostics)
=> diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
}
#pragma warning restore CS0618 // Type or member is obsolete
}

View File

@ -9,23 +9,43 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorParserOptionsBuilder : RazorParserOptionsBuilder
{
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version)
private bool _designTime;
public DefaultRazorParserOptionsBuilder(RazorConfiguration configuration)
{
DesignTime = designTime;
Version = version;
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
Configuration = configuration;
LanguageVersion = configuration.LanguageVersion;
}
public override bool DesignTime { get; }
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version)
{
_designTime = designTime;
LanguageVersion = version;
}
public override RazorConfiguration Configuration { get; }
public override bool DesignTime => _designTime;
public override ICollection<DirectiveDescriptor> Directives { get; } = new List<DirectiveDescriptor>();
public override bool ParseLeadingDirectives { get; set; }
public override RazorLanguageVersion Version { get; }
public override RazorLanguageVersion LanguageVersion { get; }
public override RazorParserOptions Build()
{
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, Version);
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, LanguageVersion);
}
public override void SetDesignTime(bool designTime)
{
_designTime = designTime;
}
}
}

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.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorParserOptionsFactoryProjectFeature : RazorProjectEngineFeatureBase, IRazorParserOptionsFactoryProjectFeature
{
private IConfigureRazorParserOptionsFeature[] _configureOptions;
protected override void OnInitialized()
{
_configureOptions = ProjectEngine.EngineFeatures.OfType<IConfigureRazorParserOptionsFeature>().ToArray();
}
public RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure)
{
var builder = new DefaultRazorParserOptionsBuilder(ProjectEngine.Configuration);
configure?.Invoke(builder);
for (var i = 0; i < _configureOptions.Length; i++)
{
_configureOptions[i].Configure(builder);
}
var options = builder.Build();
return options;
}
}
}

View File

@ -5,7 +5,9 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
#pragma warning disable CS0618 // Type or member is obsolete
internal class DefaultRazorParserOptionsFeature : RazorEngineFeatureBase, IRazorParserOptionsFeature
#pragma warning restore CS0618 // Type or member is obsolete
{
private readonly bool _designTime;
private readonly RazorLanguageVersion _version;

View File

@ -3,6 +3,7 @@
namespace Microsoft.AspNetCore.Razor.Language
{
#pragma warning disable CS0618 // Type or member is obsolete
internal class DefaultRazorParsingPhase : RazorEnginePhaseBase, IRazorParsingPhase
{
private IRazorParserOptionsFeature _optionsFeature;
@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var options = _optionsFeature.GetOptions();
var options = codeDocument.GetParserOptions() ??_optionsFeature.GetOptions();
var syntaxTree = RazorSyntaxTree.Parse(codeDocument.Source, options);
codeDocument.SetSyntaxTree(syntaxTree);
@ -26,4 +27,5 @@ namespace Microsoft.AspNetCore.Razor.Language
codeDocument.SetImportSyntaxTrees(importSyntaxTrees);
}
}
#pragma warning restore CS0618 // Type or member is obsolete
}

View File

@ -10,10 +10,16 @@ namespace Microsoft.AspNetCore.Razor.Language
internal class DefaultRazorProjectEngine : RazorProjectEngine
{
public DefaultRazorProjectEngine(
RazorConfiguration configuration,
RazorEngine engine,
RazorProjectFileSystem fileSystem,
IReadOnlyList<IRazorProjectEngineFeature> features)
IReadOnlyList<IRazorProjectEngineFeature> projectFeatures)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (engine == null)
{
throw new ArgumentNullException(nameof(engine));
@ -24,48 +30,81 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(fileSystem));
}
if (features == null)
if (projectFeatures == null)
{
throw new ArgumentNullException(nameof(features));
throw new ArgumentNullException(nameof(projectFeatures));
}
Configuration = configuration;
Engine = engine;
FileSystem = fileSystem;
Features = features;
ProjectFeatures = projectFeatures;
for (var i = 0; i < features.Count; i++)
for (var i = 0; i < projectFeatures.Count; i++)
{
features[i].ProjectEngine = this;
projectFeatures[i].ProjectEngine = this;
}
}
public override RazorConfiguration Configuration { get; }
public override RazorProjectFileSystem FileSystem { get; }
public override RazorEngine Engine { get; }
public override IReadOnlyList<IRazorProjectEngineFeature> Features { get; }
public override IReadOnlyList<IRazorProjectEngineFeature> ProjectFeatures { get; }
public override RazorCodeDocument Process(RazorProjectItem projectItem)
protected override RazorCodeDocument CreateCodeDocumentCore(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var importFeature = GetRequiredFeature<IRazorImportFeature>();
var imports = importFeature.GetImports(projectItem);
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var codeDocument = RazorCodeDocument.Create(sourceDocument, imports);
var importFeature = GetRequiredFeature<IImportProjectFeature>();
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems);
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureCodeGenerationOptions);
return RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
}
protected override RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var importFeature = GetRequiredFeature<IImportProjectFeature>();
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems);
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeCodeGenerationOptions);
return RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
}
protected override void ProcessCore(RazorCodeDocument codeDocument)
{
if (codeDocument == null)
{
throw new ArgumentNullException(nameof(codeDocument));
}
Engine.Process(codeDocument);
return codeDocument;
}
private TFeature GetRequiredFeature<TFeature>() where TFeature : IRazorProjectEngineFeature
{
var feature = Features.OfType<TFeature>().FirstOrDefault();
var feature = ProjectFeatures.OfType<TFeature>().FirstOrDefault();
if (feature == null)
{
throw new InvalidOperationException(
@ -76,5 +115,43 @@ namespace Microsoft.AspNetCore.Razor.Language
return feature;
}
private void ConfigureParserOptions(RazorParserOptionsBuilder builder)
{
}
private void ConfigureDesignTimeParserOptions(RazorParserOptionsBuilder builder)
{
builder.SetDesignTime(true);
}
private void ConfigureCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder)
{
}
private void ConfigureDesignTimeCodeGenerationOptions(RazorCodeGenerationOptionsBuilder builder)
{
builder.SetDesignTime(true);
builder.SuppressChecksum = true;
builder.SuppressMetadataAttributes = true;
}
// Internal for testing
internal static IReadOnlyList<RazorSourceDocument> GetImportSourceDocuments(IReadOnlyList<RazorProjectItem> importItems)
{
var imports = new List<RazorSourceDocument>();
for (var i = 0; i < importItems.Count; i++)
{
var importItem = importItems[i];
if (importItem.Exists)
{
var sourceDocument = RazorSourceDocument.ReadFrom(importItem);
imports.Add(sourceDocument);
}
}
return imports;
}
}
}

View File

@ -22,29 +22,19 @@ namespace Microsoft.AspNetCore.Razor.Language
Phases = new List<IRazorEnginePhase>();
}
public override RazorConfiguration Configuration { get; }
public override RazorProjectFileSystem FileSystem { get; }
public override ICollection<IRazorFeature> Features { get; }
public override IList<IRazorEnginePhase> Phases { get; }
public override RazorConfiguration Configuration { get; }
public override RazorProjectEngine Build()
{
RazorEngine engine = null;
if (Configuration.DesignTime)
{
engine = RazorEngine.CreateDesignTimeEmpty(ConfigureRazorEngine);
}
else
{
engine = RazorEngine.CreateEmpty(ConfigureRazorEngine);
}
var projectEngineFeatures = Features.OfType<IRazorProjectEngineFeature>().ToArray();
var projectEngine = new DefaultRazorProjectEngine(engine, FileSystem, projectEngineFeatures);
var engine = RazorEngine.CreateEmpty(ConfigureRazorEngine);
var projectFeatures = Features.OfType<IRazorProjectEngineFeature>().ToArray();
var projectEngine = new DefaultRazorProjectEngine(Configuration, engine, FileSystem, projectFeatures);
return projectEngine;
}

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.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class EmptyProjectFileSystem : RazorProjectFileSystem
{
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
NormalizeAndEnsureValidPath(basePath);
return Enumerable.Empty<RazorProjectItem>();
}
public override RazorProjectItem GetItem(string path)
{
NormalizeAndEnsureValidPath(path);
return new NotFoundProjectItem(string.Empty, path);
}
}
}

View File

@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
private static readonly string[] PrivateModifiers = new string[] { "private" };
public bool DesignTime { get; set; }
public string RunnerVariableName { get; set; } = "__tagHelperRunner";
public string StringValueBufferVariableName { get; set; } = "__tagHelperStringValueBuffer";
@ -82,7 +80,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
throw new InvalidOperationException(message);
}
if (DesignTime)
if (context.Options.DesignTime)
{
context.RenderChildren(node);
}
@ -136,7 +134,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
.Write(CreateTagHelperMethodName)
.WriteLine("<global::" + node.TypeName + ">();");
if (!DesignTime)
if (!context.Options.DesignTime)
{
context.CodeWriter.WriteInstanceMethodInvocation(
ExecutionContextVariableName,
@ -153,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
throw new InvalidOperationException(message);
}
if (!DesignTime)
if (!context.Options.DesignTime)
{
context.CodeWriter
.Write("await ")
@ -200,7 +198,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
throw new InvalidOperationException(message);
}
if (DesignTime)
if (context.Options.DesignTime)
{
context.RenderChildren(node);
}
@ -284,7 +282,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
throw new InvalidOperationException(message);
}
if (!DesignTime)
if (!context.Options.DesignTime)
{
// Ensure that the property we're trying to set has initialized its dictionary bound properties.
if (node.IsIndexerNameMatch &&
@ -338,7 +336,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
// If we get there, this is the first time seeing this property so we need to evaluate the expression.
if (node.BoundAttribute.ExpectsStringValue(node.AttributeName))
{
if (DesignTime)
if (context.Options.DesignTime)
{
context.RenderChildren(node);
@ -370,7 +368,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
else
{
if (DesignTime)
if (context.Options.DesignTime)
{
var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode;
var valueStart = firstMappedChild?.Source;
@ -456,7 +454,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
}
if (!DesignTime)
if (!context.Options.DesignTime)
{
// We need to inform the context of the attribute value.
context.CodeWriter
@ -474,7 +472,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperRuntime(CodeRenderingContext context, DefaultTagHelperRuntimeIntermediateNode node)
{
if (!DesignTime)
if (!context.Options.DesignTime)
{
context.CodeWriter.WriteLine("#line hidden");
@ -566,7 +564,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
else if (node is IntermediateToken token)
{
if (DesignTime && node.Source != null)
if (context.Options.DesignTime && node.Source != null)
{
context.AddSourceMappingFor(node);
}

View File

@ -15,6 +15,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
// Only supports design time. This pass rewrites directives so they will have the right design time
// behavior and would break things if it ran for runtime.
if (!documentNode.Options.DesignTime)
{
return;
}
var walker = new DesignTimeHelperWalker();
walker.VisitDocument(documentNode);
}

View File

@ -15,6 +15,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
// There's no value in executing this pass at design time, it just prevents some allocations.
if (documentNode.Options.DesignTime)
{
return;
}
var walker = new PreallocatedTagHelperWalker();
walker.VisitDocument(documentNode);
}

View File

@ -5,8 +5,8 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
public interface IRazorImportFeature : IRazorProjectEngineFeature
public interface IImportProjectFeature : IRazorProjectEngineFeature
{
IReadOnlyList<RazorSourceDocument> GetImports(RazorProjectItem projectItem);
IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem);
}
}

View File

@ -0,0 +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;
namespace Microsoft.AspNetCore.Razor.Language
{
internal interface IRazorCodeGenerationOptionsFactoryProjectFeature : IRazorProjectEngineFeature
{
RazorCodeGenerationOptions Create(Action<RazorCodeGenerationOptionsBuilder> configure);
}
}

View File

@ -1,8 +1,11 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
[Obsolete("In Razor 2.1 and newer, use RazorCodeDocument.GetCodeGenerationOptions().")]
public interface IRazorCodeGenerationOptionsFeature : IRazorEngineFeature
{
RazorCodeGenerationOptions GetOptions();

View File

@ -0,0 +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;
namespace Microsoft.AspNetCore.Razor.Language
{
internal interface IRazorParserOptionsFactoryProjectFeature : IRazorProjectEngineFeature
{
RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure);
}
}

View File

@ -1,8 +1,11 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
[Obsolete("In Razor 2.1 and newer, use RazorCodeDocument.GetParserOptions().")]
public interface IRazorParserOptionsFeature : IRazorEngineFeature
{
RazorParserOptions GetOptions();

View File

@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public string MethodName { get; set; }
public IList<MethodParameter> Parameters { get; } = new List<MethodParameter>();
public string ReturnType { get; set; }
public override void Accept(IntermediateNodeVisitor visitor)

View File

@ -0,0 +1,16 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class MethodParameter
{
public IList<string> Modifiers { get; } = new List<string>();
public string TypeName { get; set; }
public string ParameterName { get; set; }
}
}

View File

@ -2,6 +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.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.GenerateTool, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

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;
namespace Microsoft.AspNetCore.Razor.Language
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public class ProvideRazorExtensionInitializerAttribute : Attribute
{
public ProvideRazorExtensionInitializerAttribute(string extensionName, Type initializerType)
{
if (extensionName == null)
{
throw new ArgumentNullException(nameof(extensionName));
}
if (initializerType == null)
{
throw new ArgumentNullException(nameof(initializerType));
}
ExtensionName = extensionName;
InitializerType = initializerType;
}
public string ExtensionName { get; }
public Type InitializerType { get; }
}
}

View File

@ -30,6 +30,23 @@ namespace Microsoft.AspNetCore.Razor.Language
return new DefaultRazorCodeDocument(source, imports);
}
public static RazorCodeDocument Create(
RazorSourceDocument source,
IEnumerable<RazorSourceDocument> imports,
RazorParserOptions parserOptions,
RazorCodeGenerationOptions codeGenerationOptions)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = new DefaultRazorCodeDocument(source, imports);
codeDocument.SetParserOptions(parserOptions);
codeDocument.SetCodeGenerationOptions(codeGenerationOptions);
return codeDocument;
}
public abstract IReadOnlyList<RazorSourceDocument> Imports { get; }
public abstract ItemCollection Items { get; }

View File

@ -109,6 +109,46 @@ namespace Microsoft.AspNetCore.Razor.Language
document.Items[typeof(RazorCSharpDocument)] = csharp;
}
public static RazorParserOptions GetParserOptions(this RazorCodeDocument document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return (RazorParserOptions)document.Items[typeof(RazorParserOptions)];
}
public static void SetParserOptions(this RazorCodeDocument document, RazorParserOptions parserOptions)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
document.Items[typeof(RazorParserOptions)] = parserOptions;
}
public static RazorCodeGenerationOptions GetCodeGenerationOptions(this RazorCodeDocument document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return (RazorCodeGenerationOptions)document.Items[typeof(RazorCodeGenerationOptions)];
}
public static void SetCodeGenerationOptions(this RazorCodeDocument document, RazorCodeGenerationOptions codeGenerationOptions)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
document.Items[typeof(RazorCodeGenerationOptions)] = codeGenerationOptions;
}
private class ImportSyntaxTreesHolder
{
public ImportSyntaxTreesHolder(IReadOnlyList<RazorSyntaxTree> syntaxTrees)

View File

@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorCodeGenerationOptionsBuilder
{
public virtual RazorConfiguration Configuration => null;
public abstract bool DesignTime { get; }
public abstract int IndentSize { get; set; }
@ -41,5 +43,9 @@ namespace Microsoft.AspNetCore.Razor.Language
public virtual bool SuppressMetadataAttributes { get; set; }
public abstract RazorCodeGenerationOptions Build();
public virtual void SetDesignTime(bool designTime)
{
}
}
}

View File

@ -12,22 +12,12 @@ namespace Microsoft.AspNetCore.Razor.Language
public static readonly RazorConfiguration Default = new RazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty<RazorExtension>(),
designTime: false);
// This is used only in some back-compat scenarios. We don't expose it because there's no
// use case for anyone else to use it.
internal static readonly RazorConfiguration DefaultDesignTime = new RazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty<RazorExtension>(),
designTime: true);
Array.Empty<RazorExtension>());
public RazorConfiguration(
RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable<RazorExtension> extensions,
bool designTime)
IEnumerable<RazorExtension> extensions)
{
if (languageVersion == null)
{
@ -47,7 +37,6 @@ namespace Microsoft.AspNetCore.Razor.Language
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions.ToArray();
DesignTime = designTime;
}
public string ConfigurationName { get; }
@ -55,7 +44,5 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyList<RazorExtension> Extensions { get; }
public RazorLanguageVersion LanguageVersion { get; }
public bool DesignTime { get; }
}
}

View File

@ -17,27 +17,27 @@ namespace Microsoft.AspNetCore.Razor.Language
return Create(configure: null);
}
public static RazorEngine Create(Action<IRazorEngineBuilder> configure) => CreateCore(RazorConfiguration.Default, configure);
public static RazorEngine Create(Action<IRazorEngineBuilder> configure) => CreateCore(RazorConfiguration.Default, false, configure);
public static RazorEngine CreateDesignTime()
{
return CreateDesignTime(configure: null);
}
public static RazorEngine CreateDesignTime(Action<IRazorEngineBuilder> configure) => CreateCore(RazorConfiguration.DefaultDesignTime, configure);
public static RazorEngine CreateDesignTime(Action<IRazorEngineBuilder> configure) => CreateCore(RazorConfiguration.Default, true, configure);
// Internal since RazorEngine APIs are going to be obsolete.
internal static RazorEngine CreateCore(RazorConfiguration configuration, Action<IRazorEngineBuilder> configure)
internal static RazorEngine CreateCore(RazorConfiguration configuration, bool designTime, Action<IRazorEngineBuilder> configure)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var builder = new DefaultRazorEngineBuilder(configuration.DesignTime);
var builder = new DefaultRazorEngineBuilder(designTime);
AddDefaults(builder);
if (configuration.DesignTime)
if (designTime)
{
AddDefaultDesignTimeFeatures(configuration, builder.Features);
}
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var targetExtension = features.OfType<IRazorTargetExtensionFeature>().FirstOrDefault();
Debug.Assert(targetExtension != null);
targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = false });
targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension());
targetExtension.TargetExtensions.Add(new PreallocatedAttributeTargetExtension());
}
@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var targetExtension = features.OfType<IRazorTargetExtensionFeature>().FirstOrDefault();
Debug.Assert(targetExtension != null);
targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension() { DesignTime = true });
targetExtension.TargetExtensions.Add(new DefaultTagHelperTargetExtension());
targetExtension.TargetExtensions.Add(new DesignTimeDirectiveTargetExtension());
}

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.AspNetCore.Razor.Language
{
public abstract class RazorExtensionInitializer
{
public abstract void Initialize(RazorProjectEngineBuilder builder);
}
}

View File

@ -1,21 +1,26 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorParserOptionsBuilder
{
public virtual RazorConfiguration Configuration => null;
public abstract bool DesignTime { get; }
public abstract ICollection<DirectiveDescriptor> Directives { get; }
public abstract bool ParseLeadingDirectives { get; set; }
public virtual RazorLanguageVersion Version { get; }
public virtual RazorLanguageVersion LanguageVersion { get; }
public abstract RazorParserOptions Build();
public virtual void SetDesignTime(bool designTime)
{
}
}
}

View File

@ -3,26 +3,61 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorProjectEngine
{
public abstract RazorConfiguration Configuration { get; }
public abstract RazorProjectFileSystem FileSystem { get; }
public abstract RazorEngine Engine { get; }
public abstract IReadOnlyList<IRazorProjectEngineFeature> Features { get; }
public IReadOnlyList<IRazorEngineFeature> EngineFeatures => Engine.Features;
public abstract RazorCodeDocument Process(RazorProjectItem projectItem);
public IReadOnlyList<IRazorEnginePhase> Phases => Engine.Phases;
public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem) => Create(fileSystem, configure: null);
public abstract IReadOnlyList<IRazorProjectEngineFeature> ProjectFeatures { get; }
public static RazorProjectEngine Create(RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure) => Create(fileSystem, RazorConfiguration.Default, configure);
public virtual RazorCodeDocument Process(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var codeDocument = CreateCodeDocumentCore(projectItem);
ProcessCore(codeDocument);
return codeDocument;
}
public virtual RazorCodeDocument ProcessDesignTime(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var codeDocument = CreateCodeDocumentDesignTimeCore(projectItem);
ProcessCore(codeDocument);
return codeDocument;
}
protected abstract RazorCodeDocument CreateCodeDocumentCore(RazorProjectItem projectItem);
protected abstract RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorProjectItem projectItem);
protected abstract void ProcessCore(RazorCodeDocument codeDocument);
public static RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem) => Create(configuration, fileSystem, configure: null);
public static RazorProjectEngine Create(
RazorProjectFileSystem fileSystem,
RazorConfiguration configuration,
RazorProjectFileSystem fileSystem,
Action<RazorProjectEngineBuilder> configure)
{
if (fileSystem == null)
@ -37,54 +72,96 @@ namespace Microsoft.AspNetCore.Razor.Language
var builder = new DefaultRazorProjectEngineBuilder(configuration, fileSystem);
AddDefaults(builder);
// The intialization order is somewhat important.
//
// Defaults -> Extensions -> Additional customization
//
// This allows extensions to rely on default features, and customizations to override choices made by
// extensions.
RazorEngine.AddDefaultPhases(builder.Phases);
AddDefaultsFeatures(builder.Features);
if (configuration.DesignTime)
{
AddDesignTimeDefaults(builder);
}
else
{
AddRuntimeDefaults(builder);
}
LoadExtensions(builder, configuration.Extensions);
configure?.Invoke(builder);
return builder.Build();
}
private static void AddDefaults(RazorProjectEngineBuilder builder)
private static void AddDefaultsFeatures(ICollection<IRazorFeature> features)
{
builder.Features.Add(new DefaultRazorImportFeature());
}
features.Add(new DefaultImportProjectFeature());
private static void AddDesignTimeDefaults(RazorProjectEngineBuilder builder)
{
var engineFeatures = new List<IRazorEngineFeature>();
RazorEngine.AddDefaultFeatures(engineFeatures);
RazorEngine.AddDefaultDesignTimeFeatures(builder.Configuration, engineFeatures);
// General extensibility
features.Add(new DefaultRazorDirectiveFeature());
features.Add(new DefaultMetadataIdentifierFeature());
AddEngineFeaturesAndPhases(builder, engineFeatures);
}
// Options features
features.Add(new DefaultRazorParserOptionsFactoryProjectFeature());
features.Add(new DefaultRazorCodeGenerationOptionsFactoryProjectFeature());
private static void AddRuntimeDefaults(RazorProjectEngineBuilder builder)
{
var engineFeatures = new List<IRazorEngineFeature>();
RazorEngine.AddDefaultFeatures(engineFeatures);
RazorEngine.AddDefaultRuntimeFeatures(builder.Configuration, engineFeatures);
// Legacy options features
//
// These features are obsolete as of 2.1. Our code will resolve this but not invoke them.
features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Version_2_0));
features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false));
AddEngineFeaturesAndPhases(builder, engineFeatures);
}
// Syntax Tree passes
features.Add(new DefaultDirectiveSyntaxTreePass());
features.Add(new HtmlNodeOptimizationPass());
features.Add(new PreallocatedTagHelperAttributeOptimizationPass());
private static void AddEngineFeaturesAndPhases(RazorProjectEngineBuilder builder, IReadOnlyList<IRazorEngineFeature> engineFeatures)
{
for (var i = 0; i < engineFeatures.Count; i++)
// Intermediate Node Passes
features.Add(new DefaultDocumentClassifierPass());
features.Add(new MetadataAttributePass());
features.Add(new DesignTimeDirectivePass());
features.Add(new DirectiveRemovalOptimizationPass());
features.Add(new DefaultTagHelperOptimizationPass());
// Default Code Target Extensions
var targetExtensionFeature = new DefaultRazorTargetExtensionFeature();
features.Add(targetExtensionFeature);
targetExtensionFeature.TargetExtensions.Add(new MetadataAttributeTargetExtension());
targetExtensionFeature.TargetExtensions.Add(new DefaultTagHelperTargetExtension());
targetExtensionFeature.TargetExtensions.Add(new PreallocatedAttributeTargetExtension());
targetExtensionFeature.TargetExtensions.Add(new DesignTimeDirectiveTargetExtension());
// Default configuration
var configurationFeature = new DefaultDocumentClassifierPassFeature();
features.Add(configurationFeature);
configurationFeature.ConfigureClass.Add((document, @class) =>
{
var engineFeature = engineFeatures[i];
builder.Features.Add(engineFeature);
}
@class.ClassName = "Template";
@class.Modifiers.Add("public");
});
RazorEngine.AddDefaultPhases(builder.Phases);
configurationFeature.ConfigureNamespace.Add((document, @namespace) =>
{
@namespace.Content = "Razor";
});
configurationFeature.ConfigureMethod.Add((document, method) =>
{
method.MethodName = "ExecuteAsync";
method.ReturnType = $"global::{typeof(Task).FullName}";
method.Modifiers.Add("public");
method.Modifiers.Add("async");
method.Modifiers.Add("override");
});
}
private static void LoadExtensions(RazorProjectEngineBuilder builder, IReadOnlyList<RazorExtension> extensions)
{
for (var i = 0; i < extensions.Count; i++)
{
// For now we only handle AssemblyExtension - which is not user-constructable. We're keeping a tight
// lid on how things work until we add official support for extensibility everywhere. So, this is
// intentionally inflexible for the time being.
var extension = extensions[i] as AssemblyExtension;
var initializer = extension?.CreateInitializer();
initializer?.Initialize(builder);
}
}
}
}

View File

@ -7,14 +7,14 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorProjectEngineBuilder
{
public abstract RazorConfiguration Configuration { get; }
public abstract RazorProjectFileSystem FileSystem { get; }
public abstract ICollection<IRazorFeature> Features { get; }
public abstract IList<IRazorEnginePhase> Phases { get; }
public abstract RazorConfiguration Configuration { get; }
public abstract RazorProjectEngine Build();
}
}

View File

@ -2,14 +2,80 @@
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language
{
public static class RazorProjectEngineBuilderExtensions
{
public static void SetImportFeature(this RazorProjectEngineBuilder builder, IRazorImportFeature feature)
/// <summary>
/// Registers a class configuration delegate that gets invoked during code generation.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
/// <param name="configureClass"><see cref="Action"/> invoked to configure
/// <see cref="ClassDeclarationIntermediateNode"/> during code generation.</param>
/// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
public static RazorProjectEngineBuilder ConfigureClass(
this RazorProjectEngineBuilder builder,
Action<RazorCodeDocument, ClassDeclarationIntermediateNode> configureClass)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configureClass == null)
{
throw new ArgumentNullException(nameof(configureClass));
}
var configurationFeature = GetDefaultDocumentClassifierPassFeature(builder);
configurationFeature.ConfigureClass.Add(configureClass);
return builder;
}
/// <summary>
/// Sets the base type for generated types.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
/// <param name="baseType">The name of the base type.</param>
/// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
public static RazorProjectEngineBuilder SetBaseType(this RazorProjectEngineBuilder builder, string baseType)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var configurationFeature = GetDefaultDocumentClassifierPassFeature(builder);
configurationFeature.ConfigureClass.Add((document, @class) => @class.BaseType = baseType);
return builder;
}
/// <summary>
/// Sets the namespace for generated types.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
/// <param name="namespaceName">The name of the namespace.</param>
/// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
public static RazorProjectEngineBuilder SetNamespace(this RazorProjectEngineBuilder builder, string namespaceName)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var configurationFeature = GetDefaultDocumentClassifierPassFeature(builder);
configurationFeature.ConfigureNamespace.Add((document, @namespace) => @namespace.Content = namespaceName);
return builder;
}
public static void SetImportFeature(this RazorProjectEngineBuilder builder, IImportProjectFeature feature)
{
if (builder == null)
{
@ -22,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Language
}
// Remove any existing import features in favor of the new one we're given.
var existingFeatures = builder.Features.OfType<IRazorImportFeature>().ToArray();
var existingFeatures = builder.Features.OfType<IImportProjectFeature>().ToArray();
foreach (var existingFeature in existingFeatures)
{
builder.Features.Remove(existingFeature);
@ -79,6 +145,27 @@ namespace Microsoft.AspNetCore.Razor.Language
return builder;
}
/// <summary>
/// Adds the provided <see cref="RazorProjectItem" />s as imports to all project items processed
/// by the <see cref="RazorProjectEngine"/>.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
/// <param name="imports">The collection of imports.</param>
/// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
public static RazorProjectEngineBuilder AddDefaultImports(this RazorProjectEngineBuilder builder, params string[] imports)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var existingImportFeature = builder.Features.OfType<IImportProjectFeature>().First();
var testImportFeature = new AdditionalImportsProjectFeature(existingImportFeature, imports);
builder.SetImportFeature(testImportFeature);
return builder;
}
private static IRazorDirectiveFeature GetDirectiveFeature(RazorProjectEngineBuilder builder)
{
var directiveFeature = builder.Features.OfType<IRazorDirectiveFeature>().FirstOrDefault();
@ -102,5 +189,77 @@ namespace Microsoft.AspNetCore.Razor.Language
return targetExtensionFeature;
}
private static DefaultDocumentClassifierPassFeature GetDefaultDocumentClassifierPassFeature(RazorProjectEngineBuilder builder)
{
var configurationFeature = builder.Features.OfType<DefaultDocumentClassifierPassFeature>().FirstOrDefault();
if (configurationFeature == null)
{
configurationFeature = new DefaultDocumentClassifierPassFeature();
builder.Features.Add(configurationFeature);
}
return configurationFeature;
}
private class AdditionalImportsProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
private readonly IImportProjectFeature _existingImportFeature;
private readonly IEnumerable<RazorProjectItem> _imports;
public override RazorProjectEngine ProjectEngine
{
get => base.ProjectEngine;
set
{
_existingImportFeature.ProjectEngine = value;
base.ProjectEngine = value;
}
}
public AdditionalImportsProjectFeature(IImportProjectFeature existingImportFeature, params string[] imports)
{
_existingImportFeature = existingImportFeature;
_imports = imports.Select(import => new InMemoryProjectItem(import));
}
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
var imports = _existingImportFeature.GetImports(projectItem).ToList();
imports.AddRange(_imports);
return imports;
}
private class InMemoryProjectItem : RazorProjectItem
{
private readonly byte[] _importBytes;
public InMemoryProjectItem(string content)
{
if (string.IsNullOrEmpty(content))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(content));
}
var preamble = Encoding.UTF8.GetPreamble();
var contentBytes = Encoding.UTF8.GetBytes(content);
_importBytes = new byte[preamble.Length + contentBytes.Length];
preamble.CopyTo(_importBytes, 0);
contentBytes.CopyTo(_importBytes, preamble.Length);
}
public override string BasePath => null;
public override string FilePath => null;
public override string PhysicalPath => null;
public override bool Exists => true;
public override Stream Read() => new MemoryStream(_importBytes);
}
}
}
}

View File

@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorProjectFileSystem : RazorProject
{
internal static readonly RazorProjectFileSystem Empty = new EmptyProjectFileSystem();
/// <summary>
/// Create a Razor project file system based off of a root directory.
/// </summary>

View File

@ -9,9 +9,28 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
public class RazorGenerate : DotNetToolTask
{
private static readonly string[] SourceRequiredMetadata = new string[]
{
FullPath,
GeneratedOutput,
TargetPath,
};
private const string GeneratedOutput = "GeneratedOutput";
private const string TargetPath = "TargetPath";
private const string FullPath = "FullPath";
private const string Identity = "Identity";
private const string AssemblyName = "AssemblyName";
private const string AssemblyFilePath = "AssemblyFilePath";
[Required]
public string Version { get; set; }
[Required]
public ITaskItem[] Configuration { get; set; }
[Required]
public ITaskItem[] Extensions { get; set; }
[Required]
public ITaskItem[] Sources { get; set; }
@ -26,11 +45,28 @@ namespace Microsoft.AspNetCore.Razor.Tasks
protected override bool ValidateParameters()
{
if (Configuration.Length == 0)
{
Log.LogError("The project {0} must provide a value for {1}.", ProjectRoot, nameof(Configuration));
return false;
}
for (var i = 0; i < Sources.Length; i++)
{
if (!EnsureRequiredMetadata(Sources[i], FullPath) ||
!EnsureRequiredMetadata(Sources[i], GeneratedOutput) ||
!EnsureRequiredMetadata(Sources[i], TargetPath))
{
Log.LogError("The Razor source item '{0}' is missing a required metadata entry. Required metadata are: '{1}'", Sources[i], SourceRequiredMetadata);
return false;
}
}
for (var i = 0; i < Extensions.Length; i++)
{
if (!EnsureRequiredMetadata(Extensions[i], Identity) ||
!EnsureRequiredMetadata(Extensions[i], AssemblyName) ||
!EnsureRequiredMetadata(Extensions[i], AssemblyFilePath))
{
return false;
}
@ -65,6 +101,21 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.AppendLine("-t");
builder.AppendLine(TagHelperManifest);
builder.AppendLine("-v");
builder.AppendLine(Version);
builder.AppendLine("-c");
builder.AppendLine(Configuration[0].GetMetadata(Identity));
for (var i = 0; i < Extensions.Length; i++)
{
builder.AppendLine("-n");
builder.AppendLine(Extensions[i].GetMetadata(Identity));
builder.AppendLine("-e");
builder.AppendLine(Path.GetFullPath(Extensions[i].GetMetadata(AssemblyFilePath)));
}
return builder.ToString();
}

View File

@ -10,6 +10,19 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
public class RazorTagHelper : DotNetToolTask
{
private const string Identity = "Identity";
private const string AssemblyName = "AssemblyName";
private const string AssemblyFilePath = "AssemblyFilePath";
[Required]
public string Version { get; set; }
[Required]
public ITaskItem[] Configuration { get; set; }
[Required]
public ITaskItem[] Extensions { get; set; }
[Required]
public string[] Assemblies { get; set; }
@ -51,6 +64,21 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.AppendLine("-p");
builder.AppendLine(ProjectRoot);
builder.AppendLine("-v");
builder.AppendLine(Version);
builder.AppendLine("-c");
builder.AppendLine(Configuration[0].GetMetadata(Identity));
for (var i = 0; i < Extensions.Length; i++)
{
builder.AppendLine("-n");
builder.AppendLine(Extensions[i].GetMetadata(Identity));
builder.AppendLine("-e");
builder.AppendLine(Path.GetFullPath(Extensions[i].GetMetadata(AssemblyFilePath)));
}
return builder.ToString();
}
}

View File

@ -6,15 +6,19 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class Application : CommandLineApplication
{
public Application(CancellationToken cancellationToken)
public Application(CancellationToken cancellationToken, ExtensionAssemblyLoader loader, ExtensionDependencyChecker checker, Func<string, MetadataReferenceProperties, PortableExecutableReference> assemblyReferenceProvider)
{
CancellationToken = cancellationToken;
Checker = checker;
Loader = loader;
AssemblyReferenceProvider = assemblyReferenceProvider;
Name = "rzc";
FullName = "Microsoft ASP.NET Core Razor CLI tool";
@ -31,6 +35,12 @@ namespace Microsoft.AspNetCore.Razor.Tools
public CancellationToken CancellationToken { get; }
public ExtensionAssemblyLoader Loader { get; }
public ExtensionDependencyChecker Checker { get; }
public Func<string, MetadataReferenceProperties, PortableExecutableReference> AssemblyReferenceProvider { get; }
public new int Execute(params string[] args)
{
try

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 Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal sealed class CachingMetadataReference : PortableExecutableReference
{
private static readonly MetadataCache _metadataCache = new MetadataCache();
public CachingMetadataReference(string fullPath, MetadataReferenceProperties properties)
: base(properties, fullPath)
{
}
protected override DocumentationProvider CreateDocumentationProvider()
{
return DocumentationProvider.Default;
}
protected override Metadata GetMetadataImpl()
{
return _metadataCache.GetMetadata(FilePath);
}
protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties)
{
return new CachingMetadataReference(FilePath, properties);
}
}
}

View File

@ -1,10 +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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
@ -19,6 +21,25 @@ namespace Microsoft.AspNetCore.Razor.Tools
private class DefaultCompilerHost : CompilerHost
{
public DefaultCompilerHost()
{
// The loader needs to live for the lifetime of the server.
//
// This means that if a request tries to use a set of binaries that are inconsistent with what
// the server already has, then it will be rejected to try again on the client.
//
// We also check each set of extensions for missing depenencies individually, so that we can
// consistently reject a request that doesn't specify everything it needs. Otherwise the request
// could succeed sometimes if it relies on transient state.
Loader = new DefaultExtensionAssemblyLoader(Path.Combine(Path.GetTempPath(), "Razor-Server"));
AssemblyReferenceProvider = (path, properties) => new CachingMetadataReference(path, properties);
}
public Func<string, MetadataReferenceProperties, PortableExecutableReference> AssemblyReferenceProvider { get; }
public ExtensionAssemblyLoader Loader { get; }
public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
{
if (!TryParseArguments(request, out var parsed))
@ -28,28 +49,23 @@ namespace Microsoft.AspNetCore.Razor.Tools
var exitCode = 0;
var output = string.Empty;
var app = new Application(cancellationToken);
var commandArgs = parsed.args.ToArray();
var writer = ServerLogger.IsLoggingEnabled ? new StringWriter() : TextWriter.Null;
var checker = new DefaultExtensionDependencyChecker(Loader, writer, writer);
var app = new Application(cancellationToken, Loader, checker, AssemblyReferenceProvider)
{
Out = writer,
Error = writer,
};
exitCode = app.Execute(commandArgs);
if (ServerLogger.IsLoggingEnabled)
{
using (var writer = new StringWriter())
{
app.Out = writer;
app.Error = writer;
exitCode = app.Execute(commandArgs);
output = writer.ToString();
ServerLogger.Log(output);
}
}
else
{
using (var writer = new StreamWriter(Stream.Null))
{
app.Out = writer;
app.Error = writer;
exitCode = app.Execute(commandArgs);
}
output = writer.ToString();
ServerLogger.Log(output);
}
return new CompletedServerResponse(exitCode, utf8output: false, output: string.Empty);

View File

@ -3,24 +3,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class CompositeRazorProjectFileSystem : RazorProjectFileSystem
{
public CompositeRazorProjectFileSystem(IReadOnlyList<RazorProjectFileSystem> projects)
public CompositeRazorProjectFileSystem(IReadOnlyList<RazorProjectFileSystem> fileSystems)
{
Projects = projects ?? throw new ArgumentNullException(nameof(projects));
FileSystems = fileSystems ?? throw new ArgumentNullException(nameof(fileSystems));
}
public IReadOnlyList<RazorProjectFileSystem> Projects { get; }
public IReadOnlyList<RazorProjectFileSystem> FileSystems { get; }
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
foreach (var project in Projects)
foreach (var fileSystem in FileSystems)
{
foreach (var result in project.EnumerateItems(basePath))
foreach (var result in fileSystem.EnumerateItems(basePath))
{
yield return result;
}
@ -30,9 +31,9 @@ namespace Microsoft.AspNetCore.Razor.Tools
public override RazorProjectItem GetItem(string path)
{
RazorProjectItem razorProjectItem = null;
foreach (var project in Projects)
foreach (var fileSystem in FileSystems)
{
razorProjectItem = project.GetItem(path);
razorProjectItem = fileSystem.GetItem(path);
if (razorProjectItem != null && razorProjectItem.Exists)
{
return razorProjectItem;

View File

@ -0,0 +1,207 @@
// 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.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Tools
{
/// <summary>
/// Cache with a fixed size that evicts the least recently used members.
/// Thread-safe.
/// This was taken from https://github.com/dotnet/roslyn/blob/749c0ec135d7d080658dc1aa794d15229c3d10d2/src/Compilers/Core/Portable/InternalUtilities/ConcurrentLruCache.cs.
/// </summary>
internal class ConcurrentLruCache<TKey, TValue>
{
private readonly int _capacity;
private readonly Dictionary<TKey, CacheValue> _cache;
private readonly LinkedList<TKey> _nodeList;
// This is a naive course-grained lock, it can probably be optimized
private readonly object _lockObject = new object();
public ConcurrentLruCache(int capacity)
: this (capacity, EqualityComparer<TKey>.Default)
{
}
public ConcurrentLruCache(int capacity, IEqualityComparer<TKey> comparer)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
_capacity = capacity;
_cache = new Dictionary<TKey, CacheValue>(capacity, comparer);
_nodeList = new LinkedList<TKey>();
}
/// <summary>
/// Create cache from an array. The cache capacity will be the size
/// of the array. All elements of the array will be added to the
/// cache. If any duplicate keys are found in the array a
/// <see cref="ArgumentException"/> will be thrown.
/// </summary>
public ConcurrentLruCache(KeyValuePair<TKey, TValue>[] array)
: this(array.Length)
{
foreach (var kvp in array)
{
UnsafeAdd(kvp.Key, kvp.Value);
}
}
public int Count
{
get
{
lock (_lockObject)
{
return _cache.Count;
}
}
}
public void Add(TKey key, TValue value)
{
lock (_lockObject)
{
UnsafeAdd(key, value);
}
}
public TValue GetOrAdd(TKey key, TValue value)
{
lock (_lockObject)
{
if (UnsafeTryGetValue(key, out var result))
{
return result;
}
else
{
UnsafeAdd(key, value);
return value;
}
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (_lockObject)
{
return UnsafeTryGetValue(key, out value);
}
}
public bool Remove(TKey key)
{
lock (_lockObject)
{
return UnsafeRemove(key);
}
}
/// <summary>
/// For testing. Very expensive.
/// </summary>
internal IEnumerable<KeyValuePair<TKey, TValue>> TestingEnumerable
{
get
{
lock (_lockObject)
{
foreach (var key in _nodeList)
{
var kvp = new KeyValuePair<TKey, TValue>(key, _cache[key].Value);
yield return kvp;
}
}
}
}
/// <summary>
/// Doesn't lock.
/// </summary>
private bool UnsafeTryGetValue(TKey key, out TValue value)
{
if (_cache.TryGetValue(key, out var result))
{
MoveNodeToTop(result.Node);
value = result.Value;
return true;
}
else
{
value = default(TValue);
return false;
}
}
private void MoveNodeToTop(LinkedListNode<TKey> node)
{
if (!object.ReferenceEquals(_nodeList.First, node))
{
_nodeList.Remove(node);
_nodeList.AddFirst(node);
}
}
/// <summary>
/// Expects non-empty cache. Does not lock.
/// </summary>
private void UnsafeEvictLastNode()
{
Debug.Assert(_capacity > 0);
var lastNode = _nodeList.Last;
_nodeList.Remove(lastNode);
_cache.Remove(lastNode.Value);
}
private void UnsafeAddNodeToTop(TKey key, TValue value)
{
var node = new LinkedListNode<TKey>(key);
_cache.Add(key, new CacheValue(value, node));
_nodeList.AddFirst(node);
}
/// <summary>
/// Doesn't lock.
/// </summary>
private void UnsafeAdd(TKey key, TValue value)
{
if (_cache.TryGetValue(key, out var result))
{
throw new ArgumentException("Key already exists", nameof(key));
}
else
{
if (_cache.Count == _capacity)
{
UnsafeEvictLastNode();
}
UnsafeAddNodeToTop(key, value);
}
}
private bool UnsafeRemove(TKey key)
{
_nodeList.Remove(key);
return _cache.Remove(key);
}
private struct CacheValue
{
public CacheValue(TValue value, LinkedListNode<TKey> node)
{
Value = value;
Node = node;
}
public TValue Value { get; }
public LinkedListNode<TKey> Node { get; }
}
}
}

View File

@ -0,0 +1,241 @@
// 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.Collections.Immutable;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class DefaultExtensionAssemblyLoader : ExtensionAssemblyLoader
{
private readonly string _baseDirectory;
private readonly object _lock = new object();
private readonly Dictionary<string, (Assembly assembly, AssemblyIdentity identity)> _loadedByPath;
private readonly Dictionary<AssemblyIdentity, Assembly> _loadedByIdentity;
private readonly Dictionary<string, AssemblyIdentity> _identityCache;
private readonly Dictionary<string, List<string>> _wellKnownAssemblies;
private ShadowCopyManager _shadowCopyManager;
public DefaultExtensionAssemblyLoader(string baseDirectory)
{
_baseDirectory = baseDirectory;
_loadedByPath = new Dictionary<string, (Assembly assembly, AssemblyIdentity identity)>(StringComparer.OrdinalIgnoreCase);
_loadedByIdentity = new Dictionary<AssemblyIdentity, Assembly>();
_identityCache = new Dictionary<string, AssemblyIdentity>(StringComparer.OrdinalIgnoreCase);
_wellKnownAssemblies = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
LoadContext = new ExtensionAssemblyLoadContext(AssemblyLoadContext.GetLoadContext(typeof(ExtensionAssemblyLoader).Assembly), this);
}
protected AssemblyLoadContext LoadContext { get; }
public override void AddAssemblyLocation(string filePath)
{
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
if (!Path.IsPathRooted(filePath))
{
throw new ArgumentException(nameof(filePath));
}
var assemblyName = Path.GetFileNameWithoutExtension(filePath);
lock (_lock)
{
if (!_wellKnownAssemblies.TryGetValue(assemblyName, out var paths))
{
paths = new List<string>();
_wellKnownAssemblies.Add(assemblyName, paths);
}
if (!paths.Contains(filePath))
{
paths.Add(filePath);
}
}
}
public override Assembly Load(string assemblyName)
{
if (!AssemblyIdentity.TryParseDisplayName(assemblyName, out var identity))
{
return null;
}
lock (_lock)
{
// First, check if this loader already loaded the requested assembly:
if (_loadedByIdentity.TryGetValue(identity, out var assembly))
{
return assembly;
}
// Second, check if an assembly file of the same simple name was registered with the loader:
if (_wellKnownAssemblies.TryGetValue(identity.Name, out var paths))
{
// Multiple assemblies of the same simple name but different identities might have been registered.
// Load the one that matches the requested identity (if any).
foreach (var path in paths)
{
var candidateIdentity = GetIdentity(path);
if (identity.Equals(candidateIdentity))
{
return LoadFromPathUnsafe(path, candidateIdentity);
}
}
}
// We only support loading by name from 'well-known' paths. If you need to load something by
// name and you get here, then that means we don't know where to look.
return null;
}
}
public override Assembly LoadFromPath(string filePath)
{
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
if (!Path.IsPathRooted(filePath))
{
throw new ArgumentException(nameof(filePath));
}
lock (_lock)
{
return LoadFromPathUnsafe(filePath, identity: null);
}
}
private Assembly LoadFromPathUnsafe(string filePath, AssemblyIdentity identity)
{
// If we've already loaded the assembly by path there should be nothing else to do,
// all of our data is up to date.
if (_loadedByPath.TryGetValue(filePath, out var entry))
{
return entry.assembly;
}
// If we've already loaded the assembly by identity, then we might has some updating
// to do.
identity = identity ?? GetIdentity(filePath);
if (identity != null && _loadedByIdentity.TryGetValue(identity, out var assembly))
{
// An assembly file might be replaced by another file with a different identity.
// Last one wins.
_loadedByPath[filePath] = (assembly, identity);
return assembly;
}
// Ok we don't have this cached. Let's actually try to load the assembly.
assembly = LoadFromPathUnsafeCore(CopyAssembly(filePath));
identity = identity ?? AssemblyIdentity.FromAssemblyDefinition(assembly);
// It's possible an assembly was loaded by two different paths. Just use the original then.
if (_loadedByIdentity.TryGetValue(identity, out var duplicate))
{
assembly = duplicate;
}
else
{
_loadedByIdentity.Add(identity, assembly);
}
_loadedByPath[filePath] = (assembly, identity);
return assembly;
}
private AssemblyIdentity GetIdentity(string filePath)
{
if (!_identityCache.TryGetValue(filePath, out var identity))
{
identity = ReadAssemblyIdentity(filePath);
_identityCache.Add(filePath, identity);
}
return identity;
}
protected virtual string CopyAssembly(string filePath)
{
if (_baseDirectory == null)
{
// Don't shadow-copy when base directory is null. This means we're running as a CLI not
// a server.
return filePath;
}
if (_shadowCopyManager == null)
{
_shadowCopyManager = new ShadowCopyManager(_baseDirectory);
}
return _shadowCopyManager.AddAssembly(filePath);
}
protected virtual Assembly LoadFromPathUnsafeCore(string filePath)
{
return LoadContext.LoadFromAssemblyPath(filePath);
}
private static AssemblyIdentity ReadAssemblyIdentity(string filePath)
{
try
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
using (var reader = new PEReader(stream))
{
var metadataReader = reader.GetMetadataReader();
return metadataReader.GetAssemblyIdentity();
}
}
catch
{
}
return null;
}
private class ExtensionAssemblyLoadContext : AssemblyLoadContext
{
private readonly AssemblyLoadContext _parent;
private readonly DefaultExtensionAssemblyLoader _loader;
public ExtensionAssemblyLoadContext(AssemblyLoadContext parent, DefaultExtensionAssemblyLoader loader)
{
_parent = parent;
_loader = loader;
}
protected override Assembly Load(AssemblyName assemblyName)
{
// Try to load from well-known paths. This will be called when loading a dependency of an extension.
var assembly = _loader.Load(assemblyName.ToString());
if (assembly != null)
{
return assembly;
}
// If we don't have an entry, then fall back to the default load context. This allows extensions
// to resolve assemblies that are provided by the host.
return _parent.LoadFromAssemblyName(assemblyName);
}
}
}
}

View File

@ -0,0 +1,158 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class DefaultExtensionDependencyChecker : ExtensionDependencyChecker
{
// These are treated as prefixes. So `Microsoft.CodeAnalysis.Razor` would be assumed to work.
private static readonly string[] DefaultIgnoredAssemblies = new string[]
{
"mscorlib",
"netstandard",
"System",
"Microsoft.CodeAnalysis",
"Microsoft.AspNetCore.Razor.Language",
};
private readonly ExtensionAssemblyLoader _loader;
private readonly TextWriter _output;
private readonly TextWriter _error;
private readonly string[] _ignoredAssemblies;
public DefaultExtensionDependencyChecker(
ExtensionAssemblyLoader loader,
TextWriter output,
TextWriter error,
string[] ignoredAssemblies = null)
{
_loader = loader;
_output = output;
_error = error;
_ignoredAssemblies = ignoredAssemblies ?? DefaultIgnoredAssemblies;
}
public override bool Check(IEnumerable<string> assmblyFilePaths)
{
try
{
return CheckCore(assmblyFilePaths);
}
catch (Exception ex)
{
_error.WriteLine("Exception performing Extension dependency check:");
_error.WriteLine(ex.ToString());
return false;
}
}
private bool CheckCore(IEnumerable<string> assemblyFilePaths)
{
var items = assemblyFilePaths.Select(a => ExtensionVerificationItem.Create(a)).ToArray();
var assemblies = new HashSet<AssemblyIdentity>(items.Select(i => i.Identity));
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
_output.WriteLine($"Verifying assembly at {item.FilePath}");
if (!Path.IsPathRooted(item.FilePath))
{
_error.WriteLine($"The file path '{item.FilePath}' is not a rooted path. File paths must be absolute and fully-qualified.");
return false;
}
foreach (var reference in item.References)
{
if (_ignoredAssemblies.Any(n => reference.Name.StartsWith(n)))
{
// This is on the allow list, keep going.
continue;
}
if (assemblies.Contains(reference))
{
// This was also provided as a dependency, keep going.
continue;
}
// If we get here we can't resolve this assembly. This is an error.
_error.WriteLine($"Extension assembly '{item.Identity.Name}' depends on '{reference.ToString()} which is missing.");
return false;
}
}
// Assuming we get this far, the set of assemblies we have is at least a coherent set (barring
// version conflicts). Register all of the paths with the loader so they can find each other by
// name.
for (var i = 0; i < items.Length; i++)
{
_loader.AddAssemblyLocation(items[i].FilePath);
}
// Now try to load everything. This has the side effect of resolving all of these items
// in the loader's caches.
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
item.Assembly = _loader.LoadFromPath(item.FilePath);
}
// Third, check that the MVIDs of the files on disk match the MVIDs of the loaded assemblies.
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
if (item.Mvid != item.Assembly.ManifestModule.ModuleVersionId)
{
_error.WriteLine($"Extension assembly '{item.Identity.Name}' at '{item.FilePath}' has a different ModuleVersionId than loaded assembly '{item.Assembly.FullName}'");
return false;
}
}
return true;
}
private class ExtensionVerificationItem
{
public static ExtensionVerificationItem Create(string filePath)
{
using (var peReader = new PEReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
var metadataReader = peReader.GetMetadataReader();
var identity = metadataReader.GetAssemblyIdentity();
var mvid = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid);
var references = metadataReader.GetReferencedAssembliesOrThrow();
return new ExtensionVerificationItem(filePath, identity, mvid, references.ToArray());
}
}
private ExtensionVerificationItem(string filePath, AssemblyIdentity identity, Guid mvid, AssemblyIdentity[] references)
{
FilePath = filePath;
Identity = identity;
Mvid = mvid;
References = references;
}
public string FilePath { get; }
public Assembly Assembly { get; set; }
public AssemblyIdentity Identity { get; }
public Guid Mvid { get; }
public IReadOnlyList<AssemblyIdentity> References { get; }
}
}
}

View File

@ -383,6 +383,8 @@ namespace Microsoft.AspNetCore.Razor.Tools
ServerLogger.Log("End writing response.");
reason = ConnectionResult.Reason.CompilationCompleted;
_eventBus.CompilationCompleted();
}
catch
{

View File

@ -8,7 +8,6 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
@ -26,6 +25,10 @@ namespace Microsoft.AspNetCore.Razor.Tools
Assemblies = Argument("assemblies", "assemblies to search for tag helpers", multipleValues: true);
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
Version = Option("-v|--version", "Razor language version", CommandOptionType.SingleValue);
Configuration = Option("-c", "Razor configuration name", CommandOptionType.SingleValue);
ExtensionNames = Option("-n", "extension name", CommandOptionType.MultipleValue);
ExtensionFilePaths = Option("-e", "extension file path", CommandOptionType.MultipleValue);
}
public CommandArgument Assemblies { get; }
@ -34,6 +37,14 @@ namespace Microsoft.AspNetCore.Razor.Tools
public CommandOption ProjectDirectory { get; }
public CommandOption Version { get; }
public CommandOption Configuration { get; }
public CommandOption ExtensionNames { get; }
public CommandOption ExtensionFilePaths { get; }
protected override bool ValidateArguments()
{
if (string.IsNullOrEmpty(TagHelperManifest.Value()))
@ -53,12 +64,61 @@ namespace Microsoft.AspNetCore.Razor.Tools
ProjectDirectory.Values.Add(Environment.CurrentDirectory);
}
if (string.IsNullOrEmpty(Version.Value()))
{
Error.WriteLine($"{Version.ValueName} must be specified.");
return false;
}
else if (!RazorLanguageVersion.TryParse(Version.Value(), out _))
{
Error.WriteLine($"{Version.ValueName} is not a valid language version.");
return false;
}
if (string.IsNullOrEmpty(Configuration.Value()))
{
Error.WriteLine($"{Configuration.ValueName} must be specified.");
return false;
}
if (ExtensionNames.Values.Count != ExtensionFilePaths.Values.Count)
{
Error.WriteLine($"{ExtensionNames.ValueName} and {ExtensionFilePaths.ValueName} should have the same number of values.");
}
foreach (var filePath in ExtensionFilePaths.Values)
{
if (!Path.IsPathRooted(filePath))
{
Error.WriteLine($"Extension file paths must be fully-qualified, absolute paths.");
return false;
}
}
if (!Parent.Checker.Check(ExtensionFilePaths.Values))
{
Error.WriteLine($"Extenions could not be loaded. See output for details.");
return false;
}
return true;
}
protected override Task<int> ExecuteCoreAsync()
{
// Loading all of the extensions should succeed as the dependency checker will have already
// loaded them.
var extensions = new RazorExtension[ExtensionNames.Values.Count];
for (var i = 0; i < ExtensionNames.Values.Count; i++)
{
extensions[i] = new AssemblyExtension(ExtensionNames.Values[i], Parent.Loader.LoadFromPath(ExtensionFilePaths.Values[i]));
}
var version = RazorLanguageVersion.Parse(Version.Value());
var configuration = new RazorConfiguration(version, Configuration.Value(), extensions);
var result = ExecuteCore(
configuration: configuration,
projectDirectory: ProjectDirectory.Value(),
outputFilePath: TagHelperManifest.Value(),
assemblies: Assemblies.Values.ToArray());
@ -66,36 +126,31 @@ namespace Microsoft.AspNetCore.Razor.Tools
return Task.FromResult(result);
}
private int ExecuteCore(string projectDirectory, string outputFilePath, string[] assemblies)
private int ExecuteCore(RazorConfiguration configuration, string projectDirectory, string outputFilePath, string[] assemblies)
{
outputFilePath = Path.Combine(projectDirectory, outputFilePath);
var metadataReferences = new MetadataReference[assemblies.Length];
for (var i = 0; i < assemblies.Length; i++)
{
metadataReferences[i] = MetadataReference.CreateFromFile(assemblies[i]);
metadataReferences[i] = Parent.AssemblyReferenceProvider(assemblies[i], default(MetadataReferenceProperties));
}
var engine = RazorEngine.Create((b) =>
var engine = RazorProjectEngine.Create(configuration, RazorProjectFileSystem.Empty, b =>
{
RazorExtensions.Register(b);
b.Features.Add(new DefaultMetadataReferenceFeature() { References = metadataReferences });
b.Features.Add(new CompilationTagHelperFeature());
// TagHelperDescriptorProviders (actually do tag helper discovery)
b.Features.Add(new DefaultTagHelperDescriptorProvider());
b.Features.Add(new ViewComponentTagHelperDescriptorProvider());
});
var feature = engine.Features.OfType<ITagHelperFeature>().Single();
var feature = engine.Engine.Features.OfType<ITagHelperFeature>().Single();
var tagHelpers = feature.GetDescriptors();
using (var stream = new MemoryStream())
{
Serialize(stream, tagHelpers);
stream.Position = 0L;
stream.Position = 0;
var newHash = Hash(stream);
var existingHash = Hash(outputFilePath);
@ -103,7 +158,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
if (!HashesEqual(newHash, existingHash))
{
stream.Position = 0;
using (var output = File.OpenWrite(outputFilePath))
using (var output = File.Open(outputFilePath, FileMode.Create))
{
stream.CopyTo(output);
}

View File

@ -37,6 +37,13 @@ namespace Microsoft.AspNetCore.Razor.Tools
public virtual void ConnectionCompleted(int count)
{
}
/// <summary>
/// Called when a compilation is completed successfully and the response is written to the stream.
/// </summary>
public virtual void CompilationCompleted()
{
}
/// <summary>
/// Called when a bad client connection was detected and the server will be shutting down as a

View File

@ -0,0 +1,16 @@
// 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.Reflection;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal abstract class ExtensionAssemblyLoader
{
public abstract void AddAssemblyLocation(string filePath);
public abstract Assembly Load(string assemblyName);
public abstract Assembly LoadFromPath(string filePath);
}
}

View File

@ -0,0 +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.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal abstract class ExtensionDependencyChecker
{
public abstract bool Check(IEnumerable<string> extensionFilePaths);
}
}

View File

@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.VisualStudio.LanguageServices.Razor;
@ -13,13 +13,6 @@ using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class Builder<T>
{
public static Builder<T> Make(CommandBase result) => null;
public static Builder<T> Make(T result) => null;
}
internal class GenerateCommand : CommandBase
{
public GenerateCommand(Application parent)
@ -30,6 +23,10 @@ namespace Microsoft.AspNetCore.Razor.Tools
RelativePaths = Option("-r", "Relative path", CommandOptionType.MultipleValue);
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
TagHelperManifest = Option("-t", "tag helper manifest file", CommandOptionType.SingleValue);
Version = Option("-v|--version", "Razor language version", CommandOptionType.SingleValue);
Configuration = Option("-c", "Razor configuration name", CommandOptionType.SingleValue);
ExtensionNames = Option("-n", "extension name", CommandOptionType.MultipleValue);
ExtensionFilePaths = Option("-e", "extension file path", CommandOptionType.MultipleValue);
}
public CommandOption Sources { get; }
@ -42,9 +39,29 @@ namespace Microsoft.AspNetCore.Razor.Tools
public CommandOption TagHelperManifest { get; }
public CommandOption Version { get; }
public CommandOption Configuration { get; }
public CommandOption ExtensionNames { get; }
public CommandOption ExtensionFilePaths { get; }
protected override Task<int> ExecuteCoreAsync()
{
// Loading all of the extensions should succeed as the dependency checker will have already
// loaded them.
var extensions = new RazorExtension[ExtensionNames.Values.Count];
for (var i = 0; i < ExtensionNames.Values.Count; i++)
{
extensions[i] = new AssemblyExtension(ExtensionNames.Values[i], Parent.Loader.LoadFromPath(ExtensionFilePaths.Values[i]));
}
var version = RazorLanguageVersion.Parse(Version.Value());
var configuration = new RazorConfiguration(version, Configuration.Value(), extensions);
var result = ExecuteCore(
configuration: configuration,
projectDirectory: ProjectDirectory.Value(),
tagHelperManifest: TagHelperManifest.Value(),
sources: Sources.Values,
@ -77,10 +94,48 @@ namespace Microsoft.AspNetCore.Razor.Tools
ProjectDirectory.Values.Add(Environment.CurrentDirectory);
}
if (string.IsNullOrEmpty(Version.Value()))
{
Error.WriteLine($"{Version.ValueName} must be specified.");
return false;
}
else if (!RazorLanguageVersion.TryParse(Version.Value(), out _))
{
Error.WriteLine($"{Version.ValueName} is not a valid language version.");
return false;
}
if (string.IsNullOrEmpty(Configuration.Value()))
{
Error.WriteLine($"{Configuration.ValueName} must be specified.");
return false;
}
if (ExtensionNames.Values.Count != ExtensionFilePaths.Values.Count)
{
Error.WriteLine($"{ExtensionNames.ValueName} and {ExtensionFilePaths.ValueName} should have the same number of values.");
}
foreach (var filePath in ExtensionFilePaths.Values)
{
if (!Path.IsPathRooted(filePath))
{
Error.WriteLine($"Extension file paths must be fully-qualified, absolute paths.");
return false;
}
}
if (!Parent.Checker.Check(ExtensionFilePaths.Values))
{
Error.WriteLine($"Extensions could not be loaded. See output for details.");
return false;
}
return true;
}
private int ExecuteCore(
RazorConfiguration configuration,
string projectDirectory,
string tagHelperManifest,
List<string> sources,
@ -90,26 +145,19 @@ namespace Microsoft.AspNetCore.Razor.Tools
tagHelperManifest = Path.Combine(projectDirectory, tagHelperManifest);
var tagHelpers = GetTagHelpers(tagHelperManifest);
var engine = RazorEngine.Create(b =>
var inputItems = GetInputItems(projectDirectory, sources, outputs, relativePaths);
var compositeFileSystem = new CompositeRazorProjectFileSystem(new[]
{
GetVirtualRazorProjectSystem(inputItems),
RazorProjectFileSystem.Create(projectDirectory),
});
var engine = RazorProjectEngine.Create(configuration, compositeFileSystem, b =>
{
RazorExtensions.Register(b);
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, });
});
var inputItems = GetInputItems(projectDirectory, sources, outputs, relativePaths);
var compositeProject = new CompositeRazorProjectFileSystem(
new[]
{
GetVirtualRazorProjectSystem(inputItems),
RazorProjectFileSystem.Create(projectDirectory),
});
var templateEngine = new MvcRazorTemplateEngine(engine, compositeProject);
var results = GenerateCode(templateEngine, inputItems);
var results = GenerateCode(engine, inputItems);
var success = true;
@ -180,14 +228,15 @@ namespace Microsoft.AspNetCore.Razor.Tools
return items;
}
private OutputItem[] GenerateCode(RazorTemplateEngine templateEngine, SourceItem[] inputs)
private OutputItem[] GenerateCode(RazorProjectEngine engine, SourceItem[] inputs)
{
var outputs = new OutputItem[inputs.Length];
Parallel.For(0, outputs.Length, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, i =>
Parallel.For(0, outputs.Length, new ParallelOptions() { MaxDegreeOfParallelism = Debugger.IsAttached ? 1 : 4 }, i =>
{
var inputItem = inputs[i];
var csharpDocument = templateEngine.GenerateCode(inputItem.FilePath);
var codeDocument = engine.Process(engine.FileSystem.GetItem(inputItem.FilePath));
var csharpDocument = codeDocument.GetCSharpDocument();
outputs[i] = new OutputItem(inputItem, csharpDocument);
});

View File

@ -0,0 +1,86 @@
// 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.Diagnostics;
using System.IO;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal class MetadataCache
{
// Store 1000 entries -- arbitrary number
private const int CacheSize = 1000;
private readonly ConcurrentLruCache<string, MetadataCacheEntry> _metadataCache =
new ConcurrentLruCache<string, MetadataCacheEntry>(CacheSize, StringComparer.OrdinalIgnoreCase);
// For testing purposes only.
internal ConcurrentLruCache<string, MetadataCacheEntry> Cache => _metadataCache;
internal Metadata GetMetadata(string fullPath)
{
var timestamp = GetFileTimeStamp(fullPath);
// Check if we have an entry in the dictionary.
if (_metadataCache.TryGetValue(fullPath, out var entry))
{
if (timestamp.HasValue && timestamp.Value == entry.Timestamp)
{
// The file has not changed since we cached it. Return the cached entry.
return entry.Metadata;
}
else
{
// The file has changed recently. Remove the cache entry.
_metadataCache.Remove(fullPath);
}
}
Metadata metadata;
using (var fileStream = File.OpenRead(fullPath))
{
metadata = AssemblyMetadata.CreateFromStream(fileStream, PEStreamOptions.PrefetchMetadata);
}
_metadataCache.GetOrAdd(fullPath, new MetadataCacheEntry(timestamp.Value, metadata));
return metadata;
}
private static DateTime? GetFileTimeStamp(string fullPath)
{
try
{
Debug.Assert(Path.IsPathRooted(fullPath));
return File.GetLastWriteTimeUtc(fullPath);
}
catch (Exception e)
{
// There are several exceptions that can occur here: NotSupportedException or PathTooLongException
// for a bad path, UnauthorizedAccessException for access denied, etc. Rather than listing them all,
// just catch all exceptions and log.
ServerLogger.LogException(e, $"Error getting timestamp of file {fullPath}.");
return null;
}
}
internal struct MetadataCacheEntry
{
public MetadataCacheEntry(DateTime timestamp, Metadata metadata)
{
Debug.Assert(timestamp.Kind == DateTimeKind.Utc);
Timestamp = timestamp;
Metadata = metadata;
}
public DateTime Timestamp { get; }
public Metadata Metadata { get; }
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) Microsoft. 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.Collections.Immutable;
using System.Reflection;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal static class MetadataReaderExtensions
{
internal static AssemblyIdentity GetAssemblyIdentity(this MetadataReader reader)
{
if (!reader.IsAssembly)
{
throw new BadImageFormatException();
}
var definition = reader.GetAssemblyDefinition();
return CreateAssemblyIdentity(
reader,
definition.Version,
definition.Flags,
definition.PublicKey,
definition.Name,
definition.Culture,
isReference: false);
}
internal static AssemblyIdentity[] GetReferencedAssembliesOrThrow(this MetadataReader reader)
{
var references = new List<AssemblyIdentity>();
foreach (var referenceHandle in reader.AssemblyReferences)
{
var reference = reader.GetAssemblyReference(referenceHandle);
references.Add(CreateAssemblyIdentity(
reader,
reference.Version,
reference.Flags,
reference.PublicKeyOrToken,
reference.Name,
reference.Culture,
isReference: true));
}
return references.ToArray();
}
private static AssemblyIdentity CreateAssemblyIdentity(
MetadataReader reader,
Version version,
AssemblyFlags flags,
BlobHandle publicKey,
StringHandle name,
StringHandle culture,
bool isReference)
{
var publicKeyOrToken = reader.GetBlobContent(publicKey);
bool hasPublicKey;
if (isReference)
{
hasPublicKey = (flags & AssemblyFlags.PublicKey) != 0;
}
else
{
// Assembly definitions never contain a public key token, they only can have a full key or nothing,
// so the flag AssemblyFlags.PublicKey does not make sense for them and is ignored.
// See Ecma-335, Partition II Metadata, 22.2 "Assembly : 0x20".
// This also corresponds to the behavior of the native C# compiler and sn.exe tool.
hasPublicKey = !publicKeyOrToken.IsEmpty;
}
if (publicKeyOrToken.IsEmpty)
{
publicKeyOrToken = default;
}
return new AssemblyIdentity(
name: reader.GetString(name),
version: version,
cultureName: culture.IsNil ? null : reader.GetString(culture),
publicKeyOrToken: publicKeyOrToken,
hasPublicKey: hasPublicKey,
isRetargetable: (flags & AssemblyFlags.Retargetable) != 0,
contentType: (AssemblyContentType)((int)(flags & AssemblyFlags.ContentTypeMask) >> 9));
}
}
}

View File

@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor\Microsoft.CodeAnalysis.Razor.csproj" />
</ItemGroup>
<!-- This makes it so that the runtimeconfig.json is included as part of the build output of the project that references this project. -->

View File

@ -3,6 +3,7 @@
using System;
using System.Threading;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Tools
{
@ -15,7 +16,16 @@ namespace Microsoft.AspNetCore.Razor.Tools
var cancel = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) => { cancel.Cancel(); };
var application = new Application(cancel.Token);
// Prevent shadow copying.
var loader = new DefaultExtensionAssemblyLoader(baseDirectory: null);
var checker = new DefaultExtensionDependencyChecker(loader, Console.Out, Console.Error);
var application = new Application(
cancel.Token,
loader,
checker,
(path, properties) => MetadataReference.CreateFromFile(path, properties));
return application.Execute(args);
}
}

View File

@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
var host = ConnectionHost.Create(Pipe.Value());
var compilerHost = CompilerHost.Create();
ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive);
}

View File

@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
/// <summary>
/// Write a Request to the stream.
/// </summary>
public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default)
public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken))
{
using (var memoryStream = new MemoryStream())
using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode))

View File

@ -0,0 +1,169 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Razor.Tools
{
// Note that this class has no thread-safety guarantees. The caller should use a lock
// if concurrency is required.
internal class ShadowCopyManager : IDisposable
{
// Note that this class uses the *existance* of the Mutex to lock a directory.
//
// Nothing in this code actually ever acquires the Mutex, we just try to see if it exists
// already.
private readonly Mutex _mutex;
private int _counter;
public ShadowCopyManager(string baseDirectory = null)
{
BaseDirectory = baseDirectory ?? Path.Combine(Path.GetTempPath(), "Razor", "ShadowCopy");
var guid = Guid.NewGuid().ToString("N").ToLowerInvariant();
UniqueDirectory = Path.Combine(BaseDirectory, guid);
_mutex = new Mutex(initiallyOwned: false, name: guid);
Directory.CreateDirectory(UniqueDirectory);
}
public string BaseDirectory { get; }
public string UniqueDirectory { get; }
public string AddAssembly(string filePath)
{
var assemblyDirectory = CreateUniqueDirectory();
var destination = Path.Combine(assemblyDirectory, Path.GetFileName(filePath));
CopyFile(filePath, destination);
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
var resourcesNameWithoutExtension = fileNameWithoutExtension + ".resources";
var resourcesNameWithExtension = resourcesNameWithoutExtension + ".dll";
foreach (var directory in Directory.EnumerateDirectories(Path.GetDirectoryName(filePath)))
{
var directoryName = Path.GetFileName(directory);
var resourcesPath = Path.Combine(directory, resourcesNameWithExtension);
if (File.Exists(resourcesPath))
{
var resourcesShadowCopyPath = Path.Combine(assemblyDirectory, directoryName, resourcesNameWithExtension);
CopyFile(resourcesPath, resourcesShadowCopyPath);
}
resourcesPath = Path.Combine(directory, resourcesNameWithoutExtension, resourcesNameWithExtension);
if (File.Exists(resourcesPath))
{
var resourcesShadowCopyPath = Path.Combine(assemblyDirectory, directoryName, resourcesNameWithoutExtension, resourcesNameWithExtension);
CopyFile(resourcesPath, resourcesShadowCopyPath);
}
}
return destination;
}
public void Dispose()
{
_mutex.ReleaseMutex();
}
public Task PurgeUnusedDirectoriesAsync()
{
return Task.Run((Action)PurgeUnusedDirectories);
}
private string CreateUniqueDirectory()
{
var id = _counter++;
var directory = Path.Combine(UniqueDirectory, id.ToString());
Directory.CreateDirectory(directory);
return directory;
}
private void CopyFile(string originalPath, string shadowCopyPath)
{
var directory = Path.GetDirectoryName(shadowCopyPath);
Directory.CreateDirectory(directory);
File.Copy(originalPath, shadowCopyPath);
MakeWritable(new FileInfo(shadowCopyPath));
}
private void MakeWritable(string directoryPath)
{
var directory = new DirectoryInfo(directoryPath);
foreach (var file in directory.EnumerateFiles(searchPattern: "*", searchOption: SearchOption.AllDirectories))
{
MakeWritable(file);
}
}
private void MakeWritable(FileInfo file)
{
try
{
if (file.IsReadOnly)
{
file.IsReadOnly = false;
}
}
catch
{
// There are many reasons this could fail. Ignore it and keep going.
}
}
private void PurgeUnusedDirectories()
{
IEnumerable<string> directories;
try
{
directories = Directory.EnumerateDirectories(BaseDirectory);
}
catch (DirectoryNotFoundException)
{
return;
}
foreach (var directory in directories)
{
Mutex mutex = null;
try
{
// We only want to try deleting the directory if no-one else is currently using it.
//
// Note that the mutex name is the name of the directory. This is OK because we're using
// GUIDs as directory/mutex names.
if (!Mutex.TryOpenExisting(Path.GetFileName(directory).ToLowerInvariant(), out mutex))
{
MakeWritable(directory);
Directory.Delete(directory, recursive: true);
}
}
catch
{
// If something goes wrong we will leave it to the next run to clean up.
// Just swallow the exception and move on.
}
finally
{
if (mutex != null)
{
mutex.Dispose();
}
}
}
}
}
}

View File

@ -57,19 +57,21 @@ namespace Microsoft.AspNetCore.Razor.Tools
var process = Process.GetProcessById(response.ServerProcessId);
process.WaitForExit();
}
catch (Exception)
catch (Exception ex)
{
// There is an inherent race here with the server process. If it has already shutdown
// by the time we try to access it then the operation has succeed.
// by the time we try to access it then the operation has succeeded.
Error.Write(ex);
}
Out.Write("Server pid:{0} shut down", response.ServerProcessId);
}
}
}
catch (Exception) when (IsServerRunning())
catch (Exception ex) when (IsServerRunning())
{
// Ignore an exception that occurred while the server was shutting down.
Error.Write(ex);
}
return 0;

View File

@ -0,0 +1,28 @@
// 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;
namespace Microsoft.CodeAnalysis.Razor
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ExportCustomProjectEngineFactoryAttribute : ExportAttribute, ICustomProjectEngineFactoryMetadata
{
public ExportCustomProjectEngineFactoryAttribute(string configurationName)
: base(typeof(IProjectEngineFactory))
{
if (configurationName == null)
{
throw new ArgumentNullException(nameof(configurationName));
}
ConfigurationName = configurationName;
}
public string ConfigurationName { get; }
public bool SupportsSerialization { get; set; }
}
}

View File

@ -0,0 +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.
namespace Microsoft.CodeAnalysis.Razor
{
public interface ICustomProjectEngineFactoryMetadata
{
string ConfigurationName { get; }
bool SupportsSerialization { get; }
}
}

View File

@ -0,0 +1,13 @@
// 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
{
public interface IProjectEngineFactory
{
RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure);
}
}

View File

@ -7,8 +7,8 @@ using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class RazorTemplateEngineFactoryService : ILanguageService
internal abstract class RazorProjectEngineFactoryService : ILanguageService
{
public abstract RazorTemplateEngine Create(string projectPath, Action<IRazorEngineBuilder> configure);
public abstract RazorProjectEngine Create(string projectPath, Action<RazorProjectEngineBuilder> configure);
}
}

View File

@ -10,7 +10,16 @@ Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<PropertyGroup>
<!-- Determines if the Razor Sdk is responsible for importing Microsoft.NET.Sdk. Microsoft.NET.Sdk.Web may have previously imported this. -->
<_RazorSdkImportsMicrosoftNetSdk Condition="'$(UsingMicrosoftNETSdk)' != 'true'">true</_RazorSdkImportsMicrosoftNetSdk>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.props" />
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition="'$(_RazorSdkImportsMicrosoftNetSdk)' == 'true'" />
<PropertyGroup>
<RazorSdkCurrentVersionProps Condition="'$(RazorSdkCurrentVersionProps)' == ''">$(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.props</RazorSdkCurrentVersionProps>
</PropertyGroup>
<Import Project="$(RazorSdkCurrentVersionProps)" />
</Project>

View File

@ -11,11 +11,13 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<Project ToolsVersion="14.0">
<PropertyGroup Condition="'$(RazorSdkTargetsPath)' == ''">
<RazorSdkTargetsPath Condition="'$(IsCrossTargetingBuild)' == 'true'">$(MSBuildThisFileDirectory)..\buildMultiTargeting\Sdk.Razor.CurrentVersion.MultiTargeting.targets</RazorSdkTargetsPath>
<RazorSdkTargetsPath Condition="'$(IsCrossTargetingBuild)' != 'true'">$(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets</RazorSdkTargetsPath>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition="'$(_RazorSdkImportsMicrosoftNetSdk)' == 'true'" />
<PropertyGroup Condition="'$(RazorSdkCurrentVersionTargets)' == ''">
<RazorSdkCurrentVersionTargets Condition="'$(IsCrossTargetingBuild)' == 'true'">$(MSBuildThisFileDirectory)..\buildMultiTargeting\Sdk.Razor.CurrentVersion.MultiTargeting.targets</RazorSdkCurrentVersionTargets>
<RazorSdkCurrentVersionTargets Condition="'$(IsCrossTargetingBuild)' != 'true'">$(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets</RazorSdkCurrentVersionTargets>
</PropertyGroup>
<Import Project="$(RazorSdkTargetsPath)" />
<Import Project="$(RazorSdkCurrentVersionTargets)" />
</Project>

View File

@ -0,0 +1,48 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.DesignTime.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<ItemGroup>
<!--
Defines the generic .NET Core 'Razor' capability.
Note that we don't define any capabilities here that depend on the version of the runtime/toolset
in use by the project. Those capabilities are defined by the relevant runtime packages so that
we use the lack of the capability to detect downlevel scenarios.
-->
<ProjectCapability Include="DotNetCoreRazor"/>
</ItemGroup>
<!--
WebSdk imports these capabilities for nesting in DotNetCoreWeb projects.
Conditinally import these capabilities if the project isn't targeting the WebSdk.
-->
<ItemGroup Condition="'$(UsingMicrosoftNETSdkWeb)'==''">
<ProjectCapability Include="SupportHierarchyContextSvc" />
<ProjectCapability Include="DynamicDependentFile" />
<ProjectCapability Include="DynamicFileNesting" />
</ItemGroup>
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorConfiguration.xaml">
<Context>File</Context>
</PropertyPageSchema>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorExtension.xaml">
<Context>File</Context>
</PropertyPageSchema>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorGeneral.xaml">
<Context>Project</Context>
</PropertyPageSchema>
</ItemGroup>
</Project>

View File

@ -10,10 +10,8 @@ Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<Import Project="Sdk.Razor.CurrentVersion.props" />
<PropertyGroup>
<!-- Redirect the SDK to use the targets from the current package -->
<RazorSdkTargetsPath>$(MSBuildThisFileDirectory)Sdk.Razor.CurrentVersion.targets</RazorSdkTargetsPath>
<RazorSdkCurrentVersionProps>$(MSBuildThisFileDirectory)Sdk.Razor.CurrentVersion.props</RazorSdkCurrentVersionProps>
<RazorSdkCurrentVersionTargets>$(MSBuildThisFileDirectory)Sdk.Razor.CurrentVersion.targets</RazorSdkCurrentVersionTargets>
</PropertyGroup>
</Project>

View File

@ -1,14 +0,0 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<Import Project="Sdk.Razor.CurrentVersion.targets" />
</Project>

View File

@ -18,6 +18,12 @@ Copyright (c) .NET Foundation. All rights reserved.
Default properties for common Razor SDK behavior.
-->
<PropertyGroup>
<!--
Set to true to automatically include certain file types, such as .cshtml files, as content in the project.
When referenced via Microsoft.NET.Sdk.Web, this additionally includes all files under wwwroot, and any config files.
-->
<EnableDefaultContentItems Condition="'$(EnableDefaultContentItems)'==''">true</EnableDefaultContentItems>
<!--
Set to true to automatically include Razor (.cshtml) files in @(RazorGenerate) from @(Content).
-->
@ -46,37 +52,24 @@ Copyright (c) .NET Foundation. All rights reserved.
<RazorCompileToolset>Implicit</RazorCompileToolset>
<!--
Set to true to allow a Razor code generation to use a persistent build server process.
Configures whether all Razor content items (.cshtml files) will be marked to be included in the produced NuGet package as content.
All Content items are included in a NuGet package as content files. This setting can be used to control this behavior for Razor content items.
-->
<UseRazorBuildServer Condition="'$(UseRazorBuildServer)'==''">false</UseRazorBuildServer>
<IncludeRazorContentInPack Condition="'$(IncludeRazorContentInPack)'==''">false</IncludeRazorContentInPack>
<!--
Set to false to disable Razor code generation from using a persistent build server process.
-->
<UseRazorBuildServer Condition="'$(UseRazorBuildServer)'==''">true</UseRazorBuildServer>
</PropertyGroup>
<ItemGroup Condition="'$(_WebSdkHeartsRazorSdk)' == 'true' AND '$(EnableDefaultItems)' == 'true' And '$(EnableDefaultContentItems)' == 'true'">
<Content Include="**\*.cshtml" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)" />
<ItemGroup Condition="'$(EnableDefaultItems)' == 'true' And '$(EnableDefaultContentItems)' == 'true'">
<Content Include="**\*.cshtml" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<None Remove="**\*.cshtml" />
</ItemGroup>
<ItemGroup>
<!--
Defines the generic .NET Core 'Razor' capability.
Note that we don't define any capabilities here that depend on the version of the runtime/toolset
in use by the project. Those capabilities are defined by the relevant runtime packages so that
we use the lack of the capability to detect downlevel scenarios.
-->
<ProjectCapability Include="DotNetCoreRazor"/>
</ItemGroup>
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorConfiguration.xaml">
<Context>File</Context>
</PropertyPageSchema>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorExtension.xaml">
<Context>File</Context>
</PropertyPageSchema>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorGeneral.xaml">
<Context>Project</Context>
</PropertyPageSchema>
</ItemGroup>
</Project>

View File

@ -34,13 +34,13 @@ Copyright (c) .NET Foundation. All rights reserved.
ResolveRazorGenerateInputs;
AssignRazorGenerateTargetPaths;
ResolveAssemblyReferenceRazorGenerateInputs;
_EnsureRazorCompilerReferenced;
_CheckForMissingRazorCompiler;
ResolveTagHelperRazorGenerateInputs
</PrepareForRazorGenerateDependsOn>
<RazorGenerateDependsOn>
PrepareForRazorGenerate;
_EnsureRazorCompilerReferenced;
_CheckForMissingRazorCompiler;
RazorCoreGenerate
</RazorGenerateDependsOn>
@ -50,20 +50,36 @@ Copyright (c) .NET Foundation. All rights reserved.
</PrepareForRazorCompileDependsOn>
<ResolveRazorCompileInputsDependsOn>
ResolveRazorEmbeddedResources
ResolveRazorEmbeddedResources
</ResolveRazorCompileInputsDependsOn>
<RazorCompileDependsOn>
PrepareForRazorCompile;
RazorCoreCompile
</RazorCompileDependsOn>
<BuiltProjectOutputGroupDependsOn>
$(BuiltProjectOutputGroupDependsOn);
_RazorAddBuiltProjectOutputGroupOutput
</BuiltProjectOutputGroupDependsOn>
<DebugSymbolsProjectOutputGroupDependsOn>
$(DebugSymbolsProjectOutputGroupDependsOn);
_RazorAddDebugSymbolsProjectOutputGroupOutput
</DebugSymbolsProjectOutputGroupDependsOn>
<PrepareForBuildDependsOn>
_InitializePreserveCompilationContext;
$(PrepareForBuildDependsOn)
</PrepareForBuildDependsOn>
</PropertyGroup>
<!--
Default values for properties that affect Razor targets to the standard build lifecycle.
-->
<PropertyGroup Condition="'$(RazorCompileOnBuild)'==''">
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnBuild>true</RazorCompileOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(RazorCompileOnPublish)'==''">
@ -83,9 +99,12 @@ Copyright (c) .NET Foundation. All rights reserved.
<PropertyGroup>
<!-- Output directory used for generated files -->
<RazorGenerateIntermediateOutputPath Condition="'$(RazorGenerateIntermediateOutputPath)'==''">$(IntermediateOutputPath)Razor\</RazorGenerateIntermediateOutputPath>
<!-- Suffix appended to $(TargetName) to produce $(RazorTargetName), the name of the assembly produced by Razor -->
<RazorTargetNameSuffix Condition="'$(RazorTargetNameSuffix)' == ''">.Razor</RazorTargetNameSuffix>
<!-- File name (without extension) of the assembly produced by Razor -->
<RazorTargetName Condition="'$(RazorTargetName)'==''">$(TargetName).PrecompiledViews</RazorTargetName>
<RazorTargetName Condition="'$(RazorTargetName)'==''">$(TargetName)$(RazorTargetNameSuffix)</RazorTargetName>
<!--
The compatibility zone - these properties were provided by the MVC Precompilation tool and they
@ -129,7 +148,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</PropertyGroup>
<!-- Resolve the toolset to use -->
<PropertyGroup Condition="'$(_WebSdkHeartsRazorSdk)' == 'true'">
<PropertyGroup>
<!-- Default value for the property 'MvcRazorCompileOnPublish' is empty. If it has been explicitly enabled, continue using precompilation. -->
<ResolvedRazorCompileToolset Condition="'$(MvcRazorCompileOnPublish)' == 'true'">PrecompilationTool</ResolvedRazorCompileToolset>
@ -154,6 +173,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<RazorIntermediateAssembly Condition="'$(RazorIntermediateAssembly)'==''" Include="$(IntermediateOutputPath)$(RazorTargetName).dll" />
<!-- Used in Compilation.targets -->
<_RazorDebugSymbolsIntermediatePath Condition="'$(_RazorDebugSymbolsProduced)'=='true'" Include="$(IntermediateOutputPath)$(RazorTargetName).pdb" />
<!-- Add all cshtml files to UpToDateCheckInput - a collection used by the IDE's project system to determine if a project needs to be rebuilt -->
<UpToDateCheckInput Condition="'$(RazorCompileOnBuild)'=='true'" Include="@(Content->WithMetadataValue('Extension', '.cshtml'))" />
</ItemGroup>
<!--
@ -182,6 +203,16 @@ Copyright (c) .NET Foundation. All rights reserved.
<Target Name="RazorCompile" DependsOnTargets="$(RazorCompileDependsOn)">
</Target>
<!--
PreserveCompilationContext needs to enabled by default only for "applications" (OutputType = exe) which have one or more Razor files.
We cannot only inspect ItemGroups globally, therefore setting this must be done inside a target. We wire this up to run before Build.
-->
<Target Name="_InitializePreserveCompilationContext">
<PropertyGroup Condition="'$(PreserveCompilationContext)' == '' AND '$(OutputType)' == 'exe' AND '@(Content->AnyHaveMetadataValue('Extension', '.cshtml'))' == 'true'">
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
</Target>
<!--
Computes the applicable @(ResolvedRazorConfiguration) and @(ResolvedRazorExtension) items that match the project's
configuration.
@ -316,16 +347,22 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<Target
Name="_RazorAddBuiltProjectOutputGroupOutput"
DependsOnTargets="ResolveRazorGenerateInputs"
BeforeTargets="BuiltProjectOutputGroup"
DependsOnTargets="_ResolveRazorTargetPath;ResolveRazorGenerateInputs"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
<PropertyGroup>
<RazorOutputPath Condition="'$(RazorOutputPath)'==''">$([MSBuild]::EnsureTrailingSlash('$(OutDir)'))</RazorOutputPath>
</PropertyGroup>
<ItemGroup Condition="'@(RazorGenerate)'!= ''">
<BuiltProjectOutputGroupOutput Include="@(RazorIntermediateAssembly)" FinalOutputPath="$(RazorOutputPath)$(RazorTargetName).dll" />
<BuiltProjectOutputGroupOutput Include="%(RazorIntermediateAssembly.FullPath)" FinalOutputPath="$(RazorTargetPath)" />
</ItemGroup>
</Target>
<Target
Name="_RazorAddDebugSymbolsProjectOutputGroupOutput"
DependsOnTargets="_ResolveRazorTargetPath;ResolveRazorGenerateInputs"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
<ItemGroup Condition="Exists('@(_RazorDebugSymbolsIntermediatePath)')">
<DebugSymbolsProjectOutputGroupOutput Include="%(_RazorDebugSymbolsIntermediatePath.FullPath)" FinalOutputPath="$(RazorTargetDir)$(RazorTargetName).pdb" />
</ItemGroup>
</Target>
@ -393,18 +430,14 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<Target
Name="_RazorCopyFilesToOutputDirectory"
DependsOnTargets="RazorCompile"
DependsOnTargets="_ResolveRazorTargetPath;RazorCompile"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
<PropertyGroup>
<RazorOutputPath Condition="'$(RazorOutputPath)'==''">$([MSBuild]::EnsureTrailingSlash('$(OutDir)'))</RazorOutputPath>
</PropertyGroup>
<!-- Copy the Razor dll -->
<Copy
SourceFiles="@(RazorIntermediateAssembly)"
DestinationFolder="$(RazorOutputPath)"
DestinationFiles="$(RazorTargetPath)"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
@ -478,11 +511,29 @@ Copyright (c) .NET Foundation. All rights reserved.
</Target>
<Target Name="_EnsureRazorCompilerReferenced" Condition="'$(IsRazorCompilerReferenced)' != 'true'">
<Error Text="A reference to Microsoft.AspNetCore.Razor.Design is required for this target to succeed. See todo:fwlink for instructions on how to resolve this." />
<Target Name="_CheckForMissingRazorCompiler" Condition="'$(IsRazorCompilerReferenced)' != 'true'">
<Error
Text="A PackageReference for 'Microsoft.AspNetCore.Razor.Design' was not included in your project. This package is required to compile Razor files. Typically, a
transitive reference to 'Microsoft.AspNetCore.Razor.Design' and references required to compile Razor files are obtained by adding a PackageReference
for 'Microsoft.AspNetCore.Mvc' in your project. For more information, see https://go.microsoft.com/fwlink/?linkid=868374." />
</Target>
<Target Name="_ResolveRazorTargetPath">
<PropertyGroup>
<RazorOutputPath Condition="'$(RazorOutputPath)'==''">$([MSBuild]::EnsureTrailingSlash('$(OutDir)'))</RazorOutputPath>
<RazorTargetDir>$([MSBuild]::Escape($([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(RazorOutputPath)'))'))))))</RazorTargetDir>
<!-- Example, c:\MyProjects\MyProject\bin\debug\MyAssembly.Views.dll -->
<RazorTargetPath Condition=" '$(RazorTargetPath)' == '' ">$(RazorTargetDir)$(RazorTargetName).dll</RazorTargetPath>
</PropertyGroup>
</Target>
<PropertyGroup Condition="'$(RazorDesignTimeTargets)'==''">
<RazorDesignTimeTargets>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\Razor\Microsoft.NET.Sdk.Razor.DesignTime.targets</RazorDesignTimeTargets>
<RazorDesignTimeTargets Condition="!Exists('$(RazorDesignTimeTargets)')">$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.Razor.DesignTime.targets</RazorDesignTimeTargets>
</PropertyGroup>
<Import Project="$(RazorDesignTimeTargets)" />
<!--
This is a hook to import a set of targets after the Razor targets. By default this is unused.
-->

View File

@ -1,6 +1,6 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.targets
Microsoft.Net.Sdk.Razor.props
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
@ -10,5 +10,8 @@ Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<Import Project="Sdk.Razor.CurrentVersion.MultiTargeting.targets" />
<PropertyGroup>
<RazorSdkCurrentVersionProps>$(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.props</RazorSdkCurrentVersionProps>
<RazorSdkCurrentVersionTargets>$(MSBuildThisFileDirectory)Sdk.Razor.CurrentVersion.MultiTargeting.targets</RazorSdkCurrentVersTargetsFile>
</PropertyGroup>
</Project>

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
@ -18,10 +19,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
private MainThreadState _main;
private BackgroundThread _bg;
public BackgroundParser(RazorTemplateEngine templateEngine, string filePath)
public BackgroundParser(RazorProjectEngine projectEngine, string filePath, string projectDirectory)
{
_main = new MainThreadState(filePath);
_bg = new BackgroundThread(_main, templateEngine, filePath);
_bg = new BackgroundThread(_main, projectEngine, filePath, projectDirectory);
_main.ResultsReady += (sender, args) => OnResultsReady(args);
}
@ -233,22 +234,25 @@ namespace Microsoft.VisualStudio.Editor.Razor
private class BackgroundThread : ThreadStateBase
{
private readonly string _filePath;
private readonly string _relativeFilePath;
private readonly string _projectDirectory;
private MainThreadState _main;
private Thread _backgroundThread;
private CancellationToken _shutdownToken;
private RazorTemplateEngine _templateEngine;
private string _filePath;
private RazorProjectEngine _projectEngine;
private RazorSyntaxTree _currentSyntaxTree;
private IList<Edit> _previouslyDiscarded = new List<Edit>();
public BackgroundThread(MainThreadState main, RazorTemplateEngine templateEngine, string fileName)
public BackgroundThread(MainThreadState main, RazorProjectEngine projectEngine, string filePath, string projectDirectory)
{
// Run on MAIN thread!
_main = main;
_shutdownToken = _main.CancelToken;
_templateEngine = templateEngine;
_filePath = fileName;
_projectEngine = projectEngine;
_filePath = filePath;
_relativeFilePath = GetNormalizedRelativeFilePath(filePath, projectDirectory);
_projectDirectory = projectDirectory;
_backgroundThread = new Thread(WorkerLoop);
SetThreadId(_backgroundThread.ManagedThreadId);
}
@ -262,8 +266,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
// **** BACKGROUND THREAD ****
private void WorkerLoop()
{
var fileNameOnly = Path.GetFileName(_filePath);
try
{
EnsureOnThread();
@ -347,14 +349,31 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
EnsureOnThread();
var sourceDocument = new TextSnapshotSourceDocument(snapshot, _filePath);
var imports = _templateEngine.GetImports(_filePath);
var projectItem = new TextSnapshotProjectItem(snapshot, _projectDirectory, _relativeFilePath, _filePath);
var codeDocument = _projectEngine.ProcessDesignTime(projectItem);
var codeDocument = RazorCodeDocument.Create(sourceDocument, imports);
_templateEngine.GenerateCode(codeDocument);
return codeDocument;
}
private string GetNormalizedRelativeFilePath(string filePath, string projectDirectory)
{
if (filePath.StartsWith(projectDirectory, StringComparison.OrdinalIgnoreCase))
{
filePath = filePath.Substring(projectDirectory.Length);
}
if (filePath.Length > 1)
{
filePath = filePath.Replace('\\', '/');
if (filePath[0] != '/')
{
filePath = "/" + filePath;
}
}
return filePath;
}
}
private class WorkParcel

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
@ -15,7 +16,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
private readonly FileChangeTrackerFactory _fileChangeTrackerFactory;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly ErrorReporter _errorReporter;
private readonly RazorTemplateEngineFactoryService _templateEngineFactoryService;
private readonly RazorProjectEngineFactoryService _projectEngineFactoryService;
private readonly Dictionary<string, ImportTracker> _importTrackerCache;
public override event EventHandler<ImportChangedEventArgs> Changed;
@ -24,7 +25,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
ForegroundDispatcher foregroundDispatcher,
ErrorReporter errorReporter,
FileChangeTrackerFactory fileChangeTrackerFactory,
RazorTemplateEngineFactoryService templateEngineFactoryService)
RazorProjectEngineFactoryService projectEngineFactoryService)
{
if (foregroundDispatcher == null)
{
@ -41,15 +42,15 @@ namespace Microsoft.VisualStudio.Editor.Razor
throw new ArgumentNullException(nameof(fileChangeTrackerFactory));
}
if (templateEngineFactoryService == null)
if (projectEngineFactoryService == null)
{
throw new ArgumentNullException(nameof(templateEngineFactoryService));
throw new ArgumentNullException(nameof(projectEngineFactoryService));
}
_foregroundDispatcher = foregroundDispatcher;
_errorReporter = errorReporter;
_fileChangeTrackerFactory = fileChangeTrackerFactory;
_templateEngineFactoryService = templateEngineFactoryService;
_projectEngineFactoryService = projectEngineFactoryService;
_importTrackerCache = new Dictionary<string, ImportTracker>(StringComparer.OrdinalIgnoreCase);
}
@ -115,10 +116,17 @@ namespace Microsoft.VisualStudio.Editor.Razor
private IEnumerable<RazorProjectItem> GetImportItems(VisualStudioDocumentTracker tracker)
{
var projectDirectory = Path.GetDirectoryName(tracker.ProjectPath);
var templateEngine = _templateEngineFactoryService.Create(projectDirectory, _ => { });
var imports = templateEngine.GetImportItems(tracker.FilePath);
var projectEngine = _projectEngineFactoryService.Create(projectDirectory, _ => { });
var trackerItem = projectEngine.FileSystem.GetItem(tracker.FilePath);
var importFeature = projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
return imports;
// There should always be an import feature unless someone has misconfigured their RazorProjectEngine.
// In that case once we attempt to parse the Razor file we'll explode and give the a user a decent
// error message; for now, lets just be extra protective and assume 0 imports to not give a bad error.
var importItems = importFeature?.GetImports(trackerItem) ?? Enumerable.Empty<RazorProjectItem>();
var physicalImports = importItems.Where(import => import.FilePath != null);
return physicalImports;
}
private void OnChanged(ImportTracker importTracker, FileChangeKind changeKind)

View File

@ -35,13 +35,13 @@ namespace Microsoft.VisualStudio.Editor.Razor
var errorReporter = languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>();
var fileChangeTrackerFactory = languageServices.GetRequiredService<FileChangeTrackerFactory>();
var templateEngineFactoryService = languageServices.GetRequiredService<RazorTemplateEngineFactoryService>();
var projectEngineFactoryService = languageServices.GetRequiredService<RazorProjectEngineFactoryService>();
return new DefaultImportDocumentManager(
_foregroundDispatcher,
errorReporter,
fileChangeTrackerFactory,
templateEngineFactoryService);
projectEngineFactoryService);
}
}
}

Some files were not shown because too many files have changed in this diff Show More