Use new Razor in MVC

This commit is contained in:
Ryan Nowak 2017-01-27 11:10:05 -08:00
parent 7449ffad74
commit 84d2e027f5
95 changed files with 1395 additions and 4252 deletions

30
Mvc.sln
View File

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

View File

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

View File

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

View File

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

View File

@ -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 &lt;TModel&gt; 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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &lt;TModel&gt; 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
return reader.ReadToEnd();
var chars = new char[codeDocument.Source.Length];
codeDocument.Source.CopyTo(0, chars, 0, chars.Length);
return new string(chars);
}
}
catch
for (var i = 0; i < codeDocument.Imports.Count; i++)
{
// Ignore any failures
var import = codeDocument.Imports[i];
if (filePath == import.Filename)
{
var chars = new char[codeDocument.Source.Length];
codeDocument.Source.CopyTo(0, chars, 0, chars.Length);
return new string(chars);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
},

View File

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

View File

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

View File

@ -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-*",

View File

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

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public HttpClient Client { get; }
[Fact]
[Fact(Skip = "Razor #961")]
public async Task InstrumentedViews_RenderAsExpected()
{
// Arrange

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,2 @@
@model long
<p>The passed through value was: @Model</p>

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model ViewModel
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model int?
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model long?
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model TemplateModel
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model ViewModel
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,7 +1,6 @@
@using HtmlGenerationWebSite.Models
@using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@model ViewModel
@{
var metadata = ViewData.ModelMetadata;
}

View File

@ -1,5 +1,4 @@
@model HtmlGenerationWebSite.Models.Employee
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div>

View File

@ -1,5 +1,4 @@
@using HtmlGenerationWebSite.Models
@model Item
@Html.EditorFor(model => model.Name)
@Html.Editor("Id")

View File

@ -1,5 +1,4 @@
@model HtmlGenerationWebSite.Models.Product
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div>

View File

@ -1,5 +1,4 @@
@model HtmlGenerationWebSite.Models.DayOfWeek
@switch (Model)
{
case HtmlGenerationWebSite.Models.DayOfWeek.Monday:

View File

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

View File

@ -1,5 +1,4 @@
@inject TaskReturningService TaskReturningService
@{
Layout = "/Views/Shared/_LayoutWithPartialAndFlush.cshtml";
ViewBag.Title = "FlushAsync invoked inside RenderSection";

View File

@ -1,5 +1,4 @@
@inject TaskReturningService TaskReturningService
@{
Layout = "/Views/Shared/_LayoutWithRenderSectionAsync.cshtml";
ViewBag.Title = "FlushAsync invoked inside RenderSectionAsync";

View File

@ -1,5 +1,4 @@
@model RazorWebSite.Controllers.DateModel
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")

View File

@ -1,5 +1,4 @@
@model RazorWebSite.Controllers.DateModel
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")

View File

@ -1,5 +1,4 @@
@model TagHelpersWebSite.Models.Employee
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{

View File

@ -1,5 +1,4 @@
@model TagHelpersWebSite.Models.Employee
@{
Layout = null;
}

View File

@ -2,7 +2,6 @@
@using Microsoft.AspNetCore.Mvc.Razor
@model WebsiteContext
@{
ViewBag.Title = "Home Page";
}

View File

@ -1,5 +1,4 @@
@model Dictionary<string, List<string>>
<div>Items: </div>
<div>
@foreach (var item in Model)