зеркало из https://github.com/aspnet/Mvc.git
Add Visual Studio specific RC1 binaries.
- This is needed for Visual Studio RC1 backwards compatibility.
This commit is contained in:
Родитель
701869fd53
Коммит
687fd72efd
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
VisualStudioVersion = 14.0.25123.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
@ -103,6 +103,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSubAreaSample.Web", "sam
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Dnx", "src\Microsoft.AspNetCore.Mvc.Dnx\Microsoft.AspNetCore.Mvc.Dnx.xproj", "{8FB691C2-DFD8-4FEE-9628-2BB8466A691C}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Host.VSRC1", "src\Microsoft.AspNet.Mvc.Razor.Host.VSRC1\Microsoft.AspNet.Mvc.Razor.Host.VSRC1.xproj", "{85C54A84-3E60-40E1-BE39-C2F514DD922E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -619,6 +621,18 @@ Global
|
|||
{8FB691C2-DFD8-4FEE-9628-2BB8466A691C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{8FB691C2-DFD8-4FEE-9628-2BB8466A691C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8FB691C2-DFD8-4FEE-9628-2BB8466A691C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -668,5 +682,6 @@ Global
|
|||
{EE0BD773-4D47-4AA8-8472-5A938A3953BA} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
|
||||
{45F6B3B6-D114-4D77-84D6-561B3957F341} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
|
||||
{8FB691C2-DFD8-4FEE-9628-2BB8466A691C} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{85C54A84-3E60-40E1-BE39-C2F514DD922E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"Microsoft.AspNetCore.Mvc.Localization": { },
|
||||
"Microsoft.AspNetCore.Mvc.Razor": { },
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Host": { },
|
||||
"Microsoft.AspNet.Mvc.Razor.Host.VSRC1": { },
|
||||
"Microsoft.AspNetCore.Mvc.TagHelpers": { },
|
||||
"Microsoft.AspNetCore.Mvc.ViewFeatures": { },
|
||||
"Microsoft.AspNetCore.Mvc.WebApiCompatShim": { }
|
||||
|
|
|
@ -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.Linq;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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
|
||||
.Chunks
|
||||
.OfType<ModelChunk>()
|
||||
.LastOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type name of the Model specified via a <see cref="ModelChunk"/> in the
|
||||
/// <paramref name="chunkTree"/> if specified or the default model type.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="ChunkTree"/> to scan for <see cref="ModelChunk"/>s in.</param>
|
||||
/// <param name="defaultModelName">The <see cref="Type"/> name of the default model.</param>
|
||||
/// <returns>The model type name for the generated page.</returns>
|
||||
public static string GetModelTypeName(
|
||||
ChunkTree chunkTree,
|
||||
string defaultModelName)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (defaultModelName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaultModelName));
|
||||
}
|
||||
|
||||
var modelChunk = GetModelChunk(chunkTree);
|
||||
return modelChunk != null ? modelChunk.ModelType : defaultModelName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with the <TModel> token replaced with the value specified in
|
||||
/// <paramref name="modelName"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to replace the token in.</param>
|
||||
/// <param name="modelName">The model name to replace with.</param>
|
||||
/// <returns>A string with the token replaced.</returns>
|
||||
public static string ReplaceTModel(
|
||||
string value,
|
||||
string modelName)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (modelName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelName));
|
||||
}
|
||||
|
||||
return value.Replace(TModelReplaceToken, modelName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
// 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.AspNet.FileProviders.VSRC1;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
||||
namespace Microsoft.AspNet.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.Chunks)
|
||||
{
|
||||
foreach (var merger in chunkMergers)
|
||||
{
|
||||
merger.VisitChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
var inheritedChunks = _defaultInheritedChunks.Concat(
|
||||
inheritedChunkTrees.SelectMany(tree => tree.Chunks)).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.ChunkTree;
|
||||
foreach (var chunk in chunkTree.Chunks)
|
||||
{
|
||||
chunk.Start = new SourceLocation(
|
||||
viewImportsPath,
|
||||
chunk.Start.AbsoluteIndex,
|
||||
chunk.Start.LineIndex,
|
||||
chunk.Start.CharacterIndex);
|
||||
}
|
||||
|
||||
return chunkTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains <see cref="AspNet.Razor.Chunks.ChunkTree"/> information.
|
||||
/// </summary>
|
||||
public class ChunkTreeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ChunkTreeResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunkTree">The <see cref="AspNet.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="AspNet.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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// 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.AspNet.FileProviders.VSRC1;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.Extensions.Caching.Memory.VSRC1;
|
||||
|
||||
namespace Microsoft.AspNet.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.FileProviders.VSRC1;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A cache for parsed <see cref="ChunkTree"/>s.
|
||||
/// </summary>
|
||||
public interface IChunkTreeCache
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="InjectChunk"/> instances.
|
||||
/// </summary>
|
||||
public class InjectChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly HashSet<string> _addedMemberNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
private string _modelType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="InjectChunkMerger"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The model type to be used to replace <TModel> tokens.</param>
|
||||
public InjectChunkMerger(string modelType)
|
||||
{
|
||||
if (modelType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelType));
|
||||
}
|
||||
|
||||
_modelType = "<" + modelType + ">";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk(Chunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
var injectChunk = chunk as InjectChunk;
|
||||
if (injectChunk != null)
|
||||
{
|
||||
injectChunk.TypeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
|
||||
_addedMemberNames.Add(injectChunk.MemberName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList<Chunk> inheritedChunks)
|
||||
{
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
if (inheritedChunks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inheritedChunks));
|
||||
}
|
||||
|
||||
for (var i = inheritedChunks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var injectChunk = inheritedChunks[i] as InjectChunk;
|
||||
if (injectChunk != null &&
|
||||
_addedMemberNames.Add(injectChunk.MemberName))
|
||||
{
|
||||
chunkTree.Chunks.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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.Chunks.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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.Chunks.Add(namespaceChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information for the <see cref="AspNet.Razor.TagHelpers.ITagHelper"/> attribute code
|
||||
/// generation process.
|
||||
/// </summary>
|
||||
public class GeneratedTagHelperAttributeContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the model expression type.
|
||||
/// </summary>
|
||||
public string ModelExpressionTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name the method to create <c>ModelExpression</c>s.
|
||||
/// </summary>
|
||||
public string CreateModelExpressionMethodName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// 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.AspNet.Razor.CodeGenerators;
|
||||
|
||||
namespace Microsoft.AspNet.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 prefix off the main entry class in the view.
|
||||
/// </summary>
|
||||
string MainClassNamePrefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represent the namespace the main entry class in the view.
|
||||
/// </summary>
|
||||
string DefaultNamespace { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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; }
|
||||
}
|
||||
}
|
|
@ -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.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class InjectChunkVisitor : MvcCSharpCodeVisitor
|
||||
{
|
||||
private readonly string _injectAttribute;
|
||||
|
||||
public InjectChunkVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context,
|
||||
string injectAttributeName)
|
||||
: base(writer, context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (injectAttributeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(injectAttributeName));
|
||||
}
|
||||
|
||||
_injectAttribute = "[" + injectAttributeName + "]";
|
||||
}
|
||||
|
||||
public IList<InjectChunk> InjectChunks { get; } = new List<InjectChunk>();
|
||||
|
||||
protected override void Visit(InjectChunk chunk)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunk));
|
||||
}
|
||||
|
||||
Writer.WriteLine(_injectAttribute);
|
||||
|
||||
// Some of the chunks that we visit are either InjectDescriptors that are added by default or
|
||||
// are chunks from _ViewStart files and are not associated with any Spans. Invoking
|
||||
// CreateExpressionMapping to produce line mappings on these chunks would fail. We'll skip
|
||||
// generating code mappings for these chunks. This makes sense since the chunks do not map
|
||||
// to any code in the current view.
|
||||
if (Context.Host.DesignTimeMode && chunk.Association != null)
|
||||
{
|
||||
Writer.WriteLine("public");
|
||||
|
||||
var code = string.IsNullOrEmpty(chunk.MemberName) ?
|
||||
chunk.TypeName :
|
||||
chunk.TypeName + " " + chunk.MemberName;
|
||||
var csharpVisitor = new CSharpCodeVisitor(Writer, Context);
|
||||
csharpVisitor.CreateExpressionCodeMapping(code, chunk);
|
||||
Writer.WriteLine("{ get; private set; }");
|
||||
}
|
||||
else
|
||||
{
|
||||
Writer.Write("public ")
|
||||
.Write(chunk.TypeName)
|
||||
.Write(" ")
|
||||
.Write(chunk.MemberName)
|
||||
.WriteLine(" { get; private set; }");
|
||||
}
|
||||
|
||||
InjectChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// 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.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Internal
|
||||
{
|
||||
public class DesignTimeRazorPathNormalizer : RazorPathNormalizer
|
||||
{
|
||||
private readonly string _applicationRoot;
|
||||
|
||||
public DesignTimeRazorPathNormalizer(string applicationRoot)
|
||||
{
|
||||
if (applicationRoot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationRoot));
|
||||
}
|
||||
|
||||
_applicationRoot = applicationRoot;
|
||||
}
|
||||
|
||||
public override string NormalizePath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
// Need to convert path to application relative (rooted paths are passed in during design time).
|
||||
if (Path.IsPathRooted(path) && path.StartsWith(_applicationRoot, StringComparison.Ordinal))
|
||||
{
|
||||
path = path.Substring(_applicationRoot.Length);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Internal
|
||||
{
|
||||
public class RazorPathNormalizer
|
||||
{
|
||||
public virtual string NormalizePath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>85c54a84-3e60-40e1-be39-c2f514dd922e</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
|
||||
namespace Microsoft.AspNet.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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Generator
|
||||
{
|
||||
public class ModelChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public ModelChunkGenerator(string modelType)
|
||||
{
|
||||
ModelType = modelType;
|
||||
}
|
||||
|
||||
public string ModelType { get; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
var modelChunk = new ModelChunk(ModelType);
|
||||
context.ChunkTreeBuilder.AddChunk(modelChunk, target, topLevel: true);
|
||||
}
|
||||
|
||||
public override string ToString() => ModelType;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as ModelChunkGenerator;
|
||||
return other != null &&
|
||||
string.Equals(ModelType, other.ModelType, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return StringComparer.Ordinal.GetHashCode(ModelType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNet.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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class MvcCSharpCodeGenerator : CSharpCodeGenerator
|
||||
{
|
||||
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
||||
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;
|
||||
}
|
||||
|
||||
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 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.ChunkTree.Chunks);
|
||||
|
||||
writer.WriteLine();
|
||||
writer.WriteLineHiddenDirective();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
|
||||
namespace Microsoft.AspNet.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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNet.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(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// 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.AspNet.Mvc.Razor.Host;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
||||
namespace Microsoft.AspNet.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
// 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.AspNet.FileProviders.VSRC1;
|
||||
using Microsoft.AspNet.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNet.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
|
||||
{
|
||||
private const string BaseType = "Microsoft.AspNet.Mvc.Razor.RazorPage";
|
||||
private const string HtmlHelperPropertyName = "Html";
|
||||
|
||||
private static readonly string[] _defaultNamespaces = new[]
|
||||
{
|
||||
"System",
|
||||
"System.Linq",
|
||||
"System.Collections.Generic",
|
||||
"Microsoft.AspNet.Mvc",
|
||||
"Microsoft.AspNet.Mvc.Rendering",
|
||||
};
|
||||
private static readonly Chunk[] _defaultInheritedChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", HtmlHelperPropertyName),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IJsonHelper", "Json"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
|
||||
new AddTagHelperChunk
|
||||
{
|
||||
LookupText = "Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNet.Mvc.Razor"
|
||||
},
|
||||
new SetBaseTypeChunk
|
||||
{
|
||||
// Microsoft.Aspnet.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 = "Asp";
|
||||
// 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.AspNet.Mvc.Razor.HelperResult",
|
||||
defineSectionMethodName: "DefineSection",
|
||||
generatedTagHelperContext: new GeneratedTagHelperContext
|
||||
{
|
||||
ExecutionContextTypeName = typeof(TagHelperExecutionContext).FullName,
|
||||
ExecutionContextAddMethodName = nameof(TagHelperExecutionContext.Add),
|
||||
ExecutionContextAddTagHelperAttributeMethodName =
|
||||
nameof(TagHelperExecutionContext.AddTagHelperAttribute),
|
||||
ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute),
|
||||
ExecutionContextAddMinimizedHtmlAttributeMethodName =
|
||||
nameof(TagHelperExecutionContext.AddMinimizedHtmlAttribute),
|
||||
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 = nameof(TagHelperContent),
|
||||
|
||||
// Can't use nameof because RazorPage is not accessible here.
|
||||
CreateTagHelperMethodName = "CreateTagHelper",
|
||||
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
|
||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
||||
|
||||
WriteTagHelperAsyncMethodName = "WriteTagHelperAsync",
|
||||
WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync",
|
||||
|
||||
// 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),
|
||||
})
|
||||
{
|
||||
BeginContextMethodName = "BeginContext",
|
||||
EndContextMethodName = "EndContext"
|
||||
};
|
||||
|
||||
foreach (var ns in _defaultNamespaces)
|
||||
{
|
||||
NamespaceImports.Add(ns);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public MvcRazorHost(IChunkTreeCache chunkTreeCache)
|
||||
: this(chunkTreeCache, new RazorPathNormalizer())
|
||||
{
|
||||
}
|
||||
|
||||
/// <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"; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string MainClassNamePrefix
|
||||
{
|
||||
get { return "ASPV_"; }
|
||||
}
|
||||
|
||||
/// <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.AspNet.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.AspNet.Mvc.Rendering.ModelExpression"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the method name used to create model expressions.
|
||||
/// </summary>
|
||||
public virtual string CreateModelExpressionMethod
|
||||
{
|
||||
get { return "CreateModelExpression"; }
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Adding a prefix so that the main view class can be easily identified.
|
||||
var className = MainClassNamePrefix + 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.ChunkTree,
|
||||
inheritedChunkTrees,
|
||||
DefaultModel);
|
||||
|
||||
return new MvcCSharpCodeGenerator(
|
||||
context,
|
||||
DefaultModel,
|
||||
InjectAttribute,
|
||||
new GeneratedTagHelperAttributeContext
|
||||
{
|
||||
ModelExpressionTypeName = ModelExpressionType,
|
||||
CreateModelExpressionMethodName = CreateModelExpressionMethod
|
||||
});
|
||||
}
|
||||
|
||||
private IReadOnlyList<ChunkTree> GetInheritedChunkTrees(string sourceFileName)
|
||||
{
|
||||
var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName)
|
||||
.Select(result => result.ChunkTree)
|
||||
.ToList();
|
||||
|
||||
return inheritedChunkTrees;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
// 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.AspNet.Mvc.Razor.Host;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Parser.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.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;
|
||||
private readonly string _modelExpressionTypeName;
|
||||
|
||||
/// <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>
|
||||
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);
|
||||
|
||||
_modelExpressionTypeName = modelExpressionTypeName;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
var descriptors = visitor.GetDescriptors(documentRoot);
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
foreach (var attributeDescriptor in descriptor.Attributes)
|
||||
{
|
||||
if (attributeDescriptor.IsIndexer &&
|
||||
string.Equals(
|
||||
attributeDescriptor.TypeName,
|
||||
_modelExpressionTypeName,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Undefined,
|
||||
Resources.FormatMvcRazorParser_InvalidPropertyType(
|
||||
descriptor.TypeName,
|
||||
attributeDescriptor.Name,
|
||||
_modelExpressionTypeName),
|
||||
length: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDirectiveDescriptor> GetTagHelperDirectiveDescriptors(
|
||||
IReadOnlyList<ChunkTree> inheritedChunkTrees,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
{
|
||||
var descriptors = new List<TagHelperDirectiveDescriptor>();
|
||||
|
||||
var inheritedChunks = defaultInheritedChunks.Concat(inheritedChunkTrees.SelectMany(tree => tree.Chunks));
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// 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.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.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
|
||||
.WriteStartMethodInvocation(_context.CreateModelExpressionMethodName)
|
||||
.Write(ModelLambdaVariableName)
|
||||
.Write(" => ");
|
||||
if (!complexValue)
|
||||
{
|
||||
writer
|
||||
.Write(ModelLambdaVariableName)
|
||||
.Write(".");
|
||||
|
||||
}
|
||||
|
||||
renderAttributeValue(writer);
|
||||
|
||||
writer.WriteEndMethodInvocation(endLine: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
writer,
|
||||
codeGeneratorContext,
|
||||
renderAttributeValue,
|
||||
complexValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||
[assembly: NeutralResourcesLanguage("en-us")]
|
126
src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/Resources.Designer.cs
сгенерированный
Normal file
126
src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/Resources.Designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,126 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Host
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Mvc.Razor.Host.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string ArgumentCannotBeNullOrEmpy
|
||||
{
|
||||
get { return GetString("ArgumentCannotBeNullOrEmpy"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentCannotBeNullOrEmpy()
|
||||
{
|
||||
return GetString("ArgumentCannotBeNullOrEmpy");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword
|
||||
{
|
||||
get { return GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired
|
||||
{
|
||||
get { return GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' keyword must be followed by a type name on the same line.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName
|
||||
{
|
||||
get { return GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' keyword must be followed by a type name on the same line.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only one '{0}' statement is allowed in a file.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed
|
||||
{
|
||||
get { return GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only one '{0}' statement is allowed in a file.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string MvcRazorParser_InvalidPropertyType
|
||||
{
|
||||
get { return GetString("MvcRazorParser_InvalidPropertyType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,513 @@
|
|||
// 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal class PropertyHelper
|
||||
{
|
||||
// Delegate type for a by-ref property getter
|
||||
private delegate TValue ByRefFunc<TDeclaringType, TValue>(ref TDeclaringType arg);
|
||||
|
||||
private static readonly MethodInfo CallPropertyGetterOpenGenericMethod =
|
||||
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter));
|
||||
|
||||
private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod =
|
||||
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference));
|
||||
|
||||
private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod =
|
||||
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter));
|
||||
|
||||
private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod =
|
||||
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference));
|
||||
|
||||
private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
|
||||
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter));
|
||||
|
||||
// Using an array rather than IEnumerable, as target will be called on the hot path numerous times.
|
||||
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> PropertiesCache =
|
||||
new ConcurrentDictionary<Type, PropertyHelper[]>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> VisiblePropertiesCache =
|
||||
new ConcurrentDictionary<Type, PropertyHelper[]>();
|
||||
|
||||
private Action<object, object> _valueSetter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a fast <see cref="PropertyHelper"/>.
|
||||
/// This constructor does not cache the helper. For caching, use <see cref="GetProperties(object)"/>.
|
||||
/// </summary>
|
||||
public PropertyHelper(PropertyInfo property)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
Property = property;
|
||||
Name = property.Name;
|
||||
ValueGetter = MakeFastPropertyGetter(property);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backing <see cref="PropertyInfo"/>.
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in derived types) the property name.
|
||||
/// </summary>
|
||||
public virtual string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value getter.
|
||||
/// </summary>
|
||||
public Func<object, object> ValueGetter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value setter.
|
||||
/// </summary>
|
||||
public Action<object, object> ValueSetter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueSetter == null)
|
||||
{
|
||||
// We'll allow safe races here.
|
||||
_valueSetter = MakeFastPropertySetter(Property);
|
||||
}
|
||||
|
||||
return _valueSetter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the property value for the specified <paramref name="instance"/>.
|
||||
/// </summary>
|
||||
/// <param name="instance">The object whose property value will be returned.</param>
|
||||
/// <returns>The property value.</returns>
|
||||
public object GetValue(object instance)
|
||||
{
|
||||
return ValueGetter(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value for the specified <paramref name="instance" />.
|
||||
/// </summary>
|
||||
/// <param name="instance">The object whose property value will be set.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
public void SetValue(object instance, object value)
|
||||
{
|
||||
ValueSetter(instance, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and caches fast property helpers that expose getters for every public get property on the
|
||||
/// underlying type.
|
||||
/// </summary>
|
||||
/// <param name="instance">the instance to extract property accessors for.</param>
|
||||
/// <returns>a cached array of all public property getters from the underlying type of target instance.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetProperties(object instance)
|
||||
{
|
||||
return GetProperties(instance.GetType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and caches fast property helpers that expose getters for every public get property on the
|
||||
/// specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">the type to extract property accessors for.</param>
|
||||
/// <returns>a cached array of all public property getters from the type of target instance.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetProperties(Type type)
|
||||
{
|
||||
return GetProperties(type, CreateInstance, PropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates and caches fast property helpers that expose getters for every non-hidden get property
|
||||
/// on the specified type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="GetVisibleProperties"/> excludes properties defined on base types that have been
|
||||
/// hidden by definitions using the <c>new</c> keyword.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to extract property accessors for.</param>
|
||||
/// <returns>
|
||||
/// A cached array of all public property getters from the instance's type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetVisibleProperties(object instance)
|
||||
{
|
||||
return GetVisibleProperties(instance.GetType(), CreateInstance, PropertiesCache, VisiblePropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates a caches fast property helpers that expose getters for every non-hidden get property
|
||||
/// on the specified type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="GetVisibleProperties"/> excludes properties defined on base types that have been
|
||||
/// hidden by definitions using the <c>new</c> keyword.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="type">The type to extract property accessors for.</param>
|
||||
/// <returns>
|
||||
/// A cached array of all public property getters from the type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetVisibleProperties(Type type)
|
||||
{
|
||||
return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property getter. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed.
|
||||
/// </remarks>
|
||||
public static Func<object, object> MakeFastPropertyGetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
|
||||
return MakeFastPropertyGetter(
|
||||
propertyInfo,
|
||||
CallPropertyGetterOpenGenericMethod,
|
||||
CallPropertyGetterByReferenceOpenGenericMethod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property getter which is safe for a null input object. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed.
|
||||
/// </remarks>
|
||||
public static Func<object, object> MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
|
||||
return MakeFastPropertyGetter(
|
||||
propertyInfo,
|
||||
CallNullSafePropertyGetterOpenGenericMethod,
|
||||
CallNullSafePropertyGetterByReferenceOpenGenericMethod);
|
||||
}
|
||||
|
||||
private static Func<object, object> MakeFastPropertyGetter(
|
||||
PropertyInfo propertyInfo,
|
||||
MethodInfo propertyGetterWrapperMethod,
|
||||
MethodInfo propertyGetterByRefWrapperMethod)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
|
||||
// Must be a generic method with a Func<,> parameter
|
||||
Debug.Assert(propertyGetterWrapperMethod != null);
|
||||
Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition);
|
||||
Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2);
|
||||
|
||||
// Must be a generic method with a ByRefFunc<,> parameter
|
||||
Debug.Assert(propertyGetterByRefWrapperMethod != null);
|
||||
Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition);
|
||||
Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2);
|
||||
|
||||
var getMethod = propertyInfo.GetMethod;
|
||||
Debug.Assert(getMethod != null);
|
||||
Debug.Assert(!getMethod.IsStatic);
|
||||
Debug.Assert(getMethod.GetParameters().Length == 0);
|
||||
|
||||
// Instance methods in the CLR can be turned into static methods where the first parameter
|
||||
// is open over "target". This parameter is always passed by reference, so we have a code
|
||||
// path for value types and a code path for reference types.
|
||||
if (getMethod.DeclaringType.GetTypeInfo().IsValueType)
|
||||
{
|
||||
// Create a delegate (ref TDeclaringType) -> TValue
|
||||
return MakeFastPropertyGetter(
|
||||
typeof(ByRefFunc<,>),
|
||||
getMethod,
|
||||
propertyGetterByRefWrapperMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a delegate TDeclaringType -> TValue
|
||||
return MakeFastPropertyGetter(
|
||||
typeof(Func<,>),
|
||||
getMethod,
|
||||
propertyGetterWrapperMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static Func<object, object> MakeFastPropertyGetter(
|
||||
Type openGenericDelegateType,
|
||||
MethodInfo propertyGetMethod,
|
||||
MethodInfo openGenericWrapperMethod)
|
||||
{
|
||||
var typeInput = propertyGetMethod.DeclaringType;
|
||||
var typeOutput = propertyGetMethod.ReturnType;
|
||||
|
||||
var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput);
|
||||
var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType);
|
||||
|
||||
var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput);
|
||||
var accessorDelegate = wrapperDelegateMethod.CreateDelegate(
|
||||
typeof(Func<object, object>),
|
||||
propertyGetterDelegate);
|
||||
|
||||
return (Func<object, object>)accessorDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property setter for reference types. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the setter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed. This only works for reference types.
|
||||
/// </remarks>
|
||||
public static Action<object, object> MakeFastPropertySetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType);
|
||||
|
||||
var setMethod = propertyInfo.SetMethod;
|
||||
Debug.Assert(setMethod != null);
|
||||
Debug.Assert(!setMethod.IsStatic);
|
||||
Debug.Assert(setMethod.ReturnType == typeof(void));
|
||||
var parameters = setMethod.GetParameters();
|
||||
Debug.Assert(parameters.Length == 1);
|
||||
|
||||
// Instance methods in the CLR can be turned into static methods where the first parameter
|
||||
// is open over "target". This parameter is always passed by reference, so we have a code
|
||||
// path for value types and a code path for reference types.
|
||||
var typeInput = setMethod.DeclaringType;
|
||||
var parameterType = parameters[0].ParameterType;
|
||||
|
||||
// Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; }
|
||||
var propertySetterAsAction =
|
||||
setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType));
|
||||
var callPropertySetterClosedGenericMethod =
|
||||
CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType);
|
||||
var callPropertySetterDelegate =
|
||||
callPropertySetterClosedGenericMethod.CreateDelegate(
|
||||
typeof(Action<object, object>), propertySetterAsAction);
|
||||
|
||||
return (Action<object, object>)callPropertySetterDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an object, adds each instance property with a public get method as a key and its
|
||||
/// associated value to a dictionary.
|
||||
///
|
||||
/// If the object is already an <see cref="IDictionary{string, object}"/> instance, then a copy
|
||||
/// is returned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The implementation of PropertyHelper will cache the property accessors per-type. This is
|
||||
/// faster when the the same type is used multiple times with ObjectToDictionary.
|
||||
/// </remarks>
|
||||
public static IDictionary<string, object> ObjectToDictionary(object value)
|
||||
{
|
||||
var dictionary = value as IDictionary<string, object>;
|
||||
if (dictionary != null)
|
||||
{
|
||||
return new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
foreach (var helper in GetProperties(value))
|
||||
{
|
||||
dictionary[helper.Name] = helper.GetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static PropertyHelper CreateInstance(PropertyInfo property)
|
||||
{
|
||||
return new PropertyHelper(property);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallPropertyGetter<TDeclaringType, TValue>(
|
||||
Func<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
return getter((TDeclaringType)target);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallPropertyGetterByReference<TDeclaringType, TValue>(
|
||||
ByRefFunc<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
var unboxed = (TDeclaringType)target;
|
||||
return getter(ref unboxed);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallNullSafePropertyGetter<TDeclaringType, TValue>(
|
||||
Func<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return getter((TDeclaringType)target);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallNullSafePropertyGetterByReference<TDeclaringType, TValue>(
|
||||
ByRefFunc<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var unboxed = (TDeclaringType)target;
|
||||
return getter(ref unboxed);
|
||||
}
|
||||
|
||||
private static void CallPropertySetter<TDeclaringType, TValue>(
|
||||
Action<TDeclaringType, TValue> setter,
|
||||
object target,
|
||||
object value)
|
||||
{
|
||||
setter((TDeclaringType)target, (TValue)value);
|
||||
}
|
||||
|
||||
protected static PropertyHelper[] GetVisibleProperties(
|
||||
Type type,
|
||||
Func<PropertyInfo, PropertyHelper> createPropertyHelper,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> allPropertiesCache,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> visiblePropertiesCache)
|
||||
{
|
||||
PropertyHelper[] result;
|
||||
if (visiblePropertiesCache.TryGetValue(type, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// The simple and common case, this is normal POCO object - no need to allocate.
|
||||
var allPropertiesDefinedOnType = true;
|
||||
var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache);
|
||||
foreach (var propertyHelper in allProperties)
|
||||
{
|
||||
if (propertyHelper.Property.DeclaringType != type)
|
||||
{
|
||||
allPropertiesDefinedOnType = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPropertiesDefinedOnType)
|
||||
{
|
||||
result = allProperties;
|
||||
visiblePropertiesCache.TryAdd(type, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// There's some inherited properties here, so we need to check for hiding via 'new'.
|
||||
var filteredProperties = new List<PropertyHelper>(allProperties.Length);
|
||||
foreach (var propertyHelper in allProperties)
|
||||
{
|
||||
var declaringType = propertyHelper.Property.DeclaringType;
|
||||
if (declaringType == type)
|
||||
{
|
||||
filteredProperties.Add(propertyHelper);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this property was declared on a base type then look for the definition closest to the
|
||||
// the type to see if we should include it.
|
||||
var ignoreProperty = false;
|
||||
|
||||
// Walk up the hierarchy until we find the type that actally declares this
|
||||
// PropertyInfo.
|
||||
var currentTypeInfo = type.GetTypeInfo();
|
||||
var declaringTypeInfo = declaringType.GetTypeInfo();
|
||||
while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo)
|
||||
{
|
||||
// We've found a 'more proximal' public definition
|
||||
var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name);
|
||||
if (declaredProperty != null)
|
||||
{
|
||||
ignoreProperty = true;
|
||||
break;
|
||||
}
|
||||
|
||||
currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo();
|
||||
}
|
||||
|
||||
if (!ignoreProperty)
|
||||
{
|
||||
filteredProperties.Add(propertyHelper);
|
||||
}
|
||||
}
|
||||
|
||||
result = filteredProperties.ToArray();
|
||||
visiblePropertiesCache.TryAdd(type, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static PropertyHelper[] GetProperties(
|
||||
Type type,
|
||||
Func<PropertyInfo, PropertyHelper> createPropertyHelper,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> cache)
|
||||
{
|
||||
// Unwrap nullable types. This means Nullable<T>.Value and Nullable<T>.HasValue will not be
|
||||
// part of the sequence of properties returned by this method.
|
||||
type = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
PropertyHelper[] helpers;
|
||||
if (!cache.TryGetValue(type, out helpers))
|
||||
{
|
||||
// We avoid loading indexed properties using the Where statement.
|
||||
var properties = type.GetRuntimeProperties().Where(IsInterestingProperty);
|
||||
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
if (typeInfo.IsInterface)
|
||||
{
|
||||
// Reflection does not return information about inherited properties on the interface itself.
|
||||
properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany(
|
||||
interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty)));
|
||||
}
|
||||
|
||||
helpers = properties.Select(p => createPropertyHelper(p)).ToArray();
|
||||
cache.TryAdd(type, helpers);
|
||||
}
|
||||
|
||||
return helpers;
|
||||
}
|
||||
|
||||
// Indexed properties are not useful (or valid) for grabbing properties off an object.
|
||||
private static bool IsInterestingProperty(PropertyInfo property)
|
||||
{
|
||||
return property.GetIndexParameters().Length == 0 &&
|
||||
property.GetMethod != null &&
|
||||
property.GetMethod.IsPublic &&
|
||||
!property.GetMethod.IsStatic;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
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
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<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
|
||||
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
|
||||
mimetype set.
|
||||
|
||||
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
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
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
|
||||
: 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
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ArgumentCannotBeNullOrEmpy" xml:space="preserve">
|
||||
<value>Value cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
|
||||
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_InjectDirectivePropertyNameRequired" xml:space="preserve">
|
||||
<value>A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_KeywordMustBeFollowedByTypeName" xml:space="preserve">
|
||||
<value>The '{0}' keyword must be followed by a type name on the same line.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
|
||||
<value>Only one '{0}' statement is allowed in a file.</value>
|
||||
</data>
|
||||
<data name="MvcRazorParser_InvalidPropertyType" xml:space="preserve">
|
||||
<value>Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods to locate <c>_ViewStart.cshtml</c> and <c>_ViewImports.cshtml</c>
|
||||
/// </summary>
|
||||
public static class ViewHierarchyUtility
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// File name of <c>_ViewImports.cshtml</c> file
|
||||
/// </summary>
|
||||
public static readonly string ViewImportsFileName = "_ViewImports.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view start locations that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewStart</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential view start locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewStartLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, ViewStartFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the locations for <c>_ViewImports</c>s that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewImports</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential <c>_ViewImports</c> locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewImports.cshtml, /Views/_ViewImports.cshtml,
|
||||
/// /_ViewImports.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewImportsLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, ViewImportsFileName);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetHierarchicalPath(string relativePath, string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(2);
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(1);
|
||||
}
|
||||
|
||||
if (string.Equals(Path.GetFileName(relativePath), fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// If the specified path is for the file hierarchy being constructed, then the first file that applies
|
||||
// to it is in a parent directory.
|
||||
relativePath = Path.GetDirectoryName(relativePath);
|
||||
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(relativePath);
|
||||
builder.Replace('\\', '/');
|
||||
|
||||
if (builder.Length > 0 && builder[0] != '/')
|
||||
{
|
||||
builder.Insert(0, '/');
|
||||
}
|
||||
|
||||
var locations = new List<string>();
|
||||
for (var index = builder.Length - 1; index >= 0; index--)
|
||||
{
|
||||
if (builder[index] == '/')
|
||||
{
|
||||
builder.Length = index + 1;
|
||||
builder.Append(fileName);
|
||||
|
||||
locations.Add(builder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"description": "Design time hosting infrastructure for the ASP.NET MVC Razor view engine.",
|
||||
"version": "6.0.0-rc1-final",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/aspnet/mvc"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.FileProviders.Physical.VSRC1": "1.0.0-rc1-final",
|
||||
"Microsoft.AspNet.Razor.Runtime.VSRC1": "4.0.0-rc1-final",
|
||||
"Microsoft.Extensions.Caching.Memory.VSRC1": "1.0.0-rc1-final"
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc1-final"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"wwwroot",
|
||||
"node_modules",
|
||||
"bower_components"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче