Use new Razor in MVC
This commit is contained in:
parent
7449ffad74
commit
84d2e027f5
30
Mvc.sln
30
Mvc.sln
|
|
@ -125,6 +125,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SimpleWebSite", "test\WebSi
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SecurityWebSite", "test\WebSites\SecurityWebSite\SecurityWebSite.xproj", "{D28CAC79-7004-4B69-993B-EDEB4653BFA8}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.RazorPages", "src\Microsoft.AspNetCore.Mvc.RazorPages\Microsoft.AspNetCore.Mvc.RazorPages.xproj", "{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.RazorPages.Test", "test\Microsoft.AspNetCore.Mvc.RazorPages.Test\Microsoft.AspNetCore.Mvc.RazorPages.Test.xproj", "{0AB46520-F441-4E01-B444-08F4D23F8B1B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -741,6 +745,30 @@ Global
|
|||
{D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -802,5 +830,7 @@ Global
|
|||
{14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
|
||||
{396B40D7-AC70-49A7-B33C-ED42129FEBE3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{D28CAC79-7004-4B69-993B-EDEB4653BFA8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Diagnostics": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Mvc": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Razor.Tools": {
|
||||
"type": "build",
|
||||
"version": "1.1.0-preview4-final"
|
||||
},
|
||||
"Microsoft.AspNetCore.Razor": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Razor.Runtime": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.StaticFiles": "1.2.0-*",
|
||||
|
|
@ -26,7 +24,6 @@
|
|||
]
|
||||
},
|
||||
"tools": {
|
||||
"Microsoft.AspNetCore.Razor.Tools": "1.1.0-preview4-final",
|
||||
"Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information for the <see cref="AspNetCore.Razor.TagHelpers.ITagHelper"/> attribute code
|
||||
/// Contains information for the tag helper attribute code
|
||||
/// generation process.
|
||||
/// </summary>
|
||||
public class GeneratedTagHelperAttributeContext
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains necessary information for the view component <see cref="AspNetCore.Razor.TagHelpers.TagHelper"/> code generation process.
|
||||
/// Contains necessary information for the view component tag helper code generation process.
|
||||
/// </summary>
|
||||
public class GeneratedViewComponentTagHelperContext
|
||||
{
|
||||
|
|
@ -1,93 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helper methods for dealing with Chunks
|
||||
/// </summary>
|
||||
public static class ChunkHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Token that is replaced by the model name in <c>@inherits</c> and <c>@inject</c>
|
||||
/// chunks as part of <see cref="ChunkInheritanceUtility"/>.
|
||||
/// </summary>
|
||||
public static readonly string TModelToken = "TModel";
|
||||
private static readonly string TModelReplaceToken = $"<{TModelToken}>";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ModelChunk"/> used to determine the model name for the page generated
|
||||
/// using the specified <paramref name="chunkTree"/>
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="ChunkTree"/> to scan for <see cref="ModelChunk"/>s in.</param>
|
||||
/// <returns>The last <see cref="ModelChunk"/> in the <see cref="ChunkTree"/> if found, <c>null</c> otherwise.
|
||||
/// </returns>
|
||||
public static ModelChunk GetModelChunk(ChunkTree chunkTree)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
// If there's more than 1 model chunk there will be a Razor error BUT we want intellisense to show up on
|
||||
// the current model chunk that the user is typing.
|
||||
return chunkTree
|
||||
.Children
|
||||
.OfType<ModelChunk>()
|
||||
.LastOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type name of the Model specified via a <see cref="ModelChunk"/> in the
|
||||
/// <paramref name="chunkTree"/> if specified or the default model type.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="ChunkTree"/> to scan for <see cref="ModelChunk"/>s in.</param>
|
||||
/// <param name="defaultModelName">The <see cref="Type"/> name of the default model.</param>
|
||||
/// <returns>The model type name for the generated page.</returns>
|
||||
public static string GetModelTypeName(
|
||||
ChunkTree chunkTree,
|
||||
string defaultModelName)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (defaultModelName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaultModelName));
|
||||
}
|
||||
|
||||
var modelChunk = GetModelChunk(chunkTree);
|
||||
return modelChunk != null ? modelChunk.ModelType : defaultModelName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with the <TModel> token replaced with the value specified in
|
||||
/// <paramref name="modelName"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to replace the token in.</param>
|
||||
/// <param name="modelName">The model name to replace with.</param>
|
||||
/// <returns>A string with the token replaced.</returns>
|
||||
public static string ReplaceTModel(
|
||||
string value,
|
||||
string modelName)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (modelName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelName));
|
||||
}
|
||||
|
||||
return value.Replace(TModelReplaceToken, modelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +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 Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility type for supporting inheritance of directives into a page from applicable <c>_ViewImports</c> pages.
|
||||
/// </summary>
|
||||
public class ChunkInheritanceUtility
|
||||
{
|
||||
private readonly MvcRazorHost _razorHost;
|
||||
private readonly IReadOnlyList<Chunk> _defaultInheritedChunks;
|
||||
private readonly IChunkTreeCache _chunkTreeCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
|
||||
/// </summary>
|
||||
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse <c>_ViewImports</c> pages.</param>
|
||||
/// <param name="chunkTreeCache"><see cref="IChunkTreeCache"/> that caches <see cref="ChunkTree"/> instances.
|
||||
/// </param>
|
||||
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
|
||||
public ChunkInheritanceUtility(
|
||||
MvcRazorHost razorHost,
|
||||
IChunkTreeCache chunkTreeCache,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
{
|
||||
if (razorHost == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorHost));
|
||||
}
|
||||
|
||||
if (chunkTreeCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTreeCache));
|
||||
}
|
||||
|
||||
if (defaultInheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaultInheritedChunks));
|
||||
}
|
||||
|
||||
_razorHost = razorHost;
|
||||
_defaultInheritedChunks = defaultInheritedChunks;
|
||||
_chunkTreeCache = chunkTreeCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered <see cref="IReadOnlyList{ChunkTreeResult}"/> of parsed <see cref="ChunkTree"/>s and
|
||||
/// file paths for each <c>_ViewImports</c> that is applicable to the page located at
|
||||
/// <paramref name="pagePath"/>. The list is ordered so that the <see cref="ChunkTreeResult"/>'s
|
||||
/// <see cref="ChunkTreeResult.ChunkTree"/> for the <c>_ViewImports</c> closest to the
|
||||
/// <paramref name="pagePath"/> in the file system appears first.
|
||||
/// </summary>
|
||||
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
|
||||
/// <returns>A <see cref="IReadOnlyList{ChunkTreeResult}"/> of parsed <c>_ViewImports</c>
|
||||
/// <see cref="ChunkTree"/>s and their file paths.</returns>
|
||||
/// <remarks>
|
||||
/// The resulting <see cref="IReadOnlyList{ChunkTreeResult}"/> is ordered so that the result
|
||||
/// for a _ViewImport closest to the application root appears first and the _ViewImport
|
||||
/// closest to the page appears last i.e.
|
||||
/// [ /_ViewImport, /Views/_ViewImport, /Views/Home/_ViewImport ]
|
||||
/// </remarks>
|
||||
public virtual IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults(string pagePath)
|
||||
{
|
||||
if (pagePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pagePath));
|
||||
}
|
||||
|
||||
var inheritedChunkTreeResults = new List<ChunkTreeResult>();
|
||||
var templateEngine = new RazorTemplateEngine(_razorHost);
|
||||
foreach (var viewImportsPath in ViewHierarchyUtility.GetViewImportsLocations(pagePath))
|
||||
{
|
||||
// viewImportsPath contains the app-relative path of the _ViewImports.
|
||||
// Since the parsing of a _ViewImports would cause parent _ViewImports to be parsed
|
||||
// we need to ensure the paths are app-relative to allow the GetGlobalFileLocations
|
||||
// for the current _ViewImports to succeed.
|
||||
var chunkTree = _chunkTreeCache.GetOrAdd(
|
||||
viewImportsPath,
|
||||
fileInfo => ParseViewFile(
|
||||
templateEngine,
|
||||
fileInfo,
|
||||
viewImportsPath));
|
||||
|
||||
if (chunkTree != null)
|
||||
{
|
||||
var result = new ChunkTreeResult(chunkTree, viewImportsPath);
|
||||
inheritedChunkTreeResults.Insert(0, result);
|
||||
}
|
||||
}
|
||||
|
||||
return inheritedChunkTreeResults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges <see cref="Chunk"/> inherited by default and <see cref="ChunkTree"/> instances produced by parsing
|
||||
/// <c>_ViewImports</c> files into the specified <paramref name="chunkTree"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="ChunkTree"/> to merge in to.</param>
|
||||
/// <param name="inheritedChunkTrees"><see cref="IReadOnlyList{ChunkTree}"/> inherited from <c>_ViewImports</c>
|
||||
/// files.</param>
|
||||
/// <param name="defaultModel">The default model <see cref="Type"/> name.</param>
|
||||
public void MergeInheritedChunkTrees(
|
||||
ChunkTree chunkTree,
|
||||
IReadOnlyList<ChunkTree> inheritedChunkTrees,
|
||||
string defaultModel)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (inheritedChunkTrees == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunkTrees));
|
||||
}
|
||||
|
||||
var chunkMergers = GetChunkMergers(chunkTree, defaultModel);
|
||||
// We merge chunks into the ChunkTree in two passes. In the first pass, we traverse the ChunkTree visiting
|
||||
// a mapped IChunkMerger for types that are registered.
|
||||
foreach (var chunk in chunkTree.Children)
|
||||
{
|
||||
foreach (var merger in chunkMergers)
|
||||
{
|
||||
merger.VisitChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
var inheritedChunks = _defaultInheritedChunks.Concat(
|
||||
inheritedChunkTrees.SelectMany(tree => tree.Children)).ToArray();
|
||||
|
||||
foreach (var merger in chunkMergers)
|
||||
{
|
||||
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
|
||||
}
|
||||
}
|
||||
|
||||
private static IChunkMerger[] GetChunkMergers(ChunkTree chunkTree, string defaultModel)
|
||||
{
|
||||
var modelType = ChunkHelper.GetModelTypeName(chunkTree, defaultModel);
|
||||
return new IChunkMerger[]
|
||||
{
|
||||
new UsingChunkMerger(),
|
||||
new InjectChunkMerger(modelType),
|
||||
new SetBaseTypeChunkMerger(modelType)
|
||||
};
|
||||
}
|
||||
|
||||
private static ChunkTree ParseViewFile(
|
||||
RazorTemplateEngine engine,
|
||||
IFileInfo fileInfo,
|
||||
string viewImportsPath)
|
||||
{
|
||||
using (var stream = fileInfo.CreateReadStream())
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
var parseResults = engine.ParseTemplate(streamReader, viewImportsPath);
|
||||
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
|
||||
var language = engine.Host.CodeLanguage;
|
||||
var chunkGenerator = language.CreateChunkGenerator(
|
||||
className,
|
||||
engine.Host.DefaultNamespace,
|
||||
viewImportsPath,
|
||||
engine.Host);
|
||||
chunkGenerator.Visit(parseResults);
|
||||
|
||||
// Rewrite the location of inherited chunks so they point to the global import file.
|
||||
var chunkTree = chunkGenerator.Context.ChunkTreeBuilder.Root;
|
||||
foreach (var chunk in chunkTree.Children)
|
||||
{
|
||||
chunk.Start = new SourceLocation(
|
||||
viewImportsPath,
|
||||
chunk.Start.AbsoluteIndex,
|
||||
chunk.Start.LineIndex,
|
||||
chunk.Start.CharacterIndex);
|
||||
}
|
||||
|
||||
return chunkTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains <see cref="AspNetCore.Razor.Chunks.ChunkTree"/> information.
|
||||
/// </summary>
|
||||
public class ChunkTreeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ChunkTreeResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="AspNetCore.Razor.Chunks.ChunkTree"/> generated from the file at the
|
||||
/// given <paramref name="filePath"/>.</param>
|
||||
/// <param name="filePath">The path to the file that generated the given <paramref name="chunkTree"/>.</param>
|
||||
public ChunkTreeResult(ChunkTree chunkTree, string filePath)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
ChunkTree = chunkTree;
|
||||
FilePath = filePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AspNetCore.Razor.Chunks.ChunkTree"/> generated from the file at <see cref="FilePath"/>.
|
||||
/// </summary>
|
||||
public ChunkTree ChunkTree { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to the file that generated the <see cref="ChunkTree"/>.
|
||||
/// </summary>
|
||||
public string FilePath { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IChunkTreeCache"/>.
|
||||
/// </summary>
|
||||
public class DefaultChunkTreeCache : IChunkTreeCache
|
||||
{
|
||||
private static readonly MemoryCacheOptions MemoryCacheOptions = new MemoryCacheOptions
|
||||
{
|
||||
CompactOnMemoryPressure = false
|
||||
};
|
||||
private static readonly TimeSpan SlidingExpirationDuration = TimeSpan.FromMinutes(1);
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly IMemoryCache _chunkTreeCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DefaultChunkTreeCache"/>.
|
||||
/// </summary>
|
||||
/// <param name="fileProvider">The application's <see cref="IFileProvider"/>.</param>
|
||||
public DefaultChunkTreeCache(IFileProvider fileProvider)
|
||||
: this(fileProvider, MemoryCacheOptions)
|
||||
{
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal DefaultChunkTreeCache(
|
||||
IFileProvider fileProvider,
|
||||
MemoryCacheOptions options)
|
||||
{
|
||||
_fileProvider = fileProvider;
|
||||
_chunkTreeCache = new MemoryCache(options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ChunkTree GetOrAdd(
|
||||
string pagePath,
|
||||
Func<IFileInfo, ChunkTree> getChunkTree)
|
||||
{
|
||||
if (pagePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pagePath));
|
||||
}
|
||||
|
||||
if (getChunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(getChunkTree));
|
||||
}
|
||||
|
||||
ChunkTree chunkTree;
|
||||
if (!_chunkTreeCache.TryGetValue(pagePath, out chunkTree))
|
||||
{
|
||||
// GetOrAdd is invoked for each _ViewImport that might potentially exist in the path.
|
||||
// We can avoid performing file system lookups for files that do not exist by caching
|
||||
// negative results and adding a Watch for that file.
|
||||
|
||||
var options = new MemoryCacheEntryOptions()
|
||||
.AddExpirationToken(_fileProvider.Watch(pagePath))
|
||||
.SetSlidingExpiration(SlidingExpirationDuration);
|
||||
|
||||
var file = _fileProvider.GetFileInfo(pagePath);
|
||||
chunkTree = file.Exists ? getChunkTree(file) : null;
|
||||
|
||||
_chunkTreeCache.Set(pagePath, chunkTree, options);
|
||||
}
|
||||
|
||||
return chunkTree;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_chunkTreeCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for merging <see cref="Chunk"/> instances from _ViewStart files.
|
||||
/// </summary>
|
||||
public interface IChunkMerger
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits a <see cref="Chunk"/> from the <see cref="ChunkTree"/> to merge into.
|
||||
/// </summary>
|
||||
/// <param name="chunk">A <see cref="Chunk"/> from the tree.</param>
|
||||
void VisitChunk(Chunk chunk);
|
||||
|
||||
/// <summary>
|
||||
/// Merges an inherited <see cref="Chunk"/> into the <see cref="ChunkTree"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="ChunkTree"/> to merge into.</param>
|
||||
/// <param name="inheritedChunks">The <see cref="IReadOnlyList{Chunk}"/>s to merge.</param>
|
||||
void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList<Chunk> inheritedChunks);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A cache for parsed <see cref="ChunkTree"/>s.
|
||||
/// </summary>
|
||||
public interface IChunkTreeCache : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Get an existing <see cref="ChunkTree"/>, or create and add a new one if it is
|
||||
/// not available in the cache or is expired.
|
||||
/// </summary>
|
||||
/// <param name="pagePath">The application relative path of the Razor page.</param>
|
||||
/// <param name="getChunkTree">A delegate that creates a new <see cref="ChunkTree"/>.</param>
|
||||
/// <returns>The <see cref="ChunkTree"/> if a file exists at <paramref name="pagePath"/>,
|
||||
/// <c>null</c> otherwise.</returns>
|
||||
/// <remarks>The resulting <see cref="ChunkTree"/> does not contain inherited chunks from _ViewStart or
|
||||
/// default inherited chunks.</remarks>
|
||||
ChunkTree GetOrAdd(string pagePath, Func<IFileInfo, ChunkTree> getChunkTree);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="InjectChunk"/> instances.
|
||||
/// </summary>
|
||||
public class InjectChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly HashSet<string> _addedMemberNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
private string _modelType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="InjectChunkMerger"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The model type to be used to replace <TModel> tokens.</param>
|
||||
public InjectChunkMerger(string modelType)
|
||||
{
|
||||
if (modelType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelType));
|
||||
}
|
||||
|
||||
_modelType = "<" + modelType + ">";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var injectChunk = chunk as InjectChunk;
|
||||
if (injectChunk != null)
|
||||
{
|
||||
injectChunk.TypeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
|
||||
_addedMemberNames.Add(injectChunk.MemberName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList<Chunk> inheritedChunks)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (inheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunks));
|
||||
}
|
||||
|
||||
for (var i = inheritedChunks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var injectChunk = inheritedChunks[i] as InjectChunk;
|
||||
if (injectChunk != null &&
|
||||
_addedMemberNames.Add(injectChunk.MemberName))
|
||||
{
|
||||
chunkTree.Children.Add(TransformChunk(injectChunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InjectChunk TransformChunk(InjectChunk injectChunk)
|
||||
{
|
||||
var typeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
|
||||
if (typeName != injectChunk.TypeName)
|
||||
{
|
||||
return new InjectChunk(typeName, injectChunk.MemberName)
|
||||
{
|
||||
Start = injectChunk.Start,
|
||||
Association = injectChunk.Association
|
||||
};
|
||||
}
|
||||
return injectChunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="SetBaseTypeChunk"/> instances.
|
||||
/// </summary>
|
||||
public class SetBaseTypeChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly string _modelType;
|
||||
private bool _isBaseTypeSet;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SetBaseTypeChunkMerger"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The type name of the model used by default.</param>
|
||||
public SetBaseTypeChunkMerger(string modelType)
|
||||
{
|
||||
_modelType = "<" + modelType + ">";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var setBaseTypeChunk = chunk as SetBaseTypeChunk;
|
||||
if (setBaseTypeChunk != null)
|
||||
{
|
||||
setBaseTypeChunk.TypeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
|
||||
_isBaseTypeSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList<Chunk> inheritedChunks)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (inheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunks));
|
||||
}
|
||||
|
||||
if (!_isBaseTypeSet)
|
||||
{
|
||||
for (var i = inheritedChunks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var baseTypeChunk = inheritedChunks[i] as SetBaseTypeChunk;
|
||||
if (baseTypeChunk != null)
|
||||
{
|
||||
chunkTree.Children.Add(TransformChunk(baseTypeChunk));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SetBaseTypeChunk TransformChunk(SetBaseTypeChunk setBaseTypeChunk)
|
||||
{
|
||||
var typeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
|
||||
if (typeName != setBaseTypeChunk.TypeName)
|
||||
{
|
||||
return new SetBaseTypeChunk
|
||||
{
|
||||
TypeName = typeName,
|
||||
Start = setBaseTypeChunk.Start,
|
||||
Association = setBaseTypeChunk.Association
|
||||
};
|
||||
}
|
||||
return setBaseTypeChunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="UsingChunk"/> instances.
|
||||
/// </summary>
|
||||
public class UsingChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly HashSet<string> _currentUsings = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var namespaceChunk = chunk as UsingChunk;
|
||||
if (namespaceChunk != null)
|
||||
{
|
||||
_currentUsings.Add(namespaceChunk.Namespace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList<Chunk> inheritedChunks)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (inheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunks));
|
||||
}
|
||||
|
||||
var namespaceChunks = inheritedChunks.OfType<UsingChunk>();
|
||||
foreach (var namespaceChunk in namespaceChunks)
|
||||
{
|
||||
if (_currentUsings.Add(namespaceChunk.Namespace))
|
||||
{
|
||||
chunkTree.Children.Add(namespaceChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +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.IO;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the contracts for a Razor host that parses Razor files and generates C# code.
|
||||
/// </summary>
|
||||
public interface IMvcRazorHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses and generates the contents of a Razor file represented by <paramref name="inputStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootRelativePath">The path of the relative to the root of the application.
|
||||
/// Used to generate line pragmas and calculate the class name of the generated type.</param>
|
||||
/// <param name="inputStream">A <see cref="Stream"/> that represents the Razor contents.</param>
|
||||
/// <returns>A <see cref="GeneratorResults"/> instance that represents the results of code generation.
|
||||
/// </returns>
|
||||
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
|
||||
|
||||
/// <summary>
|
||||
/// Represent the namespace the main entry class in the view.
|
||||
/// </summary>
|
||||
string DefaultNamespace { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class InjectChunk : Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the chunk for an @inject statement.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The type name of the property to be injected</param>
|
||||
/// <param name="propertyName">The member name of the property to be injected.</param>
|
||||
public InjectChunk(
|
||||
string typeName,
|
||||
string propertyName)
|
||||
{
|
||||
TypeName = typeName;
|
||||
MemberName = propertyName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type name of the property to be injected.
|
||||
/// </summary>
|
||||
public string TypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property to be injected.
|
||||
/// </summary>
|
||||
public string MemberName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +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 Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class InjectChunkVisitor : MvcCSharpCodeVisitor
|
||||
{
|
||||
private readonly string _injectAttribute;
|
||||
|
||||
public InjectChunkVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context,
|
||||
string injectAttributeName)
|
||||
: base(writer, context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (injectAttributeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(injectAttributeName));
|
||||
}
|
||||
|
||||
_injectAttribute = "[" + injectAttributeName + "]";
|
||||
}
|
||||
|
||||
public IList<InjectChunk> InjectChunks { get; } = new List<InjectChunk>();
|
||||
|
||||
protected override void Visit(InjectChunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
Writer.WriteLine(_injectAttribute);
|
||||
|
||||
// Some of the chunks that we visit are either InjectDescriptors that are added by default or
|
||||
// are chunks from _ViewStart files and are not associated with any Spans. Invoking
|
||||
// CreateExpressionMapping to produce line mappings on these chunks would fail. We'll skip
|
||||
// generating code mappings for these chunks. This makes sense since the chunks do not map
|
||||
// to any code in the current view.
|
||||
if (Context.Host.DesignTimeMode && chunk.Association != null)
|
||||
{
|
||||
Writer.WriteLine("public");
|
||||
|
||||
var code = string.IsNullOrEmpty(chunk.MemberName) ?
|
||||
chunk.TypeName :
|
||||
chunk.TypeName + " " + chunk.MemberName;
|
||||
var csharpVisitor = new CSharpCodeVisitor(Writer, Context);
|
||||
csharpVisitor.CreateExpressionCodeMapping(code, chunk);
|
||||
Writer.WriteLine("{ get; private set; }");
|
||||
}
|
||||
else
|
||||
{
|
||||
Writer.Write("public ")
|
||||
.Write(chunk.TypeName)
|
||||
.Write(" ")
|
||||
.Write(chunk.MemberName)
|
||||
.WriteLine(" { get; private set; }");
|
||||
}
|
||||
|
||||
InjectChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public static class InjectDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("inject").AddType().AddMember().Build();
|
||||
|
||||
public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new Pass());
|
||||
return builder;
|
||||
}
|
||||
|
||||
private class Pass : IRazorIRPass
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public int Order => RazorIRPass.DirectiveClassifierOrder;
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(irDocument);
|
||||
|
||||
for (var i = 0; i < visitor.Directives.Count; i++)
|
||||
{
|
||||
var directive = visitor.Directives[i];
|
||||
var typeName = directive.Tokens.ElementAt(0).Content;;
|
||||
var memberName = directive.Tokens.ElementAt(1).Content;
|
||||
|
||||
var modelType = "dynamic";
|
||||
if (visitor.ModelType.Count > 0)
|
||||
{
|
||||
modelType = visitor.ModelType.Last().Tokens.First().Content;
|
||||
}
|
||||
|
||||
typeName = typeName.Replace("<TModel>", "<" + modelType + ">");
|
||||
|
||||
var member = new CSharpStatementIRNode()
|
||||
{
|
||||
Source = directive.Source,
|
||||
Content = $"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]{Environment.NewLine}public {typeName} {memberName} {{ get; private set; }}",
|
||||
Parent = visitor.Class,
|
||||
};
|
||||
|
||||
visitor.Class.Children.Add(member);
|
||||
}
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeWalker
|
||||
{
|
||||
public ClassDeclarationIRNode Class { get; private set; }
|
||||
|
||||
public IList<DirectiveIRNode> Directives { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public IList<DirectiveIRNode> ModelType { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public override void VisitClass(ClassDeclarationIRNode node)
|
||||
{
|
||||
if (Class == null)
|
||||
{
|
||||
Class = node;
|
||||
}
|
||||
|
||||
base.VisitClass(node);
|
||||
}
|
||||
|
||||
public override void VisitDirective(DirectiveIRNode node)
|
||||
{
|
||||
if (node.Descriptor == Directive)
|
||||
{
|
||||
Directives.Add(node);
|
||||
}
|
||||
else if (node.Descriptor == ModelDirective.Directive)
|
||||
{
|
||||
ModelType.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +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.Globalization;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class InjectParameterGenerator : SpanChunkGenerator
|
||||
{
|
||||
public InjectParameterGenerator(string typeName, string propertyName)
|
||||
{
|
||||
TypeName = typeName;
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
|
||||
public string TypeName { get; private set; }
|
||||
|
||||
public string PropertyName { get; private set; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
var injectChunk = new InjectChunk(TypeName, PropertyName);
|
||||
context.ChunkTreeBuilder.AddChunk(injectChunk, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "@inject {0} {1}", TypeName, PropertyName);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as InjectParameterGenerator;
|
||||
return other != null &&
|
||||
string.Equals(TypeName, other.TypeName, StringComparison.Ordinal) &&
|
||||
string.Equals(PropertyName, other.PropertyName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeName.GetHashCode() +
|
||||
(PropertyName.GetHashCode() * 13);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal
|
||||
{
|
||||
public class TagHelperChunkDecorator : CodeVisitor<CSharpCodeWriter>
|
||||
{
|
||||
private readonly string _className;
|
||||
private readonly string _namespaceName;
|
||||
|
||||
public TagHelperChunkDecorator(CodeGeneratorContext context)
|
||||
: base(new CSharpCodeWriter(), context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
_namespaceName = context.RootNamespace;
|
||||
_className = context.ClassName;
|
||||
}
|
||||
|
||||
public override void Accept(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var tagHelperChunk = chunk as TagHelperChunk;
|
||||
if (tagHelperChunk != null)
|
||||
{
|
||||
tagHelperChunk.Descriptors = Decorate(tagHelperChunk.Descriptors);
|
||||
}
|
||||
|
||||
var parentChunk = chunk as ParentChunk;
|
||||
if (parentChunk != null)
|
||||
{
|
||||
Visit(parentChunk);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Visit(ParentChunk parentChunk)
|
||||
{
|
||||
Accept(parentChunk.Children);
|
||||
}
|
||||
|
||||
private IEnumerable<TagHelperDescriptor> Decorate(IEnumerable<TagHelperDescriptor> descriptors)
|
||||
{
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
if (ViewComponentTagHelperDescriptorConventions.IsViewComponentDescriptor(descriptor))
|
||||
{
|
||||
var decoratedDescriptor = new TagHelperDescriptor(descriptor);
|
||||
decoratedDescriptor.TypeName = $"{_namespaceName}.{_className}.{descriptor.TypeName}";
|
||||
|
||||
yield return decoratedDescriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,208 +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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal
|
||||
{
|
||||
public class ViewComponentTagHelperChunkVisitor : CodeVisitor<CSharpCodeWriter>
|
||||
{
|
||||
private readonly GeneratedViewComponentTagHelperContext _context;
|
||||
private readonly HashSet<string> _writtenViewComponents;
|
||||
|
||||
private const string ViewComponentTagHelperVariable = "_viewComponentHelper";
|
||||
private const string ViewContextVariable = "ViewContext";
|
||||
|
||||
public ViewComponentTagHelperChunkVisitor(CSharpCodeWriter writer, CodeGeneratorContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
_context = new GeneratedViewComponentTagHelperContext();
|
||||
_writtenViewComponents = new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public override void Accept(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var tagHelperChunk = chunk as TagHelperChunk;
|
||||
if (tagHelperChunk != null)
|
||||
{
|
||||
Visit(tagHelperChunk);
|
||||
}
|
||||
|
||||
var parentChunk = chunk as ParentChunk;
|
||||
if (parentChunk != null)
|
||||
{
|
||||
Visit(parentChunk);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Visit(ParentChunk parentChunk)
|
||||
{
|
||||
Accept(parentChunk.Children);
|
||||
}
|
||||
|
||||
protected override void Visit(TagHelperChunk chunk)
|
||||
{
|
||||
foreach (var descriptor in chunk.Descriptors)
|
||||
{
|
||||
string shortName;
|
||||
if (descriptor.PropertyBag.TryGetValue(
|
||||
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey,
|
||||
out shortName))
|
||||
{
|
||||
var typeName = $"__Generated__{shortName}ViewComponentTagHelper";
|
||||
|
||||
if (_writtenViewComponents.Add(typeName))
|
||||
{
|
||||
WriteClass(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteClass(TagHelperDescriptor descriptor)
|
||||
{
|
||||
// Add target element.
|
||||
BuildTargetElementString(descriptor);
|
||||
|
||||
// Initialize declaration.
|
||||
var tagHelperTypeName = typeof(TagHelper).FullName;
|
||||
|
||||
var shortName = descriptor.PropertyBag[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
var className = $"__Generated__{shortName}ViewComponentTagHelper";
|
||||
|
||||
using (Writer.BuildClassDeclaration("public", className, new[] { tagHelperTypeName }))
|
||||
{
|
||||
// Add view component helper.
|
||||
Writer.WriteVariableDeclaration(
|
||||
$"private readonly global::{_context.IViewComponentHelperTypeName}",
|
||||
ViewComponentTagHelperVariable,
|
||||
value: null);
|
||||
|
||||
// Add constructor.
|
||||
BuildConstructorString(className);
|
||||
|
||||
// Add attributes.
|
||||
BuildAttributeDeclarations(descriptor);
|
||||
|
||||
// Add process method.
|
||||
BuildProcessMethodString(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildConstructorString(string className)
|
||||
{
|
||||
var viewComponentHelperVariable = "viewComponentHelper";
|
||||
|
||||
var helperPair = new KeyValuePair<string, string>(
|
||||
$"global::{_context.IViewComponentHelperTypeName}",
|
||||
viewComponentHelperVariable);
|
||||
|
||||
using (Writer.BuildConstructor( "public", className, new[] { helperPair }))
|
||||
{
|
||||
Writer.WriteStartAssignment(ViewComponentTagHelperVariable)
|
||||
.Write(viewComponentHelperVariable)
|
||||
.WriteLine(";");
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildAttributeDeclarations(TagHelperDescriptor descriptor)
|
||||
{
|
||||
Writer.Write("[")
|
||||
.Write(typeof(HtmlAttributeNotBoundAttribute).FullName)
|
||||
.WriteParameterSeparator()
|
||||
.Write($"global::{_context.ViewContextAttributeTypeName}")
|
||||
.WriteLine("]");
|
||||
|
||||
Writer.WriteAutoPropertyDeclaration(
|
||||
"public",
|
||||
$"global::{_context.ViewContextTypeName}",
|
||||
ViewContextVariable);
|
||||
|
||||
var indexerAttributes = descriptor.Attributes.Where(a => a.IsIndexer);
|
||||
|
||||
foreach (var attribute in descriptor.Attributes)
|
||||
{
|
||||
if (attribute.IsIndexer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Writer.WriteAutoPropertyDeclaration("public", attribute.TypeName, attribute.PropertyName);
|
||||
|
||||
if (indexerAttributes.Any(a => string.Equals(a.PropertyName, attribute.PropertyName, StringComparison.Ordinal)))
|
||||
{
|
||||
Writer.Write(" = ")
|
||||
.WriteStartNewObject(attribute.TypeName)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildProcessMethodString(TagHelperDescriptor descriptor)
|
||||
{
|
||||
var contextVariable = "context";
|
||||
var outputVariable = "output";
|
||||
|
||||
using (Writer.BuildMethodDeclaration(
|
||||
$"public override async",
|
||||
$"global::{typeof(Task).FullName}",
|
||||
nameof(ITagHelper.ProcessAsync),
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ typeof(TagHelperContext).FullName, contextVariable },
|
||||
{ typeof(TagHelperOutput).FullName, outputVariable }
|
||||
}))
|
||||
{
|
||||
Writer.WriteInstanceMethodInvocation(
|
||||
$"({ViewComponentTagHelperVariable} as global::{_context.IViewContextAwareTypeName})?",
|
||||
_context.ContextualizeMethodName,
|
||||
new [] { ViewContextVariable });
|
||||
|
||||
var methodParameters = GetMethodParameters(descriptor);
|
||||
var viewContentVariable = "viewContent";
|
||||
Writer.Write("var ")
|
||||
.WriteStartAssignment(viewContentVariable)
|
||||
.WriteInstanceMethodInvocation($"await {ViewComponentTagHelperVariable}", _context.InvokeAsyncMethodName, methodParameters);
|
||||
Writer.WriteStartAssignment($"{outputVariable}.{nameof(TagHelperOutput.TagName)}")
|
||||
.WriteLine("null;");
|
||||
Writer.WriteInstanceMethodInvocation(
|
||||
$"{outputVariable}.{nameof(TagHelperOutput.Content)}",
|
||||
nameof(TagHelperContent.SetHtmlContent),
|
||||
new [] { viewContentVariable });
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetMethodParameters(TagHelperDescriptor descriptor)
|
||||
{
|
||||
var propertyNames = descriptor.Attributes.Where(a => !a.IsIndexer).Select(attribute => attribute.PropertyName);
|
||||
var joinedPropertyNames = string.Join(", ", propertyNames);
|
||||
var parametersString = $" new {{ { joinedPropertyNames } }}";
|
||||
|
||||
var viewComponentName = descriptor.PropertyBag[
|
||||
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
var methodParameters = new [] { $"\"{viewComponentName}\"", parametersString };
|
||||
return methodParameters;
|
||||
}
|
||||
|
||||
private void BuildTargetElementString(TagHelperDescriptor descriptor)
|
||||
{
|
||||
Writer.Write("[")
|
||||
.WriteStartMethodInvocation(typeof(HtmlTargetElementAttribute).FullName)
|
||||
.WriteStringLiteral(descriptor.FullTagName)
|
||||
.WriteLine(")]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Chunk"/> for an <c>@model</c> directive.
|
||||
/// </summary>
|
||||
public class ModelChunk : Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ModelChunk"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The type of the view's model.</param>
|
||||
public ModelChunk(string modelType)
|
||||
{
|
||||
ModelType = modelType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the view's model.
|
||||
/// </summary>
|
||||
public string ModelType { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +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 Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Generator
|
||||
{
|
||||
public class ModelChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public ModelChunkGenerator(string modelType)
|
||||
{
|
||||
ModelType = modelType;
|
||||
}
|
||||
|
||||
public string ModelType { get; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
var modelChunk = new ModelChunk(ModelType);
|
||||
context.ChunkTreeBuilder.AddChunk(modelChunk, target, topLevel: true);
|
||||
}
|
||||
|
||||
public override string ToString() => ModelType;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as ModelChunkGenerator;
|
||||
return other != null &&
|
||||
string.Equals(ModelType, other.ModelType, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return StringComparer.Ordinal.GetHashCode(ModelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public static class ModelDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("model").AddType().Build();
|
||||
|
||||
public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new Pass());
|
||||
return builder;
|
||||
}
|
||||
|
||||
private class Pass : IRazorIRPass
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
// Runs after the @inherits directive
|
||||
public int Order => RazorIRPass.DefaultDirectiveClassifierOrder + 5;
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(irDocument);
|
||||
|
||||
string modelType = "dynamic";
|
||||
if (visitor.Directives.Count == 1)
|
||||
{
|
||||
modelType = visitor.Directives.Last().Tokens.First().Content;
|
||||
}
|
||||
|
||||
visitor.Class.BaseType = visitor.Class.BaseType.Replace("<TModel>", "<" + modelType + ">");
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeWalker
|
||||
{
|
||||
public ClassDeclarationIRNode Class { get; private set; }
|
||||
|
||||
public IList<DirectiveIRNode> Directives { get; } = new List<DirectiveIRNode>();
|
||||
|
||||
public override void VisitClass(ClassDeclarationIRNode node)
|
||||
{
|
||||
if (Class == null)
|
||||
{
|
||||
Class = node;
|
||||
}
|
||||
|
||||
base.VisitClass(node);
|
||||
}
|
||||
|
||||
public override void VisitDirective(DirectiveIRNode node)
|
||||
{
|
||||
if (node.Descriptor == Directive)
|
||||
{
|
||||
Directives.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public class ModelExpressionPass : IRazorIRPass
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public int Order => RazorIRPass.LoweringOrder;
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeWalker
|
||||
{
|
||||
public List<TagHelperIRNode> TagHelpers { get; } = new List<TagHelperIRNode>();
|
||||
|
||||
public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node)
|
||||
{
|
||||
if (string.Equals(node.Descriptor.TypeName, "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression", StringComparison.Ordinal))
|
||||
{
|
||||
var expression = new CSharpExpressionIRNode();
|
||||
var builder = RazorIRBuilder.Create(expression);
|
||||
|
||||
builder.Add(new CSharpTokenIRNode()
|
||||
{
|
||||
Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ",
|
||||
});
|
||||
|
||||
|
||||
if (node.Children.Count == 1 && node.Children[0] is HtmlContentIRNode)
|
||||
{
|
||||
var original = ((HtmlContentIRNode)node.Children[0]);
|
||||
|
||||
builder.Add(new CSharpTokenIRNode()
|
||||
{
|
||||
Content = "__model."
|
||||
});
|
||||
|
||||
builder.Add(new CSharpTokenIRNode()
|
||||
{
|
||||
Content = original.Content,
|
||||
Source = original.Source,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
builder.Add(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Add(new CSharpTokenIRNode()
|
||||
{
|
||||
Content = ")",
|
||||
});
|
||||
|
||||
node.Children.Clear();
|
||||
|
||||
node.Children.Add(expression);
|
||||
expression.Parent = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +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 Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public abstract class MvcCSharpChunkVisitor : CodeVisitor<CSharpCodeWriter>
|
||||
{
|
||||
public MvcCSharpChunkVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Accept(Chunk chunk)
|
||||
{
|
||||
if (chunk is InjectChunk)
|
||||
{
|
||||
Visit((InjectChunk)chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Accept(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Visit(InjectChunk chunk);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +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 Microsoft.AspNetCore.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Host.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcCSharpCodeGenerator : CSharpCodeGenerator
|
||||
{
|
||||
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
||||
private readonly TagHelperChunkDecorator _tagHelperChunkDecorator;
|
||||
private readonly string _defaultModel;
|
||||
private readonly string _injectAttribute;
|
||||
|
||||
public MvcCSharpCodeGenerator(
|
||||
CodeGeneratorContext context,
|
||||
string defaultModel,
|
||||
string injectAttribute,
|
||||
GeneratedTagHelperAttributeContext tagHelperAttributeContext)
|
||||
: base(context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (defaultModel == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaultModel));
|
||||
}
|
||||
|
||||
if (injectAttribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(injectAttribute));
|
||||
}
|
||||
|
||||
if (tagHelperAttributeContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperAttributeContext));
|
||||
}
|
||||
|
||||
_tagHelperAttributeContext = tagHelperAttributeContext;
|
||||
_defaultModel = defaultModel;
|
||||
_injectAttribute = injectAttribute;
|
||||
_tagHelperChunkDecorator = new TagHelperChunkDecorator(Context);
|
||||
}
|
||||
|
||||
public override CodeGeneratorResult Generate()
|
||||
{
|
||||
_tagHelperChunkDecorator.Accept(Context.ChunkTreeBuilder.Root.Children);
|
||||
return base.Generate();
|
||||
}
|
||||
|
||||
protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
|
||||
{
|
||||
if (Context.Host.DesignTimeMode &&
|
||||
string.Equals(
|
||||
Path.GetFileName(Context.SourceFile),
|
||||
ViewHierarchyUtility.ViewImportsFileName,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Write a using TModel = System.Object; token during design time to make intellisense work
|
||||
writer.WriteLine($"using {ChunkHelper.TModelToken} = {typeof(object).FullName};");
|
||||
}
|
||||
|
||||
return base.BuildClassDeclaration(writer);
|
||||
}
|
||||
|
||||
protected override void BuildAfterExecuteContent(CSharpCodeWriter writer, IList<Chunk> chunks)
|
||||
{
|
||||
new ViewComponentTagHelperChunkVisitor(writer, Context).Accept(chunks);
|
||||
}
|
||||
|
||||
protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var csharpCodeVisitor = base.CreateCSharpCodeVisitor(writer, context);
|
||||
|
||||
csharpCodeVisitor.TagHelperRenderer.AttributeValueCodeRenderer =
|
||||
new MvcTagHelperAttributeValueCodeRenderer(_tagHelperAttributeContext);
|
||||
|
||||
return csharpCodeVisitor;
|
||||
}
|
||||
|
||||
protected override CSharpDesignTimeCodeVisitor CreateCSharpDesignTimeCodeVisitor(
|
||||
CSharpCodeVisitor csharpCodeVisitor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (csharpCodeVisitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(csharpCodeVisitor));
|
||||
}
|
||||
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return new MvcCSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, context);
|
||||
}
|
||||
|
||||
protected override void BuildConstructor(CSharpCodeWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
base.BuildConstructor(writer);
|
||||
|
||||
writer.WriteLineHiddenDirective();
|
||||
|
||||
var injectVisitor = new InjectChunkVisitor(writer, Context, _injectAttribute);
|
||||
injectVisitor.Accept(Context.ChunkTreeBuilder.Root.Children);
|
||||
|
||||
writer.WriteLine();
|
||||
writer.WriteLineHiddenDirective();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +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 Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public abstract class MvcCSharpCodeVisitor : MvcCSharpChunkVisitor
|
||||
{
|
||||
public MvcCSharpCodeVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Visit(InjectChunk chunk)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +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.Diagnostics;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcCSharpDesignTimeCodeVisitor : CSharpDesignTimeCodeVisitor
|
||||
{
|
||||
private const string ModelVariable = "__modelHelper";
|
||||
private ModelChunk _modelChunk;
|
||||
|
||||
public MvcCSharpDesignTimeCodeVisitor(
|
||||
CSharpCodeVisitor csharpCodeVisitor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(csharpCodeVisitor, writer, context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AcceptTreeCore(ChunkTree tree)
|
||||
{
|
||||
base.AcceptTreeCore(tree);
|
||||
|
||||
if (_modelChunk != null)
|
||||
{
|
||||
WriteModelChunkLineMapping();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Accept(Chunk chunk)
|
||||
{
|
||||
if (chunk is ModelChunk)
|
||||
{
|
||||
Visit((ModelChunk)chunk);
|
||||
}
|
||||
|
||||
base.Accept(chunk);
|
||||
}
|
||||
|
||||
private void Visit(ModelChunk chunk)
|
||||
{
|
||||
Debug.Assert(chunk != null);
|
||||
_modelChunk = chunk;
|
||||
}
|
||||
|
||||
private void WriteModelChunkLineMapping()
|
||||
{
|
||||
Debug.Assert(Context.Host.DesignTimeMode);
|
||||
|
||||
using (var lineMappingWriter =
|
||||
Writer.BuildLineMapping(_modelChunk.Start, _modelChunk.ModelType.Length, Context.SourceFile))
|
||||
{
|
||||
// var __modelHelper = default(MyModel);
|
||||
Writer.Write("var ")
|
||||
.Write(ModelVariable)
|
||||
.Write(" = default(");
|
||||
|
||||
lineMappingWriter.MarkLineMappingStart();
|
||||
Writer.Write(_modelChunk.ModelType);
|
||||
lineMappingWriter.MarkLineMappingEnd();
|
||||
|
||||
Writer.WriteLine(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +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.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Host;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Generator;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Tokenizer.Symbols;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcRazorCodeParser : CSharpCodeParser
|
||||
{
|
||||
private const string ModelKeyword = "model";
|
||||
private const string InjectKeyword = "inject";
|
||||
private SourceLocation? _endInheritsLocation;
|
||||
private bool _modelStatementFound;
|
||||
|
||||
public MvcRazorCodeParser()
|
||||
{
|
||||
MapDirectives(ModelDirective, ModelKeyword);
|
||||
MapDirectives(InjectDirective, InjectKeyword);
|
||||
}
|
||||
|
||||
protected override void InheritsDirective()
|
||||
{
|
||||
// Verify we're on the right keyword and accept
|
||||
AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
|
||||
AcceptAndMoveNext();
|
||||
_endInheritsLocation = CurrentLocation;
|
||||
|
||||
InheritsDirectiveCore();
|
||||
CheckForInheritsAndModelStatements();
|
||||
}
|
||||
|
||||
private void CheckForInheritsAndModelStatements()
|
||||
{
|
||||
if (_modelStatementFound && _endInheritsLocation.HasValue)
|
||||
{
|
||||
Context.OnError(
|
||||
_endInheritsLocation.Value,
|
||||
Resources.FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(ModelKeyword),
|
||||
SyntaxConstants.CSharp.InheritsKeyword.Length);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ModelDirective()
|
||||
{
|
||||
// Verify we're on the right keyword and accept
|
||||
AssertDirective(ModelKeyword);
|
||||
var startModelLocation = CurrentLocation;
|
||||
AcceptAndMoveNext();
|
||||
|
||||
|
||||
BaseTypeDirective(Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(ModelKeyword),
|
||||
CreateModelChunkGenerator);
|
||||
|
||||
if (_modelStatementFound)
|
||||
{
|
||||
Context.OnError(
|
||||
startModelLocation,
|
||||
Resources.FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(ModelKeyword),
|
||||
ModelKeyword.Length);
|
||||
}
|
||||
|
||||
_modelStatementFound = true;
|
||||
|
||||
CheckForInheritsAndModelStatements();
|
||||
}
|
||||
|
||||
protected virtual void InjectDirective()
|
||||
{
|
||||
// @inject MyApp.MyService MyServicePropertyName
|
||||
AssertDirective(InjectKeyword);
|
||||
var startLocation = CurrentLocation;
|
||||
AcceptAndMoveNext();
|
||||
|
||||
Context.CurrentBlock.Type = BlockType.Directive;
|
||||
|
||||
// Accept whitespace
|
||||
var remainingWhitespace = AcceptSingleWhiteSpaceCharacter();
|
||||
var keywordwithSingleWhitespaceLength = Span.GetContent().Value.Length;
|
||||
if (Span.Symbols.Count > 1)
|
||||
{
|
||||
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
||||
}
|
||||
Output(SpanKind.MetaCode);
|
||||
|
||||
if (remainingWhitespace != null)
|
||||
{
|
||||
Accept(remainingWhitespace);
|
||||
}
|
||||
var remainingWhitespaceLength = Span.GetContent().Value.Length;
|
||||
|
||||
// Consume any other whitespace tokens.
|
||||
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
|
||||
|
||||
var hasTypeError = !At(CSharpSymbolType.Identifier);
|
||||
if (hasTypeError)
|
||||
{
|
||||
Context.OnError(
|
||||
startLocation,
|
||||
Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(InjectKeyword),
|
||||
InjectKeyword.Length);
|
||||
}
|
||||
|
||||
// Accept 'MyApp.MyService'
|
||||
NamespaceOrTypeName();
|
||||
|
||||
// typeName now contains the token 'MyApp.MyService'
|
||||
var typeName = Span.GetContent().Value;
|
||||
|
||||
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
|
||||
|
||||
if (!hasTypeError && (EndOfFile || At(CSharpSymbolType.NewLine)))
|
||||
{
|
||||
// Add an error for the property name only if we successfully read the type name
|
||||
Context.OnError(
|
||||
startLocation,
|
||||
Resources.FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(InjectKeyword),
|
||||
keywordwithSingleWhitespaceLength + remainingWhitespaceLength + typeName.Length);
|
||||
}
|
||||
|
||||
// Read until end of line. Span now contains 'MyApp.MyService MyServiceName'.
|
||||
AcceptUntil(CSharpSymbolType.NewLine);
|
||||
if (!Context.DesignTimeMode)
|
||||
{
|
||||
// We want the newline to be treated as code, but it causes issues at design-time.
|
||||
Optional(CSharpSymbolType.NewLine);
|
||||
}
|
||||
|
||||
// Parse out 'MyServicePropertyName' from the Span.
|
||||
var propertyName = Span.GetContent()
|
||||
.Value
|
||||
.Substring(typeName.Length);
|
||||
|
||||
// ';' is optional
|
||||
propertyName = RemoveWhitespaceAndTrailingSemicolons(propertyName);
|
||||
Span.ChunkGenerator = new InjectParameterGenerator(typeName.Trim(), propertyName);
|
||||
|
||||
// Output the span and finish the block
|
||||
CompleteBlock();
|
||||
Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
|
||||
}
|
||||
|
||||
private SpanChunkGenerator CreateModelChunkGenerator(string model)
|
||||
{
|
||||
return new ModelChunkGenerator(model);
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal static string RemoveWhitespaceAndTrailingSemicolons(string value)
|
||||
{
|
||||
Debug.Assert(value != null);
|
||||
value = value.TrimStart();
|
||||
|
||||
for (var index = value.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var currentChar = value[index];
|
||||
if (!char.IsWhiteSpace(currentChar) && currentChar != ';')
|
||||
{
|
||||
return value.Substring(0, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,355 +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 Microsoft.AspNetCore.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
#if NET451
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
|
||||
{
|
||||
private const string BaseType = "Microsoft.AspNetCore.Mvc.Razor.RazorPage";
|
||||
private const string HtmlHelperPropertyName = "Html";
|
||||
private const string ModelExpressionProviderProperty = "ModelExpressionProvider";
|
||||
private const string ViewDataProperty = "ViewData";
|
||||
|
||||
private static readonly string[] _defaultNamespaces = new[]
|
||||
{
|
||||
"System",
|
||||
"System.Linq",
|
||||
"System.Collections.Generic",
|
||||
"Microsoft.AspNetCore.Mvc",
|
||||
"Microsoft.AspNetCore.Mvc.Rendering",
|
||||
"Microsoft.AspNetCore.Mvc.ViewFeatures",
|
||||
};
|
||||
private static readonly Chunk[] _defaultInheritedChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>", HtmlHelperPropertyName),
|
||||
new InjectChunk("Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper", "Json"),
|
||||
new InjectChunk("Microsoft.AspNetCore.Mvc.IViewComponentHelper", "Component"),
|
||||
new InjectChunk("Microsoft.AspNetCore.Mvc.IUrlHelper", "Url"),
|
||||
new InjectChunk("Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider", ModelExpressionProviderProperty),
|
||||
new AddTagHelperChunk
|
||||
{
|
||||
LookupText = "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"
|
||||
},
|
||||
new SetBaseTypeChunk
|
||||
{
|
||||
// Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>
|
||||
TypeName = $"{BaseType}<{ChunkHelper.TModelToken}>",
|
||||
// Set the Start to Undefined to prevent Razor design time code generation from rendering a line mapping
|
||||
// for this chunk.
|
||||
Start = SourceLocation.Undefined
|
||||
}
|
||||
};
|
||||
|
||||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||
private readonly IChunkTreeCache _chunkTreeCache;
|
||||
private readonly RazorPathNormalizer _pathNormalizer;
|
||||
private ChunkInheritanceUtility _chunkInheritanceUtility;
|
||||
private ITagHelperDescriptorResolver _tagHelperDescriptorResolver;
|
||||
|
||||
internal MvcRazorHost(IChunkTreeCache chunkTreeCache, RazorPathNormalizer pathNormalizer)
|
||||
: base(new CSharpRazorCodeLanguage())
|
||||
{
|
||||
_pathNormalizer = pathNormalizer;
|
||||
_chunkTreeCache = chunkTreeCache;
|
||||
|
||||
DefaultBaseClass = $"{BaseType}<{ChunkHelper.TModelToken}>";
|
||||
DefaultNamespace = "AspNetCore";
|
||||
// Enable instrumentation by default to allow precompiled views to work with BrowserLink.
|
||||
EnableInstrumentation = true;
|
||||
GeneratedClassContext = new GeneratedClassContext(
|
||||
executeMethodName: "ExecuteAsync",
|
||||
writeMethodName: "Write",
|
||||
writeLiteralMethodName: "WriteLiteral",
|
||||
writeToMethodName: "WriteTo",
|
||||
writeLiteralToMethodName: "WriteLiteralTo",
|
||||
templateTypeName: "Microsoft.AspNetCore.Mvc.Razor.HelperResult",
|
||||
defineSectionMethodName: "DefineSection",
|
||||
generatedTagHelperContext: new GeneratedTagHelperContext
|
||||
{
|
||||
ExecutionContextTypeName = typeof(TagHelperExecutionContext).FullName,
|
||||
ExecutionContextAddMethodName = nameof(TagHelperExecutionContext.Add),
|
||||
ExecutionContextAddTagHelperAttributeMethodName =
|
||||
nameof(TagHelperExecutionContext.AddTagHelperAttribute),
|
||||
ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute),
|
||||
ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output),
|
||||
|
||||
RunnerTypeName = typeof(TagHelperRunner).FullName,
|
||||
RunnerRunAsyncMethodName = nameof(TagHelperRunner.RunAsync),
|
||||
|
||||
ScopeManagerTypeName = typeof(TagHelperScopeManager).FullName,
|
||||
ScopeManagerBeginMethodName = nameof(TagHelperScopeManager.Begin),
|
||||
ScopeManagerEndMethodName = nameof(TagHelperScopeManager.End),
|
||||
|
||||
TagHelperContentTypeName = typeof(TagHelperContent).FullName,
|
||||
|
||||
// Can't use nameof because RazorPage is not accessible here.
|
||||
CreateTagHelperMethodName = "CreateTagHelper",
|
||||
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
|
||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
||||
BeginWriteTagHelperAttributeMethodName = "BeginWriteTagHelperAttribute",
|
||||
EndWriteTagHelperAttributeMethodName = "EndWriteTagHelperAttribute",
|
||||
|
||||
// Can't use nameof because IHtmlHelper is (also) not accessible here.
|
||||
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
|
||||
BeginAddHtmlAttributeValuesMethodName = "BeginAddHtmlAttributeValues",
|
||||
EndAddHtmlAttributeValuesMethodName = "EndAddHtmlAttributeValues",
|
||||
AddHtmlAttributeValueMethodName = "AddHtmlAttributeValue",
|
||||
HtmlEncoderPropertyName = "HtmlEncoder",
|
||||
TagHelperContentGetContentMethodName = nameof(TagHelperContent.GetContent),
|
||||
TagHelperOutputIsContentModifiedPropertyName = nameof(TagHelperOutput.IsContentModified),
|
||||
TagHelperOutputContentPropertyName = nameof(TagHelperOutput.Content),
|
||||
ExecutionContextSetOutputContentAsyncMethodName = nameof(TagHelperExecutionContext.SetOutputContentAsync),
|
||||
TagHelperAttributeValuePropertyName = nameof(TagHelperAttribute.Value),
|
||||
})
|
||||
{
|
||||
BeginContextMethodName = "BeginContext",
|
||||
EndContextMethodName = "EndContext"
|
||||
};
|
||||
|
||||
foreach (var ns in _defaultNamespaces)
|
||||
{
|
||||
NamespaceImports.Add(ns);
|
||||
}
|
||||
}
|
||||
|
||||
#if NET451
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorHost"/> with the specified <paramref name="root"/>.
|
||||
/// </summary>
|
||||
/// <param name="root">The path to the application base.</param>
|
||||
// Note: This constructor is used by tooling and is created once for each
|
||||
// Razor page that is loaded. Consequently, each loaded page has its own copy of
|
||||
// the ChunkTreeCache, but this ok - having a shared ChunkTreeCache per application in tooling
|
||||
// is problematic to manage.
|
||||
public MvcRazorHost(string root)
|
||||
: this(new DefaultChunkTreeCache(new PhysicalFileProvider(root)), new DesignTimeRazorPathNormalizer(root))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="chunkTreeCache"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunkTreeCache">An <see cref="IChunkTreeCache"/> rooted at the application base path.</param>
|
||||
/// <param name="resolver">The <see cref="ITagHelperDescriptorResolver"/> used to resolve tag helpers on razor views.</param>
|
||||
public MvcRazorHost(IChunkTreeCache chunkTreeCache, ITagHelperDescriptorResolver resolver)
|
||||
: this(chunkTreeCache, new RazorPathNormalizer())
|
||||
{
|
||||
TagHelperDescriptorResolver = resolver;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITagHelperDescriptorResolver TagHelperDescriptorResolver
|
||||
{
|
||||
get
|
||||
{
|
||||
// The initialization of the _tagHelperDescriptorResolver needs to be lazy to allow for the setting
|
||||
// of DesignTimeMode.
|
||||
if (_tagHelperDescriptorResolver == null)
|
||||
{
|
||||
_tagHelperDescriptorResolver = new TagHelperDescriptorResolver(DesignTimeMode);
|
||||
}
|
||||
|
||||
return _tagHelperDescriptorResolver;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_tagHelperDescriptorResolver = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model type used by default when no model is specified.
|
||||
/// </summary>
|
||||
/// <remarks>This value is used as the generic type argument for the base type </remarks>
|
||||
public virtual string DefaultModel
|
||||
{
|
||||
get { return "dynamic"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of chunks that are injected by default by this host.
|
||||
/// </summary>
|
||||
public virtual IReadOnlyList<Chunk> DefaultInheritedChunks
|
||||
{
|
||||
get { return _defaultInheritedChunks; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name attribute that is used to decorate properties that are injected and need to be
|
||||
/// activated.
|
||||
/// </summary>
|
||||
public virtual string InjectAttribute
|
||||
{
|
||||
get { return "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name used to represent <see cref="ITagHelper"/> model expression properties.
|
||||
/// </summary>
|
||||
public virtual string ModelExpressionType
|
||||
{
|
||||
get { return "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the method name used to create model expressions.
|
||||
/// </summary>
|
||||
public virtual string CreateModelExpressionMethod
|
||||
{
|
||||
get { return "CreateModelExpression"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name for <c>IModelExpressionProvider</c>.
|
||||
/// </summary>
|
||||
public virtual string ModelExpressionProvider
|
||||
{
|
||||
get { return ModelExpressionProviderProperty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name for <c>ViewDataDictionary</c>.
|
||||
/// </summary>
|
||||
public virtual string ViewDataPropertyName
|
||||
{
|
||||
get { return ViewDataProperty; }
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal ChunkInheritanceUtility ChunkInheritanceUtility
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_chunkInheritanceUtility == null)
|
||||
{
|
||||
// This needs to be lazily evaluated to support DefaultInheritedChunks being virtual.
|
||||
_chunkInheritanceUtility = new ChunkInheritanceUtility(this, _chunkTreeCache, DefaultInheritedChunks);
|
||||
}
|
||||
|
||||
return _chunkInheritanceUtility;
|
||||
}
|
||||
set
|
||||
{
|
||||
_chunkInheritanceUtility = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates and parses _ViewImports.cshtml files applying to the given <paramref name="sourceFileName"/> to
|
||||
/// create <see cref="ChunkTreeResult"/>s.
|
||||
/// </summary>
|
||||
/// <param name="sourceFileName">The path to a Razor file to locate _ViewImports.cshtml for.</param>
|
||||
/// <returns>Inherited <see cref="ChunkTreeResult"/>s.</returns>
|
||||
public IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults(string sourceFileName)
|
||||
{
|
||||
if (sourceFileName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceFileName));
|
||||
}
|
||||
|
||||
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor
|
||||
// files checksum and line pragmas to enable DesignTime debugging.
|
||||
var normalizedPath = _pathNormalizer.NormalizePath(sourceFileName);
|
||||
|
||||
return ChunkInheritanceUtility.GetInheritedChunkTreeResults(normalizedPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||
{
|
||||
var className = ParserHelpers.SanitizeClassName(rootRelativePath);
|
||||
var engine = new RazorTemplateEngine(this);
|
||||
return engine.GenerateCode(inputStream, className, DefaultNamespace, rootRelativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override RazorParser DecorateRazorParser(RazorParser razorParser, string sourceFileName)
|
||||
{
|
||||
if (razorParser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorParser));
|
||||
}
|
||||
|
||||
var inheritedChunkTrees = GetInheritedChunkTrees(sourceFileName);
|
||||
return new MvcRazorParser(razorParser, inheritedChunkTrees, DefaultInheritedChunks, ModelExpressionType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
|
||||
{
|
||||
if (incomingCodeParser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(incomingCodeParser));
|
||||
}
|
||||
|
||||
return new MvcRazorCodeParser();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override CodeGenerator DecorateCodeGenerator(
|
||||
CodeGenerator incomingGenerator,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (incomingGenerator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(incomingGenerator));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var inheritedChunkTrees = GetInheritedChunkTrees(context.SourceFile);
|
||||
|
||||
ChunkInheritanceUtility.MergeInheritedChunkTrees(
|
||||
context.ChunkTreeBuilder.Root,
|
||||
inheritedChunkTrees,
|
||||
DefaultModel);
|
||||
|
||||
return new MvcCSharpCodeGenerator(
|
||||
context,
|
||||
DefaultModel,
|
||||
InjectAttribute,
|
||||
new GeneratedTagHelperAttributeContext
|
||||
{
|
||||
ModelExpressionTypeName = ModelExpressionType,
|
||||
CreateModelExpressionMethodName = CreateModelExpressionMethod,
|
||||
ModelExpressionProviderPropertyName = ModelExpressionProviderProperty,
|
||||
ViewDataPropertyName = ViewDataProperty,
|
||||
});
|
||||
}
|
||||
|
||||
private IReadOnlyList<ChunkTree> GetInheritedChunkTrees(string sourceFileName)
|
||||
{
|
||||
var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName)
|
||||
.Select(result => result.ChunkTree)
|
||||
.ToList();
|
||||
|
||||
return inheritedChunkTrees;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,205 +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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Host;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// A subtype of <see cref="RazorParser"/> that <see cref="MvcRazorHost"/> uses to support inheritance of tag
|
||||
/// helpers from <c>_ViewImports</c> files.
|
||||
/// </summary>
|
||||
public class MvcRazorParser : RazorParser
|
||||
{
|
||||
private readonly IEnumerable<TagHelperDirectiveDescriptor> _viewImportsDirectiveDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorParser"/>.
|
||||
/// </summary>
|
||||
/// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param>
|
||||
/// <param name="inheritedChunkTrees">The <see cref="IReadOnlyList{ChunkTree}"/>s that are inherited
|
||||
/// from parsed pages from _ViewImports files.</param>
|
||||
/// <param name="defaultInheritedChunks">The <see cref="IReadOnlyList{Chunk}"/> inherited by
|
||||
/// default by all Razor pages in the application.</param>
|
||||
/// <param name="modelExpressionTypeName">The full name of the model expression <see cref="Type"/>.</param>
|
||||
public MvcRazorParser(
|
||||
RazorParser parser,
|
||||
IReadOnlyList<ChunkTree> inheritedChunkTrees,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks,
|
||||
string modelExpressionTypeName)
|
||||
: base(parser)
|
||||
{
|
||||
if (parser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parser));
|
||||
}
|
||||
|
||||
if (inheritedChunkTrees == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunkTrees));
|
||||
}
|
||||
|
||||
if (defaultInheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaultInheritedChunks));
|
||||
}
|
||||
|
||||
if (modelExpressionTypeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelExpressionTypeName));
|
||||
}
|
||||
|
||||
// Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks
|
||||
_viewImportsDirectiveDescriptors = GetTagHelperDirectiveDescriptors(
|
||||
inheritedChunkTrees,
|
||||
defaultInheritedChunks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors(
|
||||
Block documentRoot,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
if (documentRoot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentRoot));
|
||||
}
|
||||
|
||||
if (errorSink == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorSink));
|
||||
}
|
||||
|
||||
var visitor = new ViewImportsTagHelperDirectiveSpanVisitor(
|
||||
TagHelperDescriptorResolver,
|
||||
_viewImportsDirectiveDescriptors,
|
||||
errorSink);
|
||||
|
||||
return visitor.GetDescriptors(documentRoot);
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDirectiveDescriptor> GetTagHelperDirectiveDescriptors(
|
||||
IReadOnlyList<ChunkTree> inheritedChunkTrees,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
{
|
||||
var descriptors = new List<TagHelperDirectiveDescriptor>();
|
||||
|
||||
var inheritedChunks = defaultInheritedChunks.Concat(inheritedChunkTrees.SelectMany(tree => tree.Children));
|
||||
foreach (var chunk in inheritedChunks)
|
||||
{
|
||||
// All TagHelperDirectiveDescriptors created here have undefined source locations because the source
|
||||
// that created them is not in the same file.
|
||||
var addTagHelperChunk = chunk as AddTagHelperChunk;
|
||||
if (addTagHelperChunk != null)
|
||||
{
|
||||
var descriptor = new TagHelperDirectiveDescriptor
|
||||
{
|
||||
DirectiveText = addTagHelperChunk.LookupText,
|
||||
Location = chunk.Start,
|
||||
DirectiveType = TagHelperDirectiveType.AddTagHelper
|
||||
};
|
||||
|
||||
descriptors.Add(descriptor);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var removeTagHelperChunk = chunk as RemoveTagHelperChunk;
|
||||
if (removeTagHelperChunk != null)
|
||||
{
|
||||
var descriptor = new TagHelperDirectiveDescriptor
|
||||
{
|
||||
DirectiveText = removeTagHelperChunk.LookupText,
|
||||
Location = chunk.Start,
|
||||
DirectiveType = TagHelperDirectiveType.RemoveTagHelper
|
||||
};
|
||||
|
||||
descriptors.Add(descriptor);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagHelperPrefixDirectiveChunk = chunk as TagHelperPrefixDirectiveChunk;
|
||||
if (tagHelperPrefixDirectiveChunk != null)
|
||||
{
|
||||
var descriptor = new TagHelperDirectiveDescriptor
|
||||
{
|
||||
DirectiveText = tagHelperPrefixDirectiveChunk.Prefix,
|
||||
Location = chunk.Start,
|
||||
DirectiveType = TagHelperDirectiveType.TagHelperPrefix
|
||||
};
|
||||
|
||||
descriptors.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private class ViewImportsTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor
|
||||
{
|
||||
private readonly IEnumerable<TagHelperDirectiveDescriptor> _viewImportsDirectiveDescriptors;
|
||||
|
||||
public ViewImportsTagHelperDirectiveSpanVisitor(
|
||||
ITagHelperDescriptorResolver descriptorResolver,
|
||||
IEnumerable<TagHelperDirectiveDescriptor> viewImportsDirectiveDescriptors,
|
||||
ErrorSink errorSink)
|
||||
: base(descriptorResolver, errorSink)
|
||||
{
|
||||
_viewImportsDirectiveDescriptors = viewImportsDirectiveDescriptors;
|
||||
}
|
||||
|
||||
protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext(
|
||||
IEnumerable<TagHelperDirectiveDescriptor> descriptors,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
var directivesToImport = MergeDirectiveDescriptors(descriptors, _viewImportsDirectiveDescriptors);
|
||||
|
||||
return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink);
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDirectiveDescriptor> MergeDirectiveDescriptors(
|
||||
IEnumerable<TagHelperDirectiveDescriptor> descriptors,
|
||||
IEnumerable<TagHelperDirectiveDescriptor> inheritedDescriptors)
|
||||
{
|
||||
var mergedDescriptors = new List<TagHelperDirectiveDescriptor>();
|
||||
TagHelperDirectiveDescriptor prefixDirectiveDescriptor = null;
|
||||
|
||||
foreach (var descriptor in inheritedDescriptors)
|
||||
{
|
||||
if (descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)
|
||||
{
|
||||
// Always take the latest @tagHelperPrefix descriptor. Can only have 1 per page.
|
||||
prefixDirectiveDescriptor = descriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
mergedDescriptors.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it
|
||||
// takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the
|
||||
// inherited @tagHelperPrefix directive back into the merged list.
|
||||
if (prefixDirectiveDescriptor != null &&
|
||||
!descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix))
|
||||
{
|
||||
mergedDescriptors.Add(prefixDirectiveDescriptor);
|
||||
}
|
||||
|
||||
mergedDescriptors.AddRange(descriptors);
|
||||
|
||||
return mergedDescriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +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 Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class MvcTagHelperAttributeValueCodeRenderer : TagHelperAttributeValueCodeRenderer
|
||||
{
|
||||
private const string ModelLambdaVariableName = "__model";
|
||||
|
||||
private readonly GeneratedTagHelperAttributeContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="MvcTagHelperAttributeValueCodeRenderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains code generation information for rendering attribute values.</param>
|
||||
public MvcTagHelperAttributeValueCodeRenderer(GeneratedTagHelperAttributeContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>If the attribute being rendered is of the type
|
||||
/// <see cref="GeneratedTagHelperAttributeContext.ModelExpressionTypeName"/>, then a model expression will be
|
||||
/// created by calling into <see cref="GeneratedTagHelperAttributeContext.CreateModelExpressionMethodName"/>.
|
||||
/// </remarks>
|
||||
public override void RenderAttributeValue(
|
||||
TagHelperAttributeDescriptor attributeDescriptor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext codeGeneratorContext,
|
||||
Action<CSharpCodeWriter> renderAttributeValue,
|
||||
bool complexValue)
|
||||
{
|
||||
if (attributeDescriptor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributeDescriptor));
|
||||
}
|
||||
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (codeGeneratorContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeGeneratorContext));
|
||||
}
|
||||
|
||||
if (renderAttributeValue == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderAttributeValue));
|
||||
}
|
||||
|
||||
if (attributeDescriptor.TypeName.Equals(_context.ModelExpressionTypeName, StringComparison.Ordinal))
|
||||
{
|
||||
writer
|
||||
.WriteStartInstanceMethodInvocation(_context.ModelExpressionProviderPropertyName, _context.CreateModelExpressionMethodName)
|
||||
.Write(_context.ViewDataPropertyName)
|
||||
.WriteParameterSeparator()
|
||||
.Write(ModelLambdaVariableName)
|
||||
.Write(" => ");
|
||||
if (!complexValue)
|
||||
{
|
||||
writer
|
||||
.Write(ModelLambdaVariableName)
|
||||
.Write(".");
|
||||
|
||||
}
|
||||
|
||||
renderAttributeValue(writer);
|
||||
|
||||
writer.WriteEndMethodInvocation(endLine: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
writer,
|
||||
codeGeneratorContext,
|
||||
renderAttributeValue,
|
||||
complexValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public class MvcViewDocumentClassifierPass : IRazorIRPass
|
||||
{
|
||||
public static readonly string Kind = "mvc.1.0.view";
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
// We want to run before the default, but after others since this is the MVC default.
|
||||
public virtual int Order => RazorIRPass.DefaultDocumentClassifierOrder - 1;
|
||||
|
||||
public static string DocumentKind = "default";
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
if (irDocument.DocumentKind != null)
|
||||
{
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
irDocument.DocumentKind = DocumentKind;
|
||||
|
||||
// Rewrite a use default namespace and class declaration.
|
||||
var children = new List<RazorIRNode>(irDocument.Children);
|
||||
irDocument.Children.Clear();
|
||||
|
||||
var @namespace = new NamespaceDeclarationIRNode()
|
||||
{
|
||||
Content = "AspNetCore",
|
||||
};
|
||||
|
||||
var @class = new ClassDeclarationIRNode()
|
||||
{
|
||||
AccessModifier = "public",
|
||||
Name = GetClassName(codeDocument.Source.Filename) ?? "GeneratedClass",
|
||||
BaseType = "Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>"
|
||||
};
|
||||
|
||||
var method = new RazorMethodDeclarationIRNode()
|
||||
{
|
||||
AccessModifier = "public",
|
||||
Modifiers = new List<string>() { "async", "override" },
|
||||
Name = "ExecuteAsync",
|
||||
ReturnType = "Task",
|
||||
};
|
||||
|
||||
var documentBuilder = RazorIRBuilder.Create(irDocument);
|
||||
|
||||
var namespaceBuilder = RazorIRBuilder.Create(documentBuilder.Current);
|
||||
namespaceBuilder.Push(@namespace);
|
||||
|
||||
var classBuilder = RazorIRBuilder.Create(namespaceBuilder.Current);
|
||||
classBuilder.Push(@class);
|
||||
|
||||
var methodBuilder = RazorIRBuilder.Create(classBuilder.Current);
|
||||
methodBuilder.Push(method);
|
||||
|
||||
var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder);
|
||||
|
||||
for (var i = 0; i < children.Count; i++)
|
||||
{
|
||||
visitor.Visit(children[i]);
|
||||
}
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private static string GetClassName(string filename)
|
||||
{
|
||||
if (filename == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ParserHelpers.SanitizeClassName("Generated_" + Path.GetFileNameWithoutExtension(filename));
|
||||
}
|
||||
|
||||
private static class ParserHelpers
|
||||
{
|
||||
public static bool IsNewLine(char value)
|
||||
{
|
||||
return value == '\r' // Carriage return
|
||||
|| value == '\n' // Linefeed
|
||||
|| value == '\u0085' // Next Line
|
||||
|| value == '\u2028' // Line separator
|
||||
|| value == '\u2029'; // Paragraph separator
|
||||
}
|
||||
|
||||
public static bool IsNewLine(string value)
|
||||
{
|
||||
return (value.Length == 1 && (IsNewLine(value[0]))) ||
|
||||
(string.Equals(value, Environment.NewLine, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// Returns true if the character is Whitespace and NOT a newline
|
||||
public static bool IsWhitespace(char value)
|
||||
{
|
||||
return value == ' ' ||
|
||||
value == '\f' ||
|
||||
value == '\t' ||
|
||||
value == '\u000B' || // Vertical Tab
|
||||
CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.SpaceSeparator;
|
||||
}
|
||||
|
||||
public static bool IsWhitespaceOrNewLine(char value)
|
||||
{
|
||||
return IsWhitespace(value) || IsNewLine(value);
|
||||
}
|
||||
|
||||
public static bool IsIdentifier(string value)
|
||||
{
|
||||
return IsIdentifier(value, requireIdentifierStart: true);
|
||||
}
|
||||
|
||||
public static bool IsIdentifier(string value, bool requireIdentifierStart)
|
||||
{
|
||||
IEnumerable<char> identifierPart = value;
|
||||
if (requireIdentifierStart)
|
||||
{
|
||||
identifierPart = identifierPart.Skip(1);
|
||||
}
|
||||
return (!requireIdentifierStart || IsIdentifierStart(value[0])) && identifierPart.All(IsIdentifierPart);
|
||||
}
|
||||
|
||||
public static bool IsHexDigit(char value)
|
||||
{
|
||||
return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
|
||||
}
|
||||
|
||||
public static bool IsIdentifierStart(char value)
|
||||
{
|
||||
return value == '_' || IsLetter(value);
|
||||
}
|
||||
|
||||
public static bool IsIdentifierPart(char value)
|
||||
{
|
||||
return IsLetter(value)
|
||||
|| IsDecimalDigit(value)
|
||||
|| IsConnecting(value)
|
||||
|| IsCombining(value)
|
||||
|| IsFormatting(value);
|
||||
}
|
||||
|
||||
public static bool IsTerminatingCharToken(char value)
|
||||
{
|
||||
return IsNewLine(value) || value == '\'';
|
||||
}
|
||||
|
||||
public static bool IsTerminatingQuotedStringToken(char value)
|
||||
{
|
||||
return IsNewLine(value) || value == '"';
|
||||
}
|
||||
|
||||
public static bool IsDecimalDigit(char value)
|
||||
{
|
||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.DecimalDigitNumber;
|
||||
}
|
||||
|
||||
public static bool IsLetterOrDecimalDigit(char value)
|
||||
{
|
||||
return IsLetter(value) || IsDecimalDigit(value);
|
||||
}
|
||||
|
||||
public static bool IsLetter(char value)
|
||||
{
|
||||
var cat = CharUnicodeInfo.GetUnicodeCategory(value);
|
||||
|
||||
return cat == UnicodeCategory.UppercaseLetter
|
||||
|| cat == UnicodeCategory.LowercaseLetter
|
||||
|| cat == UnicodeCategory.TitlecaseLetter
|
||||
|| cat == UnicodeCategory.ModifierLetter
|
||||
|| cat == UnicodeCategory.OtherLetter
|
||||
|| cat == UnicodeCategory.LetterNumber;
|
||||
}
|
||||
|
||||
public static bool IsFormatting(char value)
|
||||
{
|
||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.Format;
|
||||
}
|
||||
|
||||
public static bool IsCombining(char value)
|
||||
{
|
||||
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(value);
|
||||
|
||||
return cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.NonSpacingMark;
|
||||
|
||||
}
|
||||
|
||||
public static bool IsConnecting(char value)
|
||||
{
|
||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.ConnectorPunctuation;
|
||||
}
|
||||
|
||||
public static string SanitizeClassName(string inputName)
|
||||
{
|
||||
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
|
||||
{
|
||||
inputName = "_" + inputName;
|
||||
}
|
||||
|
||||
return new String((from value in inputName
|
||||
select IsIdentifierPart(value) ? value : '_')
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
public static bool IsEmailPart(char character)
|
||||
{
|
||||
// Source: http://tools.ietf.org/html/rfc5322#section-3.4.1
|
||||
// We restrict the allowed characters to alpha-numerics and '_' in order to ensure we cover most of the cases where an
|
||||
// email address is intended without restricting the usage of code within JavaScript, CSS, and other contexts.
|
||||
return Char.IsLetter(character) || Char.IsDigit(character) || character == '_';
|
||||
}
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeVisitor
|
||||
{
|
||||
private readonly RazorIRBuilder _document;
|
||||
private readonly RazorIRBuilder _namespace;
|
||||
private readonly RazorIRBuilder _class;
|
||||
private readonly RazorIRBuilder _method;
|
||||
|
||||
public Visitor(RazorIRBuilder document, RazorIRBuilder @namespace, RazorIRBuilder @class, RazorIRBuilder method)
|
||||
{
|
||||
_document = document;
|
||||
_namespace = @namespace;
|
||||
_class = @class;
|
||||
_method = method;
|
||||
}
|
||||
|
||||
public override void VisitChecksum(ChecksumIRNode node)
|
||||
{
|
||||
_document.Insert(0, node);
|
||||
}
|
||||
|
||||
public override void VisitUsingStatement(UsingStatementIRNode node)
|
||||
{
|
||||
_namespace.AddAfter<UsingStatementIRNode>(node);
|
||||
}
|
||||
|
||||
public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
|
||||
{
|
||||
_class.Insert(0, node);
|
||||
}
|
||||
|
||||
public override void VisitDefault(RazorIRNode node)
|
||||
{
|
||||
_method.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,282 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public class ViewComponentTagHelperPass : IRazorIRPass
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public int Order => RazorIRPass.LoweringOrder;
|
||||
|
||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(irDocument);
|
||||
|
||||
if (visitor.Class == null || visitor.TagHelpers.Count == 0)
|
||||
{
|
||||
// Nothing to do, bail.
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
foreach (var tagHelper in visitor.TagHelpers)
|
||||
{
|
||||
GenerateVCTHClass(visitor.Class, tagHelper.Value);
|
||||
|
||||
if (visitor.Fields.UsedTagHelperTypeNames.Remove(tagHelper.Value.TypeName))
|
||||
{
|
||||
visitor.Fields.UsedTagHelperTypeNames.Add(GetVCTHFullName(visitor.Namespace, visitor.Class, tagHelper.Value));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var createNode in visitor.CreateTagHelpers)
|
||||
{
|
||||
RewriteCreateNode(visitor.Namespace, visitor.Class, createNode);
|
||||
}
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private void GenerateVCTHClass(ClassDeclarationIRNode @class, TagHelperDescriptor tagHelper)
|
||||
{
|
||||
var writer = new CSharpCodeWriter();
|
||||
WriteClass(writer, tagHelper);
|
||||
|
||||
@class.Children.Add(new CSharpStatementIRNode()
|
||||
{
|
||||
Content = writer.Builder.ToString(),
|
||||
Parent = @class,
|
||||
});
|
||||
}
|
||||
|
||||
private void RewriteCreateNode(
|
||||
NamespaceDeclarationIRNode @namespace,
|
||||
ClassDeclarationIRNode @class,
|
||||
CreateTagHelperIRNode node)
|
||||
{
|
||||
var originalTypeName = node.TagHelperTypeName;
|
||||
|
||||
var newTypeName = GetVCTHFullName(@namespace, @class, node.Descriptor);
|
||||
for (var i = 0; i < node.Parent.Children.Count; i++)
|
||||
{
|
||||
var setProperty = node.Parent.Children[i] as SetTagHelperPropertyIRNode;
|
||||
if (setProperty != null)
|
||||
{
|
||||
setProperty.TagHelperTypeName = newTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
node.TagHelperTypeName = newTypeName;
|
||||
}
|
||||
|
||||
private static string GetVCTHFullName(
|
||||
NamespaceDeclarationIRNode @namespace,
|
||||
ClassDeclarationIRNode @class,
|
||||
TagHelperDescriptor tagHelper)
|
||||
{
|
||||
var vcName = tagHelper.PropertyBag[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
return $"{@namespace.Content}.{@class.Name}.__Generated__{vcName}ViewComponentTagHelper";
|
||||
}
|
||||
|
||||
private static string GetVCTHClassName(
|
||||
TagHelperDescriptor tagHelper)
|
||||
{
|
||||
var vcName = tagHelper.PropertyBag[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
return $"__Generated__{vcName}ViewComponentTagHelper";
|
||||
}
|
||||
|
||||
private void WriteClass(CSharpCodeWriter writer, TagHelperDescriptor descriptor)
|
||||
{
|
||||
// Add target element.
|
||||
BuildTargetElementString(writer, descriptor);
|
||||
|
||||
// Initialize declaration.
|
||||
var tagHelperTypeName = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper";
|
||||
var className = GetVCTHClassName(descriptor);
|
||||
|
||||
using (writer.BuildClassDeclaration("public", className, new[] { tagHelperTypeName }))
|
||||
{
|
||||
// Add view component helper.
|
||||
writer.WriteVariableDeclaration(
|
||||
$"private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper",
|
||||
"_helper",
|
||||
value: null);
|
||||
|
||||
// Add constructor.
|
||||
BuildConstructorString(writer, className);
|
||||
|
||||
// Add attributes.
|
||||
BuildAttributeDeclarations(writer, descriptor);
|
||||
|
||||
// Add process method.
|
||||
BuildProcessMethodString(writer, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildConstructorString(CSharpCodeWriter writer, string className)
|
||||
{
|
||||
var helperPair = new KeyValuePair<string, string>(
|
||||
$"global::Microsoft.AspNetCore.Mvc.IViewComponentHelper",
|
||||
"helper");
|
||||
|
||||
using (writer.BuildConstructor("public", className, new[] { helperPair }))
|
||||
{
|
||||
writer.WriteStartAssignment("_helper")
|
||||
.Write("helper")
|
||||
.WriteLine(";");
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildAttributeDeclarations(CSharpCodeWriter writer, TagHelperDescriptor descriptor)
|
||||
{
|
||||
writer.Write("[")
|
||||
.Write("Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute")
|
||||
.WriteParameterSeparator()
|
||||
.Write($"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute")
|
||||
.WriteLine("]");
|
||||
|
||||
writer.WriteAutoPropertyDeclaration(
|
||||
"public",
|
||||
$"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext",
|
||||
"_context");
|
||||
|
||||
var indexerAttributes = descriptor.Attributes.Where(a => a.IsIndexer);
|
||||
|
||||
foreach (var attribute in descriptor.Attributes)
|
||||
{
|
||||
if (attribute.IsIndexer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.WriteAutoPropertyDeclaration("public", attribute.TypeName, attribute.PropertyName);
|
||||
|
||||
if (indexerAttributes.Any(a => string.Equals(a.PropertyName, attribute.PropertyName, StringComparison.Ordinal)))
|
||||
{
|
||||
writer.Write(" = ")
|
||||
.WriteStartNewObject(attribute.TypeName)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildProcessMethodString(CSharpCodeWriter writer, TagHelperDescriptor descriptor)
|
||||
{
|
||||
var contextVariable = "context";
|
||||
var outputVariable = "output";
|
||||
|
||||
using (writer.BuildMethodDeclaration(
|
||||
$"public override async",
|
||||
$"global::{typeof(Task).FullName}",
|
||||
"ProcessAsync",
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext", contextVariable },
|
||||
{ "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput", outputVariable }
|
||||
}))
|
||||
{
|
||||
writer.WriteInstanceMethodInvocation(
|
||||
$"(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?",
|
||||
"Contextualize",
|
||||
new[] { "_context" });
|
||||
|
||||
var methodParameters = GetMethodParameters(descriptor);
|
||||
var viewContentVariable = "viewContent";
|
||||
writer.Write("var ")
|
||||
.WriteStartAssignment(viewContentVariable)
|
||||
.WriteInstanceMethodInvocation($"await _helper", "InvokeAsync", methodParameters);
|
||||
writer.WriteStartAssignment($"{outputVariable}.TagName")
|
||||
.WriteLine("null;");
|
||||
writer.WriteInstanceMethodInvocation(
|
||||
$"{outputVariable}.Content",
|
||||
"SetHtmlContent",
|
||||
new[] { viewContentVariable });
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetMethodParameters(TagHelperDescriptor descriptor)
|
||||
{
|
||||
var propertyNames = descriptor.Attributes.Where(a => !a.IsIndexer).Select(attribute => attribute.PropertyName);
|
||||
var joinedPropertyNames = string.Join(", ", propertyNames);
|
||||
var parametersString = $" new {{ { joinedPropertyNames } }}";
|
||||
|
||||
var viewComponentName = descriptor.PropertyBag[
|
||||
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
var methodParameters = new[] { $"\"{viewComponentName}\"", parametersString };
|
||||
return methodParameters;
|
||||
}
|
||||
|
||||
private void BuildTargetElementString(CSharpCodeWriter writer, TagHelperDescriptor descriptor)
|
||||
{
|
||||
writer.Write("[")
|
||||
.WriteStartMethodInvocation("Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute")
|
||||
.WriteStringLiteral(descriptor.FullTagName)
|
||||
.WriteLine(")]");
|
||||
}
|
||||
|
||||
private class Visitor : RazorIRNodeWalker
|
||||
{
|
||||
public ClassDeclarationIRNode Class { get; private set; }
|
||||
|
||||
public DeclareTagHelperFieldsIRNode Fields { get; private set; }
|
||||
|
||||
public NamespaceDeclarationIRNode Namespace { get; private set; }
|
||||
|
||||
public List<CreateTagHelperIRNode> CreateTagHelpers { get; } = new List<CreateTagHelperIRNode>();
|
||||
|
||||
public Dictionary<string, TagHelperDescriptor> TagHelpers { get; } = new Dictionary<string, TagHelperDescriptor>();
|
||||
|
||||
public override void VisitCreateTagHelper(CreateTagHelperIRNode node)
|
||||
{
|
||||
var tagHelper = node.Descriptor;
|
||||
if (ViewComponentTagHelperDescriptorConventions.IsViewComponentDescriptor(tagHelper))
|
||||
{
|
||||
// Capture all the VCTagHelpers (unique by type name) so we can generate a class for each one.
|
||||
var vcName = tagHelper.PropertyBag[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||
TagHelpers[vcName] = tagHelper;
|
||||
|
||||
CreateTagHelpers.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamespace(NamespaceDeclarationIRNode node)
|
||||
{
|
||||
if (Namespace == null)
|
||||
{
|
||||
Namespace = node;
|
||||
}
|
||||
|
||||
base.VisitNamespace(node);
|
||||
}
|
||||
|
||||
public override void VisitClass(ClassDeclarationIRNode node)
|
||||
{
|
||||
if (Class == null)
|
||||
{
|
||||
Class = node;
|
||||
}
|
||||
|
||||
base.VisitClass(node);
|
||||
}
|
||||
|
||||
public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
|
||||
{
|
||||
if (Fields == null)
|
||||
{
|
||||
Fields = node;
|
||||
}
|
||||
|
||||
base.VisitDeclareTagHelperFields(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.IMvcRazorHost",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.ModelChunk : Microsoft.AspNetCore.Razor.Chunks.Chunk",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Razor.Generator.ModelChunkGenerator : Microsoft.AspNetCore.Razor.Chunks.Generators.SpanChunkGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectChunk : Microsoft.AspNetCore.Razor.Chunks.Chunk",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectChunkVisitor : Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectParameterGenerator : Microsoft.AspNetCore.Razor.Chunks.Generators.SpanChunkGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public abstract class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpChunkVisitor : Microsoft.AspNetCore.Razor.CodeGenerators.Visitors.CodeVisitor<Microsoft.AspNetCore.Razor.CodeGenerators.CSharpCodeWriter>",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeGenerator : Microsoft.AspNetCore.Razor.CodeGenerators.CSharpCodeGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public abstract class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeVisitor : Microsoft.AspNetCore.Mvc.Razor.MvcCSharpChunkVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpDesignTimeCodeVisitor : Microsoft.AspNetCore.Razor.CodeGenerators.Visitors.CSharpDesignTimeCodeVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorCodeParser : Microsoft.AspNetCore.Razor.Parser.CSharpCodeParser",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorParser : Microsoft.AspNetCore.Razor.Parser.RazorParser",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorHost : Microsoft.AspNetCore.Razor.RazorEngineHost, Microsoft.AspNetCore.Mvc.Razor.IMvcRazorHost",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcTagHelperAttributeValueCodeRenderer : Microsoft.AspNetCore.Razor.CodeGenerators.TagHelperAttributeValueCodeRenderer",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkHelper",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkInheritanceUtility",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkTreeResult",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.DefaultChunkTreeCache : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkTreeCache",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkTreeCache : System.IDisposable",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.InjectChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.SetBaseTypeChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.UsingChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.IMvcRazorHost",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.ModelChunk : Microsoft.AspNetCore.Razor.Chunks.Chunk",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Razor.Generator.ModelChunkGenerator : Microsoft.AspNetCore.Razor.Chunks.Generators.SpanChunkGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectChunk : Microsoft.AspNetCore.Razor.Chunks.Chunk",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectChunkVisitor : Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.InjectParameterGenerator : Microsoft.AspNetCore.Razor.Chunks.Generators.SpanChunkGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public abstract class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpChunkVisitor : Microsoft.AspNetCore.Razor.CodeGenerators.Visitors.CodeVisitor<Microsoft.AspNetCore.Razor.CodeGenerators.CSharpCodeWriter>",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeGenerator : Microsoft.AspNetCore.Razor.CodeGenerators.CSharpCodeGenerator",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public abstract class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpCodeVisitor : Microsoft.AspNetCore.Mvc.Razor.MvcCSharpChunkVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcCSharpDesignTimeCodeVisitor : Microsoft.AspNetCore.Razor.CodeGenerators.Visitors.CSharpDesignTimeCodeVisitor",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorCodeParser : Microsoft.AspNetCore.Razor.Parser.CSharpCodeParser",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorParser : Microsoft.AspNetCore.Razor.Parser.RazorParser",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcRazorHost : Microsoft.AspNetCore.Razor.RazorEngineHost, Microsoft.AspNetCore.Mvc.Razor.IMvcRazorHost",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.MvcTagHelperAttributeValueCodeRenderer : Microsoft.AspNetCore.Razor.CodeGenerators.TagHelperAttributeValueCodeRenderer",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkHelper",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkInheritanceUtility",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.ChunkTreeResult",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.DefaultChunkTreeCache : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkTreeCache",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkTreeCache : System.IDisposable",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.InjectChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.SetBaseTypeChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.UsingChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
"xmlDoc": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Razor.Runtime": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Razor.Evolution": "1.2.0-*",
|
||||
"Microsoft.Extensions.Caching.Memory": "1.2.0-*",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "1.2.0-*",
|
||||
"Microsoft.Extensions.PropertyHelper.Sources": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// 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.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -9,13 +11,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
public interface ICompilationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiles content and returns the result of compilation.
|
||||
/// Compiles a <see cref="RazorCSharpDocument"/> and returns the result of compilation.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The <see cref="RelativeFileInfo"/> for the Razor file that was compiled.</param>
|
||||
/// <param name="compilationContent">The generated C# content to be compiled.</param>
|
||||
/// <param name="codeDocument">
|
||||
/// The <see cref="RazorCodeDocument"/> that contains the sources for the compilation.
|
||||
/// </param>
|
||||
/// <param name="cSharpDocument">
|
||||
/// The <see cref="RazorCSharpDocument"/> to compile.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CompilationResult"/> representing the result of compilation.
|
||||
/// </returns>
|
||||
CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent);
|
||||
CompilationResult Compile(RazorCodeDocument codeDocument, RazorCSharpDocument cSharpDocument);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Host;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
|
@ -157,12 +158,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
services.TryAddSingleton<IRazorViewEngine, RazorViewEngine>();
|
||||
|
||||
services.TryAdd(ServiceDescriptor.Singleton<IChunkTreeCache>(serviceProvider =>
|
||||
{
|
||||
var accessor = serviceProvider.GetRequiredService<IRazorViewEngineFileProviderAccessor>();
|
||||
return new DefaultChunkTreeCache(accessor.FileProvider);
|
||||
}));
|
||||
|
||||
services.TryAddSingleton<ITagHelperTypeResolver, TagHelperTypeResolver>();
|
||||
services.TryAddSingleton<ITagHelperDescriptorFactory>(s => new TagHelperDescriptorFactory(designTime: false));
|
||||
services.TryAddSingleton<TagHelperDescriptorResolver>();
|
||||
|
|
@ -176,7 +171,32 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// creating the singleton RazorViewEngine instance.
|
||||
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
|
||||
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
|
||||
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();
|
||||
|
||||
services.TryAddSingleton<RazorProject>(s =>
|
||||
{
|
||||
return new DefaultRazorProject(s.GetRequiredService<IRazorViewEngineFileProviderAccessor>().FileProvider);
|
||||
});
|
||||
|
||||
services.TryAddSingleton<RazorEngine>(s =>
|
||||
{
|
||||
return RazorEngine.Create(b =>
|
||||
{
|
||||
InjectDirective.Register(b);
|
||||
ModelDirective.Register(b);
|
||||
|
||||
b.Features.Add(new ModelExpressionPass());
|
||||
b.Features.Add(new ViewComponentTagHelperPass());
|
||||
b.Features.Add(new MvcViewDocumentClassifierPass());
|
||||
|
||||
b.Features.Add(new Microsoft.CodeAnalysis.Razor.DefaultTagHelperFeature());
|
||||
|
||||
var referenceManager = s.GetRequiredService<RazorReferenceManager>();
|
||||
b.Features.Add(new Microsoft.CodeAnalysis.Razor.DefaultMetadataReferenceFeature()
|
||||
{
|
||||
References = referenceManager.CompilationReferences.ToArray(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// This caches Razor page activation details that are valid for the lifetime of the application.
|
||||
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.IO;
|
|||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class DefaultRazorProject : RazorProject
|
||||
{
|
||||
|
|
@ -5,7 +5,7 @@ using System.IO;
|
|||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class DefaultRazorProjectItem : RazorProjectItem
|
||||
{
|
||||
|
|
@ -10,6 +10,7 @@ using System.Reflection;
|
|||
using System.Text;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
|
@ -56,23 +57,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent)
|
||||
public CompilationResult Compile(RazorCodeDocument codeDocument, RazorCSharpDocument cSharpDocument)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
if (codeDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileInfo));
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
if (compilationContent == null)
|
||||
if (cSharpDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(compilationContent));
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
_logger.GeneratedCodeToAssemblyCompilationStart(fileInfo.RelativePath);
|
||||
_logger.GeneratedCodeToAssemblyCompilationStart(codeDocument.Source.Filename);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
|
||||
var assemblyName = Path.GetRandomFileName();
|
||||
var compilation = CreateCompilation(compilationContent, assemblyName);
|
||||
var compilation = CreateCompilation(cSharpDocument.GeneratedCode, assemblyName);
|
||||
|
||||
using (var assemblyStream = new MemoryStream())
|
||||
{
|
||||
|
|
@ -86,8 +88,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
if (!result.Success)
|
||||
{
|
||||
return GetCompilationFailedResult(
|
||||
fileInfo.RelativePath,
|
||||
compilationContent,
|
||||
codeDocument,
|
||||
cSharpDocument.GeneratedCode,
|
||||
assemblyName,
|
||||
result.Diagnostics);
|
||||
}
|
||||
|
|
@ -98,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var assembly = LoadAssembly(assemblyStream, pdbStream);
|
||||
var type = assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested);
|
||||
|
||||
_logger.GeneratedCodeToAssemblyCompilationEnd(fileInfo.RelativePath, startTimestamp);
|
||||
_logger.GeneratedCodeToAssemblyCompilationEnd(codeDocument.Source.Filename, startTimestamp);
|
||||
|
||||
return new CompilationResult(type);
|
||||
}
|
||||
|
|
@ -122,14 +124,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Internal for unit testing
|
||||
internal CompilationResult GetCompilationFailedResult(
|
||||
string relativePath,
|
||||
RazorCodeDocument codeDocument,
|
||||
string compilationContent,
|
||||
string assemblyName,
|
||||
IEnumerable<Diagnostic> diagnostics)
|
||||
{
|
||||
var diagnosticGroups = diagnostics
|
||||
.Where(IsError)
|
||||
.GroupBy(diagnostic => GetFilePath(relativePath, diagnostic), StringComparer.Ordinal);
|
||||
.GroupBy(diagnostic => GetFilePath(codeDocument, diagnostic), StringComparer.Ordinal);
|
||||
|
||||
var failures = new List<CompilationFailure>();
|
||||
foreach (var group in diagnosticGroups)
|
||||
|
|
@ -144,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
sourceFileContent = ReadFileContentsSafely(_fileProvider, sourceFilePath);
|
||||
sourceFileContent = GetContent(codeDocument, sourceFilePath);
|
||||
}
|
||||
|
||||
string additionalMessage = null;
|
||||
|
|
@ -171,11 +173,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
return new CompilationResult(failures);
|
||||
}
|
||||
|
||||
private static string GetFilePath(string relativePath, Diagnostic diagnostic)
|
||||
private static string GetFilePath(RazorCodeDocument codeDocument, Diagnostic diagnostic)
|
||||
{
|
||||
if (diagnostic.Location == Location.None)
|
||||
{
|
||||
return relativePath;
|
||||
return codeDocument.Source.Filename;
|
||||
}
|
||||
|
||||
return diagnostic.Location.GetMappedLineSpan().Path;
|
||||
|
|
@ -197,21 +199,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
return assembly;
|
||||
}
|
||||
|
||||
private static string ReadFileContentsSafely(IFileProvider fileProvider, string filePath)
|
||||
private static string GetContent(RazorCodeDocument codeDocument, string filePath)
|
||||
{
|
||||
var fileInfo = fileProvider.GetFileInfo(filePath);
|
||||
if (fileInfo.Exists)
|
||||
if (filePath == codeDocument.Source.Filename)
|
||||
{
|
||||
try
|
||||
var chars = new char[codeDocument.Source.Length];
|
||||
codeDocument.Source.CopyTo(0, chars, 0, chars.Length);
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
for (var i = 0; i < codeDocument.Imports.Count; i++)
|
||||
{
|
||||
var import = codeDocument.Imports[i];
|
||||
if (filePath == import.Filename)
|
||||
{
|
||||
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore any failures
|
||||
var chars = new char[codeDocument.Source.Length];
|
||||
codeDocument.Source.CopyTo(0, chars, 0, chars.Length);
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -22,27 +23,52 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
public class RazorCompilationService : IRazorCompilationService
|
||||
{
|
||||
private readonly ICompilationService _compilationService;
|
||||
private readonly IMvcRazorHost _razorHost;
|
||||
private readonly RazorEngine _engine;
|
||||
private readonly RazorProject _project;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly ILogger _logger;
|
||||
private readonly RazorSourceDocument _globalImports;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="RazorCompilationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compilationService">The <see cref="ICompilationService"/> to compile generated code.</param>
|
||||
/// <param name="razorHost">The <see cref="IMvcRazorHost"/> to generate code from Razor files.</param>
|
||||
/// <param name="engine">The <see cref="RazorEngine"/> to generate code from Razor files.</param>
|
||||
/// <param name="project">The <see cref="RazorProject"/> implementation for locating files.</param>
|
||||
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public RazorCompilationService(
|
||||
ICompilationService compilationService,
|
||||
IMvcRazorHost razorHost,
|
||||
RazorEngine engine,
|
||||
RazorProject project,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_compilationService = compilationService;
|
||||
_razorHost = razorHost;
|
||||
_engine = engine;
|
||||
_fileProvider = fileProviderAccessor.FileProvider;
|
||||
_logger = loggerFactory.CreateLogger<RazorCompilationService>();
|
||||
|
||||
_project = project;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream, Encoding.UTF8);
|
||||
writer.WriteLine("@using System");
|
||||
writer.WriteLine("@using System.Linq");
|
||||
writer.WriteLine("@using System.Collections.Generic");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.IUrlHelper Url");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
|
||||
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
|
||||
writer.Flush();
|
||||
|
||||
stream.Seek(0L, SeekOrigin.Begin);
|
||||
_globalImports = RazorSourceDocument.ReadFrom(stream, filename: null, encoding: Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -53,43 +79,66 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
GeneratorResults results;
|
||||
RazorCodeDocument codeDocument;
|
||||
RazorCSharpDocument cSharpDocument;
|
||||
using (var inputStream = file.FileInfo.CreateReadStream())
|
||||
{
|
||||
_logger.RazorFileToCodeCompilationStart(file.RelativePath);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
|
||||
results = GenerateCode(file.RelativePath, inputStream);
|
||||
codeDocument = CreateCodeDocument(file.RelativePath, inputStream);
|
||||
cSharpDocument = ProcessCodeDocument(codeDocument);
|
||||
|
||||
_logger.RazorFileToCodeCompilationEnd(file.RelativePath, startTimestamp);
|
||||
}
|
||||
|
||||
if (!results.Success)
|
||||
if (cSharpDocument.Diagnostics.Count > 0)
|
||||
{
|
||||
return GetCompilationFailedResult(file, results.ParserErrors);
|
||||
return GetCompilationFailedResult(file, cSharpDocument.Diagnostics);
|
||||
}
|
||||
|
||||
return _compilationService.Compile(file, results.GeneratedCode);
|
||||
return _compilationService.Compile(codeDocument, cSharpDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate code for the Razor file at <paramref name="relativePath"/> with content
|
||||
/// <paramref name="inputStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">
|
||||
/// The path of the Razor file relative to the root of the application. Used to generate line pragmas and
|
||||
/// calculate the class name of the generated type.
|
||||
/// </param>
|
||||
/// <param name="inputStream">A <see cref="Stream"/> that contains the Razor content.</param>
|
||||
/// <returns>A <see cref="GeneratorResults"/> instance containing results of code generation.</returns>
|
||||
protected virtual GeneratorResults GenerateCode(string relativePath, Stream inputStream)
|
||||
protected virtual RazorCodeDocument CreateCodeDocument(string relativePath, Stream inputStream)
|
||||
{
|
||||
return _razorHost.GenerateCode(relativePath, inputStream);
|
||||
var absolutePath = _fileProvider.GetFileInfo(relativePath)?.PhysicalPath ?? relativePath;
|
||||
|
||||
var source = RazorSourceDocument.ReadFrom(inputStream, absolutePath);
|
||||
|
||||
var imports = new List<RazorSourceDocument>()
|
||||
{
|
||||
_globalImports,
|
||||
};
|
||||
|
||||
var paths = ViewHierarchyUtility.GetViewImportsLocations(relativePath);
|
||||
foreach (var path in paths.Reverse())
|
||||
{
|
||||
var file = _fileProvider.GetFileInfo(path);
|
||||
if (file.Exists)
|
||||
{
|
||||
using (var stream = file.CreateReadStream())
|
||||
{
|
||||
imports.Add(RazorSourceDocument.ReadFrom(stream, file.PhysicalPath ?? path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RazorCodeDocument.Create(source, imports);
|
||||
}
|
||||
|
||||
protected virtual RazorCSharpDocument ProcessCodeDocument(RazorCodeDocument codeDocument)
|
||||
{
|
||||
_engine.Process(codeDocument);
|
||||
|
||||
return codeDocument.GetCSharpDocument();
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal CompilationResult GetCompilationFailedResult(RelativeFileInfo file, IEnumerable<RazorError> errors)
|
||||
internal CompilationResult GetCompilationFailedResult(
|
||||
RelativeFileInfo file,
|
||||
IEnumerable<Microsoft.AspNetCore.Razor.Evolution.Legacy.RazorError> errors)
|
||||
{
|
||||
// If a SourceLocation does not specify a file path, assume it is produced
|
||||
// from parsing the current file.
|
||||
|
|
@ -114,7 +163,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
return new CompilationResult(failures);
|
||||
}
|
||||
|
||||
private DiagnosticMessage CreateDiagnosticMessage(RazorError error, string filePath)
|
||||
private DiagnosticMessage CreateDiagnosticMessage(
|
||||
Microsoft.AspNetCore.Razor.Evolution.Legacy.RazorError error,
|
||||
string filePath)
|
||||
{
|
||||
var location = error.Location;
|
||||
return new DiagnosticMessage(
|
||||
|
|
|
|||
|
|
@ -510,6 +510,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("FileProvidersAreRequired"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path must begin with a forward slash '/'.
|
||||
/// </summary>
|
||||
internal static string RazorProject_PathMustStartWithForwardSlash
|
||||
{
|
||||
get { return GetString("RazorProject_PathMustStartWithForwardSlash"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path must begin with a forward slash '/'.
|
||||
/// </summary>
|
||||
internal static string FormatRazorProject_PathMustStartWithForwardSlash()
|
||||
{
|
||||
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -856,7 +856,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
if (PreviousSectionWriters.TryGetValue(sectionName, out renderDelegate))
|
||||
{
|
||||
_renderedSections.Add(sectionName);
|
||||
await renderDelegate(Output);
|
||||
|
||||
await renderDelegate();
|
||||
|
||||
// Return a token value that allows the Write call that wraps the RenderSection \ RenderSectionAsync
|
||||
// to succeed.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public delegate Task RenderAsyncDelegate(TextWriter writer);
|
||||
public delegate Task RenderAsyncDelegate();
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -212,4 +212,7 @@
|
|||
<data name="FileProvidersAreRequired" xml:space="preserve">
|
||||
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
|
||||
</data>
|
||||
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||
<value>Path must begin with a forward slash '/'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"OldMemberId": "public virtual System.Threading.Tasks.Task Invoke(System.IO.TextWriter writer)",
|
||||
"NewTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"NewMemberId": "public virtual System.Threading.Tasks.Task Invoke()",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"OldMemberId": "public virtual System.IAsyncResult BeginInvoke(System.IO.TextWriter writer, System.AsyncCallback callback, System.Object object)",
|
||||
"NewTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"NewMemberId": "public virtual System.IAsyncResult BeginInvoke(System.AsyncCallback callback, System.Object object)",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"OldMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Mvc.Razor.Compilation.RelativeFileInfo fileInfo, System.String compilationContent)",
|
||||
"NewTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Razor.Evolution.RazorCodeDocument codeDocument, Microsoft.AspNetCore.Razor.Evolution.RazorCSharpDocument cSharpDocument)",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Razor.Evolution.RazorCodeDocument codeDocument, Microsoft.AspNetCore.Razor.Evolution.RazorCSharpDocument cSharpDocument)",
|
||||
"Kind": "Addition"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"OldMemberId": "public virtual System.Threading.Tasks.Task Invoke(System.IO.TextWriter writer)",
|
||||
"NewTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"NewMemberId": "public virtual System.Threading.Tasks.Task Invoke()",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"OldMemberId": "public virtual System.IAsyncResult BeginInvoke(System.IO.TextWriter writer, System.AsyncCallback callback, System.Object object)",
|
||||
"NewTypeId": "public sealed class Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate : System.MulticastDelegate",
|
||||
"NewMemberId": "public virtual System.IAsyncResult BeginInvoke(System.AsyncCallback callback, System.Object object)",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"OldMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Mvc.Razor.Compilation.RelativeFileInfo fileInfo, System.String compilationContent)",
|
||||
"NewTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Razor.Evolution.RazorCodeDocument codeDocument, Microsoft.AspNetCore.Razor.Evolution.RazorCSharpDocument cSharpDocument)",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewTypeId": "public interface Microsoft.AspNetCore.Mvc.Razor.Compilation.ICompilationService",
|
||||
"NewMemberId": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult Compile(Microsoft.AspNetCore.Razor.Evolution.RazorCodeDocument codeDocument, Microsoft.AspNetCore.Razor.Evolution.RazorCSharpDocument cSharpDocument)",
|
||||
"Kind": "Addition"
|
||||
}
|
||||
]
|
||||
|
|
@ -22,6 +22,8 @@
|
|||
"xmlDoc": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Razor": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Razor.Runtime": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Host": {
|
||||
"target": "project"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,22 +26,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path must begin with a forward slash '/'.
|
||||
/// </summary>
|
||||
internal static string RazorProject_PathMustStartWithForwardSlash
|
||||
{
|
||||
get { return GetString("RazorProject_PathMustStartWithForwardSlash"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path must begin with a forward slash '/'.
|
||||
/// </summary>
|
||||
internal static string FormatRazorProject_PathMustStartWithForwardSlash()
|
||||
{
|
||||
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' property of '{1}' must not be null.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -120,9 +120,6 @@
|
|||
<data name="PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable" xml:space="preserve">
|
||||
<value>The route for the page at '{0}' cannot start with / or ~/. Pages do not support overriding the file path of the page.</value>
|
||||
</data>
|
||||
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||
<value>Path must begin with a forward slash '/'.</value>
|
||||
</data>
|
||||
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
||||
<value>The '{0}' property of '{1}' must not be null.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"Microsoft.AspNetCore.Mvc.Razor": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.AspNetCore.Razor.Runtime": "1.2.0-*",
|
||||
"Microsoft.AspNetCore.Routing.Abstractions": "1.2.0-*",
|
||||
"Microsoft.Extensions.Caching.Memory": "1.2.0-*",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "1.2.0-*",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains($"/Views/ErrorPageMiddleware/{action}.cshtml", content);
|
||||
Assert.Contains($"{action}.cshtml", content);
|
||||
Assert.Contains(expected, content);
|
||||
Assert.DoesNotContain(PreserveCompilationContextMessage, content);
|
||||
}
|
||||
|
|
@ -64,11 +64,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains($"/Views/ErrorPageMiddleware/{action}.cshtml", content);
|
||||
Assert.Contains($"{action}.cshtml", content);
|
||||
Assert.Contains(expected, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Razor #595")]
|
||||
public async Task CompilationFailuresFromViewImportsAreListed()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("/Views/ErrorFromViewImports/_ViewImports.cshtml", content);
|
||||
Assert.Contains("_ViewImports.cshtml", content);
|
||||
Assert.Contains(expectedMessage, content);
|
||||
Assert.Contains(PreserveCompilationContextMessage, content);
|
||||
Assert.Contains(expectedCompilationContent, content);
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("/Views/ErrorPageMiddleware/RuntimeError.cshtml", content);
|
||||
Assert.Contains("RuntimeError.cshtml", content);
|
||||
Assert.Contains(expectedMessage, content);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Razor #961")]
|
||||
public async Task InstrumentedViews_RenderAsExpected()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -1,183 +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.IO;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
||||
{
|
||||
public class ChunkInheritanceUtilityTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReadsChunksFromGlobalFilesInPath()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"/Views/accounts/_ViewImports.cshtml", "@using AccountModels");
|
||||
fileProvider.AddFile(@"/Views/Shared/_ViewImports.cshtml", "@inject SharedHelper Shared");
|
||||
fileProvider.AddFile(@"/Views/home/_ViewImports.cshtml", "@using MyNamespace");
|
||||
fileProvider.AddFile(@"/Views/_ViewImports.cshtml",
|
||||
@"@inject MyHelper<TModel> Helper
|
||||
@inherits MyBaseType
|
||||
|
||||
@{
|
||||
Layout = ""test.cshtml"";
|
||||
}
|
||||
|
||||
");
|
||||
var defaultChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
};
|
||||
var cache = new DefaultChunkTreeCache(fileProvider);
|
||||
var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false));
|
||||
var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
|
||||
|
||||
// Act
|
||||
var chunkTreeResults = utility.GetInheritedChunkTreeResults(
|
||||
Path.Combine("Views", "home", "Index.cshtml"));
|
||||
|
||||
// Assert
|
||||
Assert.Collection(chunkTreeResults,
|
||||
chunkTreeResult =>
|
||||
{
|
||||
var viewImportsPath = @"/Views/_ViewImports.cshtml";
|
||||
Assert.Collection(chunkTreeResult.ChunkTree.Children,
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunk);
|
||||
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
|
||||
Assert.Equal("Helper", injectChunk.MemberName);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
||||
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<StatementChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
});
|
||||
Assert.Equal(viewImportsPath, chunkTreeResult.FilePath);
|
||||
},
|
||||
chunkTreeResult =>
|
||||
{
|
||||
var viewImportsPath = "/Views/home/_ViewImports.cshtml";
|
||||
Assert.Collection(chunkTreeResult.ChunkTree.Children,
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunk);
|
||||
Assert.Equal("MyNamespace", usingChunk.Namespace);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
},
|
||||
chunk =>
|
||||
{
|
||||
Assert.IsType<LiteralChunk>(chunk);
|
||||
Assert.Equal(viewImportsPath, chunk.Start.FilePath);
|
||||
});
|
||||
Assert.Equal(viewImportsPath, chunkTreeResult.FilePath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReturnsEmptySequenceIfNoGlobalsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"/_ViewImports.cs", string.Empty);
|
||||
fileProvider.AddFile(@"/Views/_Layout.cshtml", string.Empty);
|
||||
fileProvider.AddFile(@"/Views/home/_not-viewimports.cshtml", string.Empty);
|
||||
var cache = new DefaultChunkTreeCache(fileProvider);
|
||||
var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false));
|
||||
var defaultChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
};
|
||||
var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
|
||||
|
||||
// Act
|
||||
var chunkTrees = utility.GetInheritedChunkTreeResults(Path.Combine("Views", "home", "Index.cshtml"));
|
||||
|
||||
// Assert
|
||||
Assert.Empty(chunkTrees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeInheritedChunks_MergesDefaultInheritedChunks()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"/Views/_ViewImports.cshtml",
|
||||
"@inject DifferentHelper<TModel> Html");
|
||||
var cache = new DefaultChunkTreeCache(fileProvider);
|
||||
var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false));
|
||||
var defaultChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
};
|
||||
var inheritedChunkTrees = new ChunkTree[]
|
||||
{
|
||||
new ChunkTree
|
||||
{
|
||||
Children = new Chunk[]
|
||||
{
|
||||
new UsingChunk { Namespace = "InheritedNamespace" },
|
||||
new LiteralChunk { Text = "some text" }
|
||||
}
|
||||
},
|
||||
new ChunkTree
|
||||
{
|
||||
Children = new Chunk[]
|
||||
{
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
|
||||
var chunkTree = new ChunkTree();
|
||||
|
||||
// Act
|
||||
utility.MergeInheritedChunkTrees(chunkTree, inheritedChunkTrees, "dynamic");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(chunkTree.Children,
|
||||
chunk => Assert.Same(defaultChunks[1], chunk),
|
||||
chunk => Assert.Same(inheritedChunkTrees[0].Children[0], chunk),
|
||||
chunk => Assert.Same(defaultChunks[0], chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,180 +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 Microsoft.AspNetCore.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Directives
|
||||
{
|
||||
public class ChunkTreeCacheTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsCachedEntriesOnSubsequentCalls()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\_ViewStart.cshtml";
|
||||
var mockFileProvider = new Mock<TestFileProvider> { CallBase = true };
|
||||
var fileProvider = mockFileProvider.Object;
|
||||
fileProvider.AddFile(path, "test content");
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider))
|
||||
{
|
||||
var expected = new ChunkTree();
|
||||
|
||||
// Act
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => expected);
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, result1);
|
||||
Assert.Same(expected, result2);
|
||||
mockFileProvider.Verify(f => f.GetFileInfo(It.IsAny<string>()), Times.Once());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsNullValues_IfFileDoesNotExistInFileProvider()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\_ViewStart.cshtml";
|
||||
var mockFileProvider = new Mock<TestFileProvider> { CallBase = true };
|
||||
var fileProvider = mockFileProvider.Object;
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider))
|
||||
{
|
||||
var expected = new ChunkTree();
|
||||
|
||||
// Act
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => expected);
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
|
||||
|
||||
// Assert
|
||||
Assert.Null(result1);
|
||||
Assert.Null(result2);
|
||||
mockFileProvider.Verify(f => f.GetFileInfo(It.IsAny<string>()), Times.Once());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_UpdatesCache_IfFileExpirationTriggerExpires()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\Home\_ViewStart.cshtml";
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(path, "test content");
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider))
|
||||
{
|
||||
var expected1 = new ChunkTree();
|
||||
var expected2 = new ChunkTree();
|
||||
|
||||
// Act 1
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => expected1);
|
||||
|
||||
// Assert 1
|
||||
Assert.Same(expected1, result1);
|
||||
|
||||
// Act 2
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => expected2);
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(expected2, result2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_UpdatesCacheWithNullValue_IfFileWasDeleted()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\Home\_ViewStart.cshtml";
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(path, "test content");
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider))
|
||||
{
|
||||
var expected1 = new ChunkTree();
|
||||
|
||||
// Act 1
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => expected1);
|
||||
|
||||
// Assert 1
|
||||
Assert.Same(expected1, result1);
|
||||
|
||||
// Act 2
|
||||
fileProvider.DeleteFile(path);
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
|
||||
|
||||
// Assert 2
|
||||
Assert.Null(result2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_UpdatesCacheWithValue_IfFileWasAdded()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\Home\_ViewStart.cshtml";
|
||||
var fileProvider = new TestFileProvider();
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider))
|
||||
{
|
||||
var expected = new ChunkTree();
|
||||
|
||||
// Act 1
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
|
||||
|
||||
// Assert 1
|
||||
Assert.Null(result1);
|
||||
|
||||
// Act 2
|
||||
fileProvider.AddFile(path, "test content");
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => expected);
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(expected, result2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ExpiresEntriesAfterOneMinute()
|
||||
{
|
||||
// Arrange
|
||||
var path = @"Views\Home\_ViewStart.cshtml";
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(path, "some content");
|
||||
var clock = new Mock<ISystemClock>();
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
clock.SetupGet(c => c.UtcNow)
|
||||
.Returns(() => utcNow);
|
||||
var options = new MemoryCacheOptions { Clock = clock.Object };
|
||||
using (var chunkTreeCache = new DefaultChunkTreeCache(fileProvider, options))
|
||||
{
|
||||
var chunkTree1 = new ChunkTree();
|
||||
var chunkTree2 = new ChunkTree();
|
||||
|
||||
// Act 1
|
||||
var result1 = chunkTreeCache.GetOrAdd(path, fileInfo => chunkTree1);
|
||||
|
||||
// Assert 1
|
||||
Assert.Same(chunkTree1, result1);
|
||||
|
||||
// Act 2
|
||||
utcNow = utcNow.AddSeconds(59);
|
||||
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(chunkTree1, result2);
|
||||
|
||||
// Act 3
|
||||
utcNow = utcNow.AddSeconds(65);
|
||||
var result3 = chunkTreeCache.GetOrAdd(path, fileInfo => chunkTree2);
|
||||
|
||||
// Assert 3
|
||||
Assert.Same(chunkTree2, result3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
{
|
||||
public class InjectChunkMergerTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Theory]
|
||||
[InlineData("MyApp.TestHelper<TModel>", "MyApp.TestHelper<Person>")]
|
||||
[InlineData("TestBaseType", "TestBaseType")]
|
||||
|
|
@ -168,5 +169,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
Assert.Equal("DifferentProperty", injectChunk.MemberName);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
{
|
||||
public class SetBaseTypeChunkMergerTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Theory]
|
||||
[InlineData("MyApp.BaseType<TModel>", "MyApp.BaseType<Person>")]
|
||||
[InlineData("TestBaseType", "TestBaseType")]
|
||||
|
|
@ -88,5 +89,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
||||
Assert.Equal("MyBase1", setBaseTypeChunk.TypeName);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
{
|
||||
public class UsingChunkMergerTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Fact]
|
||||
public void Merge_AddsNamespacesThatHaveNotBeenVisitedInChunkTree()
|
||||
{
|
||||
|
|
@ -98,5 +99,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|||
chunk = Assert.IsType<UsingChunk>(chunkTree.Children[1]);
|
||||
Assert.Equal("Microsoft.AspNetCore.mvc", chunk.Namespace);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
public class InjectChunkVisitorTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Fact]
|
||||
public void Visit_IgnoresNonInjectChunks()
|
||||
{
|
||||
|
|
@ -157,5 +158,6 @@ MyType1
|
|||
shouldGenerateLinePragmas: true),
|
||||
new ErrorSink());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test
|
|||
{
|
||||
public class TagHelperChunkDecoratorTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Fact]
|
||||
public void Accept_CorrectlyDecoratesViewComponentChunks()
|
||||
{
|
||||
|
|
@ -63,5 +64,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test
|
|||
resultTagHelperChunk.Descriptors.First().TypeName,
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test.Internal
|
|||
{
|
||||
public class ViewComponentTagHelperChunkVisitorTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
public static TheoryData CodeGenerationData
|
||||
{
|
||||
get
|
||||
|
|
@ -56,5 +57,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test.Internal
|
|||
#endif
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,554 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Generator;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcCSharpRazorCodeParserTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("model")]
|
||||
[InlineData("inject")]
|
||||
public void Constructor_AddsMvcSpecificKeywords(string keyword)
|
||||
{
|
||||
// Arrange
|
||||
var parser = new TestMvcCSharpRazorCodeParser();
|
||||
|
||||
// Act
|
||||
var hasDirective = parser.HasDirective(keyword);
|
||||
|
||||
// Assert
|
||||
Assert.True(hasDirective);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesSingleInstance()
|
||||
{
|
||||
// Arrange
|
||||
var document = "@model Foo";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var errors = new List<RazorError>();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" Foo")
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Foo?", "Foo?")]
|
||||
[InlineData("Foo[[]][]", "Foo[[]][]")]
|
||||
[InlineData("$rootnamespace$.MyModel", "$rootnamespace$.MyModel")]
|
||||
public void ParseModelKeyword_InfersBaseType_FromModelName(
|
||||
string modelName,
|
||||
string expectedModel)
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "@model " + modelName + Environment.NewLine + "Bar";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var errors = new List<RazorError>();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(modelName + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator(expectedModel))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMissingModelType()
|
||||
{
|
||||
// Arrange + Act
|
||||
var errors = new List<RazorError>();
|
||||
var document = "@model ";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" ")
|
||||
.As(new ModelChunkGenerator(string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'model' keyword must be followed by a type name on the same line.",
|
||||
new SourceLocation(1, 0, 1), 5)
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMultipleModelStatements()
|
||||
{
|
||||
// Arrange + Act
|
||||
var errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@model Bar";
|
||||
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new ModelChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"Only one 'model' statement is allowed in a file.",
|
||||
PlatformNormalizer.NormalizedSourceLocation(13, 1, 1),
|
||||
5)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@inherits Bar";
|
||||
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new SetBaseTypeChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"The 'inherits' keyword is not allowed when a 'model' keyword is used.",
|
||||
PlatformNormalizer.NormalizedSourceLocation(21, 1, 9),
|
||||
length: 8)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var document =
|
||||
"@inherits Bar" + Environment.NewLine
|
||||
+ "@model Foo";
|
||||
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar" + Environment.NewLine)
|
||||
.As(new SetBaseTypeChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo")
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"The 'inherits' keyword is not allowed when a 'model' keyword is used.",
|
||||
new SourceLocation(9, 0, 9),
|
||||
length: 8)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("IMyService Service", "IMyService", "Service")]
|
||||
[InlineData(" Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper ",
|
||||
"Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
|
||||
[InlineData(" TestService @class ", "TestService", "@class")]
|
||||
public void ParseInjectKeyword_InfersTypeAndPropertyName(
|
||||
string injectStatement,
|
||||
string expectedService,
|
||||
string expectedPropertyName)
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "@inject " + injectStatement;
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var errors = new List<RazorError>();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(injectStatement)
|
||||
.As(new InjectParameterGenerator(expectedService, expectedPropertyName))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("IMyService Service;", "IMyService", "Service")]
|
||||
[InlineData("IMyService Service;;", "IMyService", "Service")]
|
||||
[InlineData(" Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper; ",
|
||||
"Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
|
||||
[InlineData(" Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper; ; ",
|
||||
"Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
|
||||
[InlineData(" TestService @class; ; ", "TestService", "@class")]
|
||||
[InlineData("IMyService Service ;", "IMyService", "Service")]
|
||||
[InlineData("IMyService Service ; ;", "IMyService", "Service")]
|
||||
[InlineData(" Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper ; ",
|
||||
"Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
|
||||
[InlineData(" Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper ; ; ",
|
||||
"Microsoft.AspNetCore.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
|
||||
[InlineData(" TestService @class ; ", "TestService", "@class")]
|
||||
[InlineData(" TestService @class ; ; ", "TestService", "@class")]
|
||||
public void ParseInjectKeyword_AllowsOptionalTrailingSemicolon(
|
||||
string injectStatement,
|
||||
string expectedService,
|
||||
string expectedPropertyName)
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "@inject " + injectStatement;
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var errors = new List<RazorError>();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(injectStatement)
|
||||
.As(new InjectParameterGenerator(expectedService, expectedPropertyName))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("IMyService Service ", "IMyService", "Service")]
|
||||
[InlineData(" TestService @namespace ", "TestService", "@namespace")]
|
||||
public void ParseInjectKeyword_ParsesUpToNewLine(
|
||||
string injectStatement,
|
||||
string expectedService,
|
||||
string expectedPropertyName)
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "@inject " + injectStatement + Environment.NewLine + "Bar";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var errors = new List<RazorError>();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(injectStatement + Environment.NewLine)
|
||||
.As(new InjectParameterGenerator(expectedService, expectedPropertyName))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseInjectKeyword_ErrorOnMissingTypeName()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var documentContent = $"@inject {Environment.NewLine}Bar";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" " + Environment.NewLine)
|
||||
.As(new InjectParameterGenerator(string.Empty, string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'inject' keyword must be followed by a type name on the same line.",
|
||||
new SourceLocation(1, 0, 1), 6)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseInjectKeyword_ErrorOnMissingTypeName_WhenTypeNameEndsWithEOF()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var documentContent = "@inject ";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" ")
|
||||
.As(new InjectParameterGenerator(string.Empty, string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'inject' keyword must be followed by a type name on the same line.",
|
||||
new SourceLocation(1, 0, 1), 6)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseInjectKeyword_ErrorOnMissingPropertyName()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var documentContent = $"@inject IMyService {Environment.NewLine}Bar";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" IMyService " + Environment.NewLine)
|
||||
.As(new InjectParameterGenerator("IMyService", string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("A property name must be specified when using the 'inject' statement. " +
|
||||
"Format for a 'inject' statement is '@inject <Type Name> <Property Name>'.",
|
||||
new SourceLocation(1, 0, 1), 21)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseInjectKeyword_ErrorOnMissingPropertyName_WhenTypeNameEndsWithEOF()
|
||||
{
|
||||
// Arrange
|
||||
var errors = new List<RazorError>();
|
||||
var documentContent = "@inject IMyServi";
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inject ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" IMyServi")
|
||||
.As(new InjectParameterGenerator("IMyServi", string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("A property name must be specified when using the 'inject' statement. " +
|
||||
"Format for a 'inject' statement is '@inject <Type Name> <Property Name>'.",
|
||||
new SourceLocation(1, 0, 1), 21)
|
||||
};
|
||||
|
||||
// Act
|
||||
var spans = ParseDocument(documentContent, errors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedSpans, spans);
|
||||
Assert.Equal(expectedErrors, errors);
|
||||
}
|
||||
|
||||
private static List<Span> ParseDocument(
|
||||
string documentContents,
|
||||
List<RazorError> errors = null,
|
||||
List<LineMapping> lineMappings = null)
|
||||
{
|
||||
errors = errors ?? new List<RazorError>();
|
||||
var markupParser = new HtmlMarkupParser();
|
||||
var codeParser = new TestMvcCSharpRazorCodeParser();
|
||||
var reader = new SeekableTextReader(documentContents);
|
||||
var context = new ParserContext(
|
||||
reader,
|
||||
codeParser,
|
||||
markupParser,
|
||||
markupParser,
|
||||
new ErrorSink());
|
||||
codeParser.Context = context;
|
||||
markupParser.Context = context;
|
||||
markupParser.ParseDocument();
|
||||
|
||||
var results = context.CompleteParse();
|
||||
errors.AddRange(results.ParserErrors);
|
||||
return results.Document.Flatten().ToList();
|
||||
}
|
||||
|
||||
private sealed class TestMvcCSharpRazorCodeParser : MvcRazorCodeParser
|
||||
{
|
||||
public bool HasDirective(string directive)
|
||||
{
|
||||
Action handler;
|
||||
return TryGetDirectiveHandler(directive, out handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,10 @@ using System.Reflection;
|
|||
#if GENERATE_BASELINES
|
||||
using System.Text;
|
||||
#endif
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators.Visitors;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
|
|
@ -27,6 +24,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
public class MvcRazorHostTest
|
||||
{
|
||||
|
||||
#if OLD_RAZOR
|
||||
private static Assembly _assembly = typeof(MvcRazorHostTest).GetTypeInfo().Assembly;
|
||||
|
||||
public static TheoryData NormalizeChunkInheritanceUtilityPaths_Data
|
||||
|
|
@ -106,22 +105,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
Assert.Equal("src/file.cshtml", chunkInheritanceUtility.InheritedChunkTreePagePath, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MvcRazorHost_EnablesInstrumentationByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var host = new MvcRazorHost(
|
||||
new DefaultChunkTreeCache(fileProvider),
|
||||
new TagHelperDescriptorResolver(designTime: false));
|
||||
|
||||
// Act
|
||||
var instrumented = host.EnableInstrumentation;
|
||||
|
||||
// Assert
|
||||
Assert.True(instrumented);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MvcRazorHost_GeneratesTagHelperModelExpressionCode_DesignTime()
|
||||
{
|
||||
|
|
@ -595,148 +578,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
documentLocation: new MappingLocation(documentLocation, contentLength),
|
||||
generatedLocation: new MappingLocation(generatedLocation, contentLength));
|
||||
}
|
||||
|
||||
private class PathValidatingChunkInheritanceUtility : ChunkInheritanceUtility
|
||||
{
|
||||
public PathValidatingChunkInheritanceUtility(MvcRazorHost razorHost, IChunkTreeCache chunkTreeCache)
|
||||
: base(razorHost, chunkTreeCache, defaultInheritedChunks: new Chunk[0])
|
||||
{
|
||||
}
|
||||
|
||||
public string InheritedChunkTreePagePath { get; private set; }
|
||||
|
||||
public override IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults(string pagePath)
|
||||
{
|
||||
InheritedChunkTreePagePath = pagePath;
|
||||
|
||||
return new ChunkTreeResult[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalizes the newlines in different OS platforms.
|
||||
private class MvcRazorHostWithNormalizedNewLine : MvcRazorHost
|
||||
{
|
||||
public MvcRazorHostWithNormalizedNewLine(IChunkTreeCache codeTreeCache)
|
||||
: base(codeTreeCache, new TagHelperDescriptorResolver(designTime: false))
|
||||
{ }
|
||||
|
||||
public override CodeGenerator DecorateCodeGenerator(
|
||||
CodeGenerator incomingBuilder,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
base.DecorateCodeGenerator(incomingBuilder, context);
|
||||
|
||||
return new TestCSharpCodeGenerator(
|
||||
context,
|
||||
DefaultModel,
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute",
|
||||
new GeneratedTagHelperAttributeContext
|
||||
{
|
||||
ModelExpressionTypeName = ModelExpressionType,
|
||||
CreateModelExpressionMethodName = CreateModelExpressionMethod,
|
||||
ModelExpressionProviderPropertyName = ModelExpressionProvider,
|
||||
ViewDataPropertyName = ViewDataPropertyName
|
||||
});
|
||||
}
|
||||
|
||||
protected class TestCSharpCodeGenerator : MvcCSharpCodeGenerator
|
||||
{
|
||||
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
||||
|
||||
public TestCSharpCodeGenerator(
|
||||
CodeGeneratorContext context,
|
||||
string defaultModel,
|
||||
string activateAttribute,
|
||||
GeneratedTagHelperAttributeContext tagHelperAttributeContext)
|
||||
: base(context, defaultModel, activateAttribute, tagHelperAttributeContext)
|
||||
{
|
||||
_tagHelperAttributeContext = tagHelperAttributeContext;
|
||||
}
|
||||
|
||||
protected override CSharpCodeWriter CreateCodeWriter()
|
||||
{
|
||||
// We normalize newlines so no matter what platform we're on
|
||||
// they're consistent (for code generation tests).
|
||||
var codeWriter = base.CreateCodeWriter();
|
||||
codeWriter.NewLine = "\r\n";
|
||||
|
||||
return codeWriter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used when testing Tag Helpers, it disables the unique ID generation feature.
|
||||
/// </summary>
|
||||
private class TestMvcRazorHost : MvcRazorHost
|
||||
{
|
||||
public TestMvcRazorHost(IChunkTreeCache ChunkTreeCache)
|
||||
: base(ChunkTreeCache, new TagHelperDescriptorResolver(designTime: false))
|
||||
{
|
||||
}
|
||||
|
||||
public override CodeGenerator DecorateCodeGenerator(
|
||||
CodeGenerator incomingBuilder,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
base.DecorateCodeGenerator(incomingBuilder, context);
|
||||
|
||||
return new TestCSharpCodeGenerator(
|
||||
context,
|
||||
DefaultModel,
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute",
|
||||
new GeneratedTagHelperAttributeContext
|
||||
{
|
||||
ModelExpressionTypeName = ModelExpressionType,
|
||||
CreateModelExpressionMethodName = CreateModelExpressionMethod,
|
||||
ModelExpressionProviderPropertyName = ModelExpressionProvider,
|
||||
ViewDataPropertyName = ViewDataPropertyName
|
||||
});
|
||||
}
|
||||
|
||||
protected class TestCSharpCodeGenerator : MvcCSharpCodeGenerator
|
||||
{
|
||||
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
||||
|
||||
public TestCSharpCodeGenerator(
|
||||
CodeGeneratorContext context,
|
||||
string defaultModel,
|
||||
string activateAttribute,
|
||||
GeneratedTagHelperAttributeContext tagHelperAttributeContext)
|
||||
: base(context, defaultModel, activateAttribute, tagHelperAttributeContext)
|
||||
{
|
||||
_tagHelperAttributeContext = tagHelperAttributeContext;
|
||||
}
|
||||
|
||||
protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
var visitor = base.CreateCSharpCodeVisitor(writer, context);
|
||||
visitor.TagHelperRenderer = new NoUniqueIdsTagHelperCodeRenderer(visitor, writer, context)
|
||||
{
|
||||
AttributeValueCodeRenderer =
|
||||
new MvcTagHelperAttributeValueCodeRenderer(_tagHelperAttributeContext)
|
||||
};
|
||||
return visitor;
|
||||
}
|
||||
|
||||
private class NoUniqueIdsTagHelperCodeRenderer : CSharpTagHelperCodeRenderer
|
||||
{
|
||||
public NoUniqueIdsTagHelperCodeRenderer(
|
||||
IChunkVisitor bodyVisitor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(bodyVisitor, writer, context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string GenerateUniqueId()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -1,253 +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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class MvcRazorCodeParserTest
|
||||
{
|
||||
public static TheoryData ViewImportsData
|
||||
{
|
||||
get
|
||||
{
|
||||
// chunkTrees, expectedDirectiveDescriptors
|
||||
return new TheoryData<ChunkTree[], TagHelperDirectiveDescriptor[]>
|
||||
{
|
||||
{
|
||||
new[] { CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP" }) },
|
||||
new[] { CreateDirectiveDescriptor("THP", TagHelperDirectiveType.TagHelperPrefix) }
|
||||
},
|
||||
{
|
||||
new[] { CreateChunkTree(new AddTagHelperChunk { LookupText = "ATH" }) },
|
||||
new[] { CreateDirectiveDescriptor("ATH", TagHelperDirectiveType.AddTagHelper) }
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(
|
||||
new AddTagHelperChunk { LookupText = "ATH1" },
|
||||
new AddTagHelperChunk { LookupText = "ATH2" })
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("ATH1", TagHelperDirectiveType.AddTagHelper),
|
||||
CreateDirectiveDescriptor("ATH2", TagHelperDirectiveType.AddTagHelper)
|
||||
}
|
||||
},
|
||||
{
|
||||
new[] { CreateChunkTree(new RemoveTagHelperChunk { LookupText = "RTH" }) },
|
||||
new[] { CreateDirectiveDescriptor("RTH", TagHelperDirectiveType.RemoveTagHelper) }
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(
|
||||
new RemoveTagHelperChunk { LookupText = "RTH1" },
|
||||
new RemoveTagHelperChunk { LookupText = "RTH2" })
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("RTH1", TagHelperDirectiveType.RemoveTagHelper),
|
||||
CreateDirectiveDescriptor("RTH2", TagHelperDirectiveType.RemoveTagHelper)
|
||||
}
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP2" }),
|
||||
CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP1" }),
|
||||
},
|
||||
new[] { CreateDirectiveDescriptor("THP1", TagHelperDirectiveType.TagHelperPrefix) }
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(
|
||||
new TagHelperPrefixDirectiveChunk { Prefix = "THP" },
|
||||
new RemoveTagHelperChunk { LookupText = "RTH" },
|
||||
new AddTagHelperChunk { LookupText = "ATH" })
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("RTH", TagHelperDirectiveType.RemoveTagHelper),
|
||||
CreateDirectiveDescriptor("ATH", TagHelperDirectiveType.AddTagHelper),
|
||||
CreateDirectiveDescriptor("THP", TagHelperDirectiveType.TagHelperPrefix),
|
||||
}
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(new RemoveTagHelperChunk { LookupText = "RTH" }),
|
||||
CreateChunkTree(
|
||||
new LiteralChunk { Text = "Hello world" },
|
||||
new AddTagHelperChunk { LookupText = "ATH" }),
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("RTH", TagHelperDirectiveType.RemoveTagHelper),
|
||||
CreateDirectiveDescriptor("ATH", TagHelperDirectiveType.AddTagHelper),
|
||||
}
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(new RemoveTagHelperChunk { LookupText = "RTH" }),
|
||||
CreateChunkTree(
|
||||
new LiteralChunk { Text = "Hello world" },
|
||||
new AddTagHelperChunk { LookupText = "ATH" }),
|
||||
CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP" }),
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("RTH", TagHelperDirectiveType.RemoveTagHelper),
|
||||
CreateDirectiveDescriptor("ATH", TagHelperDirectiveType.AddTagHelper),
|
||||
CreateDirectiveDescriptor("THP", TagHelperDirectiveType.TagHelperPrefix),
|
||||
}
|
||||
},
|
||||
{
|
||||
new[]
|
||||
{
|
||||
CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP2" }),
|
||||
CreateChunkTree(new RemoveTagHelperChunk { LookupText = "RTH" }),
|
||||
CreateChunkTree(new AddTagHelperChunk { LookupText = "ATH" }),
|
||||
CreateChunkTree(new TagHelperPrefixDirectiveChunk { Prefix = "THP1" }),
|
||||
},
|
||||
new[]
|
||||
{
|
||||
CreateDirectiveDescriptor("RTH", TagHelperDirectiveType.RemoveTagHelper),
|
||||
CreateDirectiveDescriptor("ATH", TagHelperDirectiveType.AddTagHelper),
|
||||
CreateDirectiveDescriptor("THP1", TagHelperDirectiveType.TagHelperPrefix),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ViewImportsData))]
|
||||
public void GetTagHelperDescriptors_ReturnsExpectedDirectiveDescriptors(
|
||||
ChunkTree[] chunkTrees,
|
||||
TagHelperDirectiveDescriptor[] expectedDirectiveDescriptors)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new BlockBuilder { Type = BlockType.Comment };
|
||||
var block = new Block(builder);
|
||||
|
||||
IList<TagHelperDirectiveDescriptor> descriptors = null;
|
||||
var resolver = new Mock<ITagHelperDescriptorResolver>();
|
||||
resolver.Setup(r => r.Resolve(It.IsAny<TagHelperDescriptorResolutionContext>()))
|
||||
.Callback((TagHelperDescriptorResolutionContext context) =>
|
||||
{
|
||||
descriptors = context.DirectiveDescriptors;
|
||||
})
|
||||
.Returns(Enumerable.Empty<TagHelperDescriptor>())
|
||||
.Verifiable();
|
||||
|
||||
var baseParser = new RazorParser(
|
||||
new CSharpCodeParser(),
|
||||
new HtmlMarkupParser(),
|
||||
tagHelperDescriptorResolver: resolver.Object);
|
||||
var parser = new TestableMvcRazorParser(baseParser, chunkTrees, defaultInheritedChunks: new Chunk[0]);
|
||||
|
||||
// Act
|
||||
parser.GetTagHelperDescriptorsPublic(block, errorSink: new ErrorSink()).ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(descriptors);
|
||||
Assert.Equal(expectedDirectiveDescriptors.Length, descriptors.Count);
|
||||
|
||||
for (var i = 0; i < expectedDirectiveDescriptors.Length; i++)
|
||||
{
|
||||
var expected = expectedDirectiveDescriptors[i];
|
||||
var actual = descriptors[i];
|
||||
|
||||
Assert.Equal(expected.DirectiveText, actual.DirectiveText, StringComparer.Ordinal);
|
||||
Assert.Equal(SourceLocation.Zero, actual.Location);
|
||||
Assert.Equal(expected.DirectiveType, actual.DirectiveType);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "")]
|
||||
[InlineData(" ; ", "")]
|
||||
[InlineData(" ", "")]
|
||||
[InlineData(";;", "")]
|
||||
[InlineData("a", "a")]
|
||||
[InlineData("a;", "a")]
|
||||
[InlineData("abcd", "abcd")]
|
||||
[InlineData("abc;d", "abc;d")]
|
||||
[InlineData("a bc d", "a bc d")]
|
||||
[InlineData("a\t\tbc\td\t", "a\t\tbc\td")]
|
||||
[InlineData("abc;", "abc")]
|
||||
[InlineData(" abc;", "abc")]
|
||||
[InlineData("\tabc;", "abc")]
|
||||
[InlineData(";; abc;", ";; abc")]
|
||||
[InlineData(";;\tabc;", ";;\tabc")]
|
||||
[InlineData("\t;;abc;", ";;abc")]
|
||||
[InlineData("abc;; ;", "abc")]
|
||||
[InlineData("abc;;\t;", "abc")]
|
||||
[InlineData("\tabc \t;", "abc")]
|
||||
[InlineData("abc;;\r\n;", "abc")]
|
||||
[InlineData("abcd \n", "abcd")]
|
||||
[InlineData("\r\n\r \n\t abcd \t \t \n \r\n", "abcd")]
|
||||
[InlineData("pqrs\r", "pqrs")]
|
||||
public void RemoveWhitespaceAndTrailingSemicolons_ReturnsExpectedValues(string input, string expectedOutput)
|
||||
{
|
||||
// Arrange and Act
|
||||
var output = MvcRazorCodeParser.RemoveWhitespaceAndTrailingSemicolons(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedOutput, output, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private static ChunkTree CreateChunkTree(params Chunk[] chunks)
|
||||
{
|
||||
return new ChunkTree
|
||||
{
|
||||
Children = chunks
|
||||
};
|
||||
}
|
||||
|
||||
private static TagHelperDirectiveDescriptor CreateDirectiveDescriptor(
|
||||
string directiveText,
|
||||
TagHelperDirectiveType directiveType)
|
||||
{
|
||||
return new TagHelperDirectiveDescriptor
|
||||
{
|
||||
DirectiveText = directiveText,
|
||||
Location = SourceLocation.Undefined,
|
||||
DirectiveType = directiveType
|
||||
};
|
||||
}
|
||||
|
||||
private class TestableMvcRazorParser : MvcRazorParser
|
||||
{
|
||||
public TestableMvcRazorParser(
|
||||
RazorParser parser,
|
||||
IReadOnlyList<ChunkTree> chunkTrees,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
: base(parser, chunkTrees, defaultInheritedChunks, typeof(ModelExpression).FullName)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<TagHelperDescriptor> GetTagHelperDescriptorsPublic(
|
||||
Block documentRoot,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
return GetTagHelperDescriptors(documentRoot, errorSink);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
public class MvcTagHelperAttributeValueCodeRendererTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Theory]
|
||||
[InlineData("SomeType", "SomeType", "Provider.SomeMethod(ViewData, __model => __model.MyValue)")]
|
||||
[InlineData("SomeType", "SomeType2", "MyValue")]
|
||||
|
|
@ -55,5 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
// Assert
|
||||
Assert.Equal(expectedValue, writer.GenerateCode());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
public class DefaultRoslynCompilationServiceTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Fact]
|
||||
public void Compile_ReturnsCompilationResult()
|
||||
{
|
||||
|
|
@ -357,5 +358,6 @@ public class MyNonCustomDefinedClass {}
|
|||
optionsAccessor,
|
||||
NullLoggerFactory.Instance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
public class RazorCompilationServiceTest
|
||||
{
|
||||
#if OLD_RAZOR
|
||||
[Theory]
|
||||
[InlineData(@"src\work\myapp", @"src\work\myapp\Views\index\home.cshtml")]
|
||||
[InlineData(@"src\work\myapp\", @"src\work\myapp\Views\index\home.cshtml")]
|
||||
public void CompileCalculatesRootRelativePath(string appPath, string viewPath)
|
||||
{
|
||||
// Arrange
|
||||
var host = new Mock<IMvcRazorHost>();
|
||||
var host = new Mock<RazorEngineHost>();
|
||||
host.Setup(h => h.GenerateCode(@"Views\index\home.cshtml", It.IsAny<Stream>()))
|
||||
.Returns(GetGeneratorResult())
|
||||
.Verifiable();
|
||||
|
|
@ -222,5 +223,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
return options.Object;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,323 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Generator;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test
|
||||
{
|
||||
public class MvcRazorCodeParserTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_AddsModelKeyword()
|
||||
{
|
||||
var parser = new TestMvcCSharpRazorCodeParser();
|
||||
|
||||
Assert.True(parser.HasDirective("model"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesSingleInstance()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model Foo";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" Foo")
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesNullableTypes()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = $"@model Foo?{Environment.NewLine}Bar";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo?" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo?"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesArrays()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = $"@model Foo[[]][]{Environment.NewLine}Bar";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo[[]][]" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo[[]][]"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupChunkGenerator())
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesVSTemplateSyntax()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model $rootnamespace$.MyModel";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("$rootnamespace$.MyModel")
|
||||
.As(new ModelChunkGenerator("$rootnamespace$.MyModel"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMissingModelType()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document = "@model ";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" ")
|
||||
.As(new ModelChunkGenerator(string.Empty))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'model' keyword must be followed by a type name on the same line.", new SourceLocation(1, 0, 1), 5)
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMultipleModelStatements()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@model Bar";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new ModelChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"Only one 'model' statement is allowed in a file.",
|
||||
PlatformNormalizer.NormalizedSourceLocation(13, 1, 1),
|
||||
5)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@inherits Bar";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo" + Environment.NewLine)
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new SetBaseTypeChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"The 'inherits' keyword is not allowed when a 'model' keyword is used.",
|
||||
PlatformNormalizer.NormalizedSourceLocation(21, 1, 9),
|
||||
length: 8)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@inherits Bar" + Environment.NewLine
|
||||
+ "@model Foo";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar" + Environment.NewLine)
|
||||
.As(new SetBaseTypeChunkGenerator("Bar"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo")
|
||||
.As(new ModelChunkGenerator("Foo"))
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline),
|
||||
factory.EmptyHtml()
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"The 'inherits' keyword is not allowed when a 'model' keyword is used.",
|
||||
new SourceLocation(9, 0, 9),
|
||||
length: 8)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
private static List<Span> ParseDocument(string documentContents, IList<RazorError> errors = null)
|
||||
{
|
||||
errors = errors ?? new List<RazorError>();
|
||||
var markupParser = new HtmlMarkupParser();
|
||||
var codeParser = new TestMvcCSharpRazorCodeParser();
|
||||
var context = new ParserContext(
|
||||
new SeekableTextReader(documentContents),
|
||||
codeParser,
|
||||
markupParser,
|
||||
markupParser,
|
||||
new ErrorSink());
|
||||
codeParser.Context = context;
|
||||
markupParser.Context = context;
|
||||
markupParser.ParseDocument();
|
||||
|
||||
ParserResults results = context.CompleteParse();
|
||||
foreach (RazorError error in results.ParserErrors)
|
||||
{
|
||||
errors.Add(error);
|
||||
}
|
||||
return results.Document.Flatten().ToList();
|
||||
}
|
||||
|
||||
private sealed class TestMvcCSharpRazorCodeParser : MvcRazorCodeParser
|
||||
{
|
||||
public bool HasDirective(string directive)
|
||||
{
|
||||
Action handler;
|
||||
return TryGetDirectiveHandler(directive, out handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
public class RazorPageTest
|
||||
{
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = writer => Task.FromResult(0);
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = () => Task.FromResult(0);
|
||||
private readonly Func<TextWriter, Task> NullAsyncWrite = writer => writer.WriteAsync(string.Empty);
|
||||
|
||||
[Fact]
|
||||
|
|
@ -443,7 +443,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "bar", writer => writer.WriteAsync(expected) }
|
||||
{ "bar", () => page.Output.WriteAsync(expected) }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -767,7 +767,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "ignored", _nullRenderAsyncDelegate },
|
||||
{ "not-ignored-section", writer => writer.WriteAsync("not-ignored-section-content") }
|
||||
{ "not-ignored-section", () => page.Output.WriteAsync("not-ignored-section-content") }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -826,16 +826,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{
|
||||
"footer", writer => writer.WriteLineAsync("Footer section")
|
||||
"footer", () => page.Output.WriteLineAsync("Footer section")
|
||||
},
|
||||
{
|
||||
"header", writer => writer.WriteLineAsync("Header section")
|
||||
"header", () => page.Output.WriteLineAsync("Header section")
|
||||
},
|
||||
{
|
||||
"async-header", writer => writer.WriteLineAsync("Async Header section")
|
||||
"async-header", () => page.Output.WriteLineAsync("Async Header section")
|
||||
},
|
||||
{
|
||||
"async-footer", writer => writer.WriteLineAsync("Async Footer section")
|
||||
"async-footer", () => page.Output.WriteLineAsync("Async Footer section")
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -928,7 +928,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
var page = CreatePage(p =>
|
||||
{
|
||||
p.Layout = "bar";
|
||||
p.DefineSection("test-section", async _ =>
|
||||
p.DefineSection("test-section", async () =>
|
||||
{
|
||||
await p.FlushAsync();
|
||||
});
|
||||
|
|
@ -940,7 +940,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
|
||||
// Assert (does not throw)
|
||||
var renderAsyncDelegate = page.SectionWriters["test-section"];
|
||||
await renderAsyncDelegate(TextWriter.Null);
|
||||
await renderAsyncDelegate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
private const string LayoutPath = "~/Shared/_Layout.cshtml";
|
||||
|
||||
#pragma warning disable 1998
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async writer => { };
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async () => { };
|
||||
#pragma warning restore 1998
|
||||
|
||||
[Fact]
|
||||
|
|
@ -601,13 +601,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.WriteLiteral("body-content");
|
||||
v.Layout = LayoutPath;
|
||||
v.DefineSection("head", async writer =>
|
||||
v.DefineSection("head", async () =>
|
||||
{
|
||||
await writer.WriteAsync("head-content");
|
||||
await v.Output.WriteAsync("head-content");
|
||||
});
|
||||
v.DefineSection("foot", async writer =>
|
||||
v.DefineSection("foot", async () =>
|
||||
{
|
||||
await writer.WriteAsync("foot-content");
|
||||
await v.Output.WriteAsync("foot-content");
|
||||
});
|
||||
});
|
||||
var layout = new TestableRazorPage(v =>
|
||||
|
|
@ -702,9 +702,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteAsync("page-section-content");
|
||||
await v.Output.WriteAsync("page-section-content");
|
||||
});
|
||||
});
|
||||
var nestedLayout = new TestableRazorPage(v =>
|
||||
|
|
@ -712,9 +712,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
v.RenderBodyPublic();
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("layout-section-content");
|
||||
await v.Output.WriteLineAsync("layout-section-content");
|
||||
await v.RenderSectionAsync("foo");
|
||||
});
|
||||
})
|
||||
|
|
@ -769,9 +769,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "NestedLayout";
|
||||
v.WriteLiteral("Page body content that will not be written");
|
||||
v.DefineSection("sectionA", async writer =>
|
||||
v.DefineSection("sectionA", async () =>
|
||||
{
|
||||
await writer.WriteAsync("page-section-content");
|
||||
await v.Output.WriteAsync("page-section-content");
|
||||
});
|
||||
});
|
||||
var nestedLayout = new TestableRazorPage(v =>
|
||||
|
|
@ -779,9 +779,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "Layout";
|
||||
v.WriteLiteral("Nested layout content that will not be written");
|
||||
v.DefineSection("sectionB", async writer =>
|
||||
v.DefineSection("sectionB", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("layout-section-content");
|
||||
await v.Output.WriteLineAsync("layout-section-content");
|
||||
await v.RenderSectionAsync("sectionA");
|
||||
});
|
||||
});
|
||||
|
|
@ -832,9 +832,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("BodyContent");
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
await v.Output.WriteLineAsync("foo-content");
|
||||
});
|
||||
});
|
||||
var nestedLayout = new TestableRazorPage(v =>
|
||||
|
|
@ -843,7 +843,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
v.Write("NestedLayout" + Environment.NewLine);
|
||||
v.RenderBodyPublic();
|
||||
v.DefineSection("foo", async _ =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await v.RenderSectionAsync("foo");
|
||||
});
|
||||
|
|
@ -894,9 +894,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("BodyContent");
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
await v.Output.WriteLineAsync("foo-content");
|
||||
});
|
||||
})
|
||||
{
|
||||
|
|
@ -909,9 +909,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
v.Write("NestedLayout" + Environment.NewLine);
|
||||
v.RenderBodyPublic();
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("dont-render-inner-foo");
|
||||
await v.Output.WriteLineAsync("dont-render-inner-foo");
|
||||
});
|
||||
})
|
||||
{
|
||||
|
|
@ -1003,9 +1003,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.HtmlEncoder = htmlEncoder;
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
await v.Output.WriteLineAsync("foo-content");
|
||||
});
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("body-content");
|
||||
|
|
@ -1015,7 +1015,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Write("layout-1" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("foo"));
|
||||
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
|
||||
v.DefineSection("bar", () => v.Output.WriteLineAsync("bar-content"));
|
||||
v.RenderBodyPublic();
|
||||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
});
|
||||
|
|
@ -1068,9 +1068,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.HtmlEncoder = htmlEncoder;
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
await v.Output.WriteLineAsync("foo-content");
|
||||
});
|
||||
v.Layout = "Layout1.cshtml";
|
||||
v.WriteLiteral("body-content");
|
||||
|
|
@ -1084,7 +1084,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Write("layout-1" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("foo"));
|
||||
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
|
||||
v.DefineSection("bar", () => v.Output.WriteLineAsync("bar-content"));
|
||||
v.RenderBodyPublic();
|
||||
v.Layout = "Layout2.cshtml";
|
||||
})
|
||||
|
|
@ -1238,9 +1238,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("BodyContent");
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
await v.Output.WriteLineAsync("foo-content");
|
||||
});
|
||||
});
|
||||
var nestedLayout = new TestableRazorPage(v =>
|
||||
|
|
@ -1249,9 +1249,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
v.Write("NestedLayout" + Environment.NewLine);
|
||||
v.RenderBodyPublic();
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
await writer.WriteLineAsync(htmlEncoder.Encode(v.RenderSection("foo").ToString()));
|
||||
await v.Output.WriteLineAsync(htmlEncoder.Encode(v.RenderSection("foo").ToString()));
|
||||
});
|
||||
});
|
||||
nestedLayout.Path = "~/Shared/Layout1.cshtml";
|
||||
|
|
@ -1306,7 +1306,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "layout-1";
|
||||
v.WriteLiteral("body content" + Environment.NewLine);
|
||||
v.DefineSection("foo", async _ =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
v.WriteLiteral("section-content-1" + Environment.NewLine);
|
||||
await v.FlushAsync();
|
||||
|
|
@ -1360,7 +1360,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
v.HtmlEncoder = htmlEncoder;
|
||||
v.Layout = "layout-1";
|
||||
v.DefineSection("foo", async _ =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
v.WriteLiteral("section-content-1" + Environment.NewLine);
|
||||
await v.FlushAsync();
|
||||
|
|
@ -1437,9 +1437,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
v.Path = "/Views/TestPath/Test.cshtml";
|
||||
v.HtmlEncoder = new HtmlTestEncoder();
|
||||
v.DefineSection("foo", async writer =>
|
||||
v.DefineSection("foo", async () =>
|
||||
{
|
||||
writer.WriteLine("foo-content");
|
||||
v.Output.WriteLine("foo-content");
|
||||
await v.FlushAsync();
|
||||
});
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
|
|
@ -1450,7 +1450,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
v.HtmlEncoder = new HtmlTestEncoder();
|
||||
v.Write("layout-1" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("foo"));
|
||||
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
|
||||
v.DefineSection("bar", () => v.Output.WriteLineAsync("bar-content"));
|
||||
v.RenderBodyPublic();
|
||||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Microsoft.Extensions.Options;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
|
|
@ -298,7 +299,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
return new DefaultRazorProjectItem(testFileInfo, basePath, path);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
@model long
|
||||
|
||||
<p>The passed through value was: @Model</p>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model ViewModel
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model int?
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model long?
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model TemplateModel
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model ViewModel
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
@model ViewModel
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model HtmlGenerationWebSite.Models.Employee
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@model Item
|
||||
|
||||
@Html.EditorFor(model => model.Name)
|
||||
@Html.Editor("Id")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model HtmlGenerationWebSite.Models.Product
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model HtmlGenerationWebSite.Models.DayOfWeek
|
||||
|
||||
@switch (Model)
|
||||
{
|
||||
case HtmlGenerationWebSite.Models.DayOfWeek.Monday:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
|
|
@ -15,14 +14,15 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
{
|
||||
public TestRazorCompilationService(
|
||||
ICompilationService compilationService,
|
||||
IMvcRazorHost razorHost,
|
||||
RazorEngine engine,
|
||||
RazorProject project,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(compilationService, razorHost, fileProviderAccessor, loggerFactory)
|
||||
: base(compilationService, engine, project, fileProviderAccessor, loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override GeneratorResults GenerateCode(string relativePath, Stream inputStream)
|
||||
protected override RazorCodeDocument CreateCodeDocument(string relativePath, Stream inputStream)
|
||||
{
|
||||
// Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and
|
||||
// .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle
|
||||
|
|
@ -36,7 +36,7 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
inputStream = new MemoryStream(bytes);
|
||||
|
||||
return base.GenerateCode(relativePath, inputStream);
|
||||
return base.CreateCodeDocument(relativePath, inputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
@inject TaskReturningService TaskReturningService
|
||||
|
||||
@{
|
||||
Layout = "/Views/Shared/_LayoutWithPartialAndFlush.cshtml";
|
||||
ViewBag.Title = "FlushAsync invoked inside RenderSection";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@inject TaskReturningService TaskReturningService
|
||||
|
||||
@{
|
||||
Layout = "/Views/Shared/_LayoutWithRenderSectionAsync.cshtml";
|
||||
ViewBag.Title = "FlushAsync invoked inside RenderSectionAsync";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model RazorWebSite.Controllers.DateModel
|
||||
|
||||
@Html.ValidationSummary(true, "MySummary")
|
||||
@Html.ValidationMessage("Error")
|
||||
@Html.TextBox("Prefix.Property1")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model RazorWebSite.Controllers.DateModel
|
||||
|
||||
@Html.ValidationSummary(true, "MySummary")
|
||||
@Html.ValidationMessage("Error")
|
||||
@Html.TextBox("Prefix.Property1")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model TagHelpersWebSite.Models.Employee
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
@{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@model TagHelpersWebSite.Models.Employee
|
||||
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,52 @@
|
|||
@using TagHelpersWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.Razor
|
||||
|
||||
@model WebsiteContext
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Home Page";
|
||||
}
|
||||
|
||||
@addTagHelper *, TagHelpersWebSite
|
||||
|
||||
@section css {
|
||||
<style condition="!Model.Approved">
|
||||
h1 {
|
||||
color:red;
|
||||
font-size:2em;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@functions {
|
||||
public void RenderTemplate(string title, Func<string, HelperResult> template)
|
||||
{
|
||||
Output.WriteLine("<br /><p><em>Rendering Template:</em></p>");
|
||||
var helperResult = template(title);
|
||||
helperResult.WriteTo(Output, HtmlEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
<div condition="!Model.Approved">
|
||||
<p>This website has <strong surround="em">not</strong> been approved yet. Visit www.contoso.com for <strong make-pretty="false">more</strong> information.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Current Tag Cloud from Tag Helper</h3>
|
||||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
||||
<section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
|
||||
@{
|
||||
RenderTemplate(
|
||||
"Tag Cloud from Template: ",
|
||||
@<div condition="true"><h3>@item</h3><tag-cloud count="Model.TagsToShow"></tag-cloud></div>);
|
||||
}
|
||||
</div>
|
||||
|
||||
@using TagHelpersWebSite.Models
|
||||
@using Microsoft.AspNetCore.Mvc.Razor
|
||||
|
||||
@model WebsiteContext
|
||||
@{
|
||||
ViewBag.Title = "Home Page";
|
||||
}
|
||||
|
||||
@addTagHelper *, TagHelpersWebSite
|
||||
|
||||
@section css {
|
||||
<style condition="!Model.Approved">
|
||||
h1 {
|
||||
color:red;
|
||||
font-size:2em;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@functions {
|
||||
public void RenderTemplate(string title, Func<string, HelperResult> template)
|
||||
{
|
||||
Output.WriteLine("<br /><p><em>Rendering Template:</em></p>");
|
||||
var helperResult = template(title);
|
||||
helperResult.WriteTo(Output, HtmlEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
<div condition="!Model.Approved">
|
||||
<p>This website has <strong surround="em">not</strong> been approved yet. Visit www.contoso.com for <strong make-pretty="false">more</strong> information.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Current Tag Cloud from Tag Helper</h3>
|
||||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
||||
<section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
|
||||
@{
|
||||
RenderTemplate(
|
||||
"Tag Cloud from Template: ",
|
||||
@<div condition="true"><h3>@item</h3><tag-cloud count="Model.TagsToShow"></tag-cloud></div>);
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Dictionary Valued Model Expression</h3>
|
||||
<div prefix-test1="@Model.TagsToShow" prefix-test2="@Model.Version.Build"></div>
|
||||
</div>
|
||||
|
||||
@section footerContent {
|
||||
<p condition="Model.Approved" bold surround="section">© @Model.CopyrightYear - My ASP.NET Application</p>
|
||||
</div>
|
||||
|
||||
@section footerContent {
|
||||
<p condition="Model.Approved" bold surround="section">© @Model.CopyrightYear - My ASP.NET Application</p>
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
@model Dictionary<string, List<string>>
|
||||
|
||||
<div>Items: </div>
|
||||
<div>
|
||||
@foreach (var item in Model)
|
||||
|
|
|
|||
Loading…
Reference in New Issue