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

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

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -212,4 +212,7 @@
<data name="FileProvidersAreRequired" xml:space="preserve">
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
</data>
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
<value>Path must begin with a forward slash '/'.</value>
</data>
</root>

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

@ -1,53 +1,52 @@
@using TagHelpersWebSite.Models
@using Microsoft.AspNetCore.Mvc.Razor
@model WebsiteContext
@{
ViewBag.Title = "Home Page";
}
@addTagHelper *, TagHelpersWebSite
@section css {
<style condition="!Model.Approved">
h1 {
color:red;
font-size:2em;
}
</style>
}
@functions {
public void RenderTemplate(string title, Func<string, HelperResult> template)
{
Output.WriteLine("<br /><p><em>Rendering Template:</em></p>");
var helperResult = template(title);
helperResult.WriteTo(Output, HtmlEncoder);
}
}
<div condition="!Model.Approved">
<p>This website has <strong surround="em">not</strong> been approved yet. Visit www.contoso.com for <strong make-pretty="false">more</strong> information.</p>
</div>
<div>
<h3>Current Tag Cloud from Tag Helper</h3>
<tag-cloud count="Model.TagsToShow" surround="div" />
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
<section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
@{
RenderTemplate(
"Tag Cloud from Template: ",
@<div condition="true"><h3>@item</h3><tag-cloud count="Model.TagsToShow"></tag-cloud></div>);
}
</div>
@using TagHelpersWebSite.Models
@using Microsoft.AspNetCore.Mvc.Razor
@model WebsiteContext
@{
ViewBag.Title = "Home Page";
}
@addTagHelper *, TagHelpersWebSite
@section css {
<style condition="!Model.Approved">
h1 {
color:red;
font-size:2em;
}
</style>
}
@functions {
public void RenderTemplate(string title, Func<string, HelperResult> template)
{
Output.WriteLine("<br /><p><em>Rendering Template:</em></p>");
var helperResult = template(title);
helperResult.WriteTo(Output, HtmlEncoder);
}
}
<div condition="!Model.Approved">
<p>This website has <strong surround="em">not</strong> been approved yet. Visit www.contoso.com for <strong make-pretty="false">more</strong> information.</p>
</div>
<div>
<h3>Current Tag Cloud from Tag Helper</h3>
<tag-cloud count="Model.TagsToShow" surround="div" />
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
<section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
@{
RenderTemplate(
"Tag Cloud from Template: ",
@<div condition="true"><h3>@item</h3><tag-cloud count="Model.TagsToShow"></tag-cloud></div>);
}
</div>
<div>
<h3>Dictionary Valued Model Expression</h3>
<div prefix-test1="@Model.TagsToShow" prefix-test2="@Model.Version.Build"></div>
</div>
@section footerContent {
<p condition="Model.Approved" bold surround="section">&copy; @Model.CopyrightYear - My ASP.NET Application</p>
</div>
@section footerContent {
<p condition="Model.Approved" bold surround="section">&copy; @Model.CopyrightYear - My ASP.NET Application</p>
}

View File

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