diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln
index f8139aef79..d72a103394 100644
--- a/Mvc.NoFun.sln
+++ b/Mvc.NoFun.sln
@@ -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
diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json
index 392bb1a748..322a420f71 100644
--- a/NuGetPackageVerifier.json
+++ b/NuGetPackageVerifier.json
@@ -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": { }
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkHelper.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkHelper.cs
new file mode 100644
index 0000000000..8f39c3596a
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkHelper.cs
@@ -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
+{
+ ///
+ /// Contains helper methods for dealing with Chunks
+ ///
+ public static class ChunkHelper
+ {
+ ///
+ /// Token that is replaced by the model name in @inherits and @inject
+ /// chunks as part of .
+ ///
+ public static readonly string TModelToken = "TModel";
+ private static readonly string TModelReplaceToken = $"<{TModelToken}>";
+
+ ///
+ /// Returns the used to determine the model name for the page generated
+ /// using the specified
+ ///
+ /// The to scan for s in.
+ /// The last in the if found, null otherwise.
+ ///
+ 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()
+ .LastOrDefault();
+ }
+
+ ///
+ /// Returns the type name of the Model specified via a in the
+ /// if specified or the default model type.
+ ///
+ /// The to scan for s in.
+ /// The name of the default model.
+ /// The model type name for the generated page.
+ 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;
+ }
+
+ ///
+ /// Returns a string with the <TModel> token replaced with the value specified in
+ /// .
+ ///
+ /// The string to replace the token in.
+ /// The model name to replace with.
+ /// A string with the token replaced.
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkInheritanceUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkInheritanceUtility.cs
new file mode 100644
index 0000000000..4b77170f9c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkInheritanceUtility.cs
@@ -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
+{
+ ///
+ /// A utility type for supporting inheritance of directives into a page from applicable _ViewImports pages.
+ ///
+ public class ChunkInheritanceUtility
+ {
+ private readonly MvcRazorHost _razorHost;
+ private readonly IReadOnlyList _defaultInheritedChunks;
+ private readonly IChunkTreeCache _chunkTreeCache;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The used to parse _ViewImports pages.
+ /// that caches instances.
+ ///
+ /// Sequence of s inherited by default.
+ public ChunkInheritanceUtility(
+ MvcRazorHost razorHost,
+ IChunkTreeCache chunkTreeCache,
+ IReadOnlyList 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;
+ }
+
+ ///
+ /// Gets an ordered of parsed s and
+ /// file paths for each _ViewImports that is applicable to the page located at
+ /// . The list is ordered so that the 's
+ /// for the _ViewImports closest to the
+ /// in the file system appears first.
+ ///
+ /// The path of the page to locate inherited chunks for.
+ /// A of parsed _ViewImports
+ /// s and their file paths.
+ ///
+ /// The resulting 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 ]
+ ///
+ public virtual IReadOnlyList GetInheritedChunkTreeResults(string pagePath)
+ {
+ if (pagePath == null)
+ {
+ throw new ArgumentNullException(nameof(pagePath));
+ }
+
+ var inheritedChunkTreeResults = new List();
+ 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;
+ }
+
+ ///
+ /// Merges inherited by default and instances produced by parsing
+ /// _ViewImports files into the specified .
+ ///
+ /// The to merge in to.
+ /// inherited from _ViewImports
+ /// files.
+ /// The default model name.
+ public void MergeInheritedChunkTrees(
+ ChunkTree chunkTree,
+ IReadOnlyList 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;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkTreeResult.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkTreeResult.cs
new file mode 100644
index 0000000000..1f96a130c5
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/ChunkTreeResult.cs
@@ -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
+{
+ ///
+ /// Contains information.
+ ///
+ public class ChunkTreeResult
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The generated from the file at the
+ /// given .
+ /// The path to the file that generated the given .
+ 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;
+ }
+
+ ///
+ /// The generated from the file at .
+ ///
+ public ChunkTree ChunkTree { get; }
+
+ ///
+ /// The path to the file that generated the .
+ ///
+ public string FilePath { get; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/DefaultChunkTreeCache.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/DefaultChunkTreeCache.cs
new file mode 100644
index 0000000000..8a61b354d1
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/DefaultChunkTreeCache.cs
@@ -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
+{
+ ///
+ /// Default implementation of .
+ ///
+ 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;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The application's .
+ public DefaultChunkTreeCache(IFileProvider fileProvider)
+ : this(fileProvider, MemoryCacheOptions)
+ {
+ }
+
+ // Internal for unit testing
+ internal DefaultChunkTreeCache(
+ IFileProvider fileProvider,
+ MemoryCacheOptions options)
+ {
+ _fileProvider = fileProvider;
+ _chunkTreeCache = new MemoryCache(options);
+ }
+
+ ///
+ public ChunkTree GetOrAdd(
+ string pagePath,
+ Func 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkMerger.cs
new file mode 100644
index 0000000000..fbef21fcf6
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkMerger.cs
@@ -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
+{
+ ///
+ /// Defines the contract for merging instances from _ViewStart files.
+ ///
+ public interface IChunkMerger
+ {
+ ///
+ /// Visits a from the to merge into.
+ ///
+ /// A from the tree.
+ void VisitChunk(Chunk chunk);
+
+ ///
+ /// Merges an inherited into the .
+ ///
+ /// The to merge into.
+ /// The s to merge.
+ void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList inheritedChunks);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkTreeCache.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkTreeCache.cs
new file mode 100644
index 0000000000..58d854acee
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/IChunkTreeCache.cs
@@ -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
+{
+ ///
+ /// A cache for parsed s.
+ ///
+ public interface IChunkTreeCache
+ {
+ ///
+ /// Get an existing , or create and add a new one if it is
+ /// not available in the cache or is expired.
+ ///
+ /// The application relative path of the Razor page.
+ /// A delegate that creates a new .
+ /// The if a file exists at ,
+ /// null otherwise.
+ /// The resulting does not contain inherited chunks from _ViewStart or
+ /// default inherited chunks.
+ ChunkTree GetOrAdd(string pagePath, Func getChunkTree);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/InjectChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/InjectChunkMerger.cs
new file mode 100644
index 0000000000..99f8354532
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/InjectChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class InjectChunkMerger : IChunkMerger
+ {
+ private readonly HashSet _addedMemberNames = new HashSet(StringComparer.Ordinal);
+ private string _modelType;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The model type to be used to replace <TModel> tokens.
+ public InjectChunkMerger(string modelType)
+ {
+ if (modelType == null)
+ {
+ throw new ArgumentNullException(nameof(modelType));
+ }
+
+ _modelType = "<" + modelType + ">";
+ }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/SetBaseTypeChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/SetBaseTypeChunkMerger.cs
new file mode 100644
index 0000000000..05ab57f9ac
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/SetBaseTypeChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class SetBaseTypeChunkMerger : IChunkMerger
+ {
+ private readonly string _modelType;
+ private bool _isBaseTypeSet;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The type name of the model used by default.
+ public SetBaseTypeChunkMerger(string modelType)
+ {
+ _modelType = "<" + modelType + ">";
+ }
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/UsingChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/UsingChunkMerger.cs
new file mode 100644
index 0000000000..896aae6f7b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Directives/UsingChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class UsingChunkMerger : IChunkMerger
+ {
+ private readonly HashSet _currentUsings = new HashSet(StringComparer.Ordinal);
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ public void MergeInheritedChunks(ChunkTree chunkTree, IReadOnlyList inheritedChunks)
+ {
+ if (chunkTree == null)
+ {
+ throw new ArgumentNullException(nameof(chunkTree));
+ }
+
+ if (inheritedChunks == null)
+ {
+ throw new ArgumentNullException(nameof(inheritedChunks));
+ }
+
+ var namespaceChunks = inheritedChunks.OfType();
+ foreach (var namespaceChunk in namespaceChunks)
+ {
+ if (_currentUsings.Add(namespaceChunk.Namespace))
+ {
+ chunkTree.Chunks.Add(namespaceChunk);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/GeneratedTagHelperAttributeContext.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/GeneratedTagHelperAttributeContext.cs
new file mode 100644
index 0000000000..a0cfa398b8
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/GeneratedTagHelperAttributeContext.cs
@@ -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
+{
+ ///
+ /// Contains information for the attribute code
+ /// generation process.
+ ///
+ public class GeneratedTagHelperAttributeContext
+ {
+ ///
+ /// Name of the model expression type.
+ ///
+ public string ModelExpressionTypeName { get; set; }
+
+ ///
+ /// Name the method to create ModelExpressions.
+ ///
+ public string CreateModelExpressionMethodName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/IMvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/IMvcRazorHost.cs
new file mode 100644
index 0000000000..ad2b7ef855
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/IMvcRazorHost.cs
@@ -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
+{
+ ///
+ /// Specifies the contracts for a Razor host that parses Razor files and generates C# code.
+ ///
+ public interface IMvcRazorHost
+ {
+ ///
+ /// Parses and generates the contents of a Razor file represented by .
+ ///
+ /// 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.
+ /// A that represents the Razor contents.
+ /// A instance that represents the results of code generation.
+ ///
+ GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
+
+ ///
+ /// Represent the prefix off the main entry class in the view.
+ ///
+ string MainClassNamePrefix { get; }
+
+ ///
+ /// Represent the namespace the main entry class in the view.
+ ///
+ string DefaultNamespace { get; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunk.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunk.cs
new file mode 100644
index 0000000000..fccb761bff
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunk.cs
@@ -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
+ {
+ ///
+ /// Represents the chunk for an @inject statement.
+ ///
+ /// The type name of the property to be injected
+ /// The member name of the property to be injected.
+ public InjectChunk(
+ string typeName,
+ string propertyName)
+ {
+ TypeName = typeName;
+ MemberName = propertyName;
+ }
+
+ ///
+ /// Gets or sets the type name of the property to be injected.
+ ///
+ public string TypeName { get; set; }
+
+ ///
+ /// Gets or sets the name of the property to be injected.
+ ///
+ public string MemberName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunkVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunkVisitor.cs
new file mode 100644
index 0000000000..bbd37b5672
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectChunkVisitor.cs
@@ -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 InjectChunks { get; } = new List();
+
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectParameterGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectParameterGenerator.cs
new file mode 100644
index 0000000000..a2ed9c0686
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/InjectParameterGenerator.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/DesignTimeRazorPathNormalizer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/DesignTimeRazorPathNormalizer.cs
new file mode 100644
index 0000000000..8841859d59
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/DesignTimeRazorPathNormalizer.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/RazorPathNormalizer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/RazorPathNormalizer.cs
new file mode 100644
index 0000000000..21cfc2b3cf
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Internal/RazorPathNormalizer.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Microsoft.AspNet.Mvc.Razor.Host.VSRC1.xproj b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Microsoft.AspNet.Mvc.Razor.Host.VSRC1.xproj
new file mode 100644
index 0000000000..e3e003061e
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Microsoft.AspNet.Mvc.Razor.Host.VSRC1.xproj
@@ -0,0 +1,17 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 85c54a84-3e60-40e1-be39-c2f514dd922e
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunk.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunk.cs
new file mode 100644
index 0000000000..1c86d091f9
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunk.cs
@@ -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
+{
+ ///
+ /// for an @model directive.
+ ///
+ public class ModelChunk : Chunk
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The type of the view's model.
+ public ModelChunk(string modelType)
+ {
+ ModelType = modelType;
+ }
+
+ ///
+ /// Gets the type of the view's model.
+ ///
+ public string ModelType { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunkGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunkGenerator.cs
new file mode 100644
index 0000000000..55e55920f1
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/ModelChunkGenerator.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpChunkVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpChunkVisitor.cs
new file mode 100644
index 0000000000..e90bbc70bb
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpChunkVisitor.cs
@@ -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
+ {
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeGenerator.cs
new file mode 100644
index 0000000000..78b6fba6dd
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeGenerator.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeVistor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeVistor.cs
new file mode 100644
index 0000000000..77169dc992
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpCodeVistor.cs
@@ -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)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpDesignTimeCodeVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpDesignTimeCodeVisitor.cs
new file mode 100644
index 0000000000..e2db2dfc54
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcCSharpDesignTimeCodeVisitor.cs
@@ -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(");");
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorCodeParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorCodeParser.cs
new file mode 100644
index 0000000000..252ea60ad7
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorCodeParser.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorHost.cs
new file mode 100644
index 0000000000..e4b4af6e2b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorHost.cs
@@ -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", 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
+ 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.
+ 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);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of with the specified .
+ ///
+ /// The path to the application base.
+ // 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))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of using the specified .
+ ///
+ /// An rooted at the application base path.
+ public MvcRazorHost(IChunkTreeCache chunkTreeCache)
+ : this(chunkTreeCache, new RazorPathNormalizer())
+ {
+ }
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Gets the model type used by default when no model is specified.
+ ///
+ /// This value is used as the generic type argument for the base type
+ public virtual string DefaultModel
+ {
+ get { return "dynamic"; }
+ }
+
+ ///
+ public string MainClassNamePrefix
+ {
+ get { return "ASPV_"; }
+ }
+
+ ///
+ /// Gets the list of chunks that are injected by default by this host.
+ ///
+ public virtual IReadOnlyList DefaultInheritedChunks
+ {
+ get { return _defaultInheritedChunks; }
+ }
+
+ ///
+ /// Gets or sets the name attribute that is used to decorate properties that are injected and need to be
+ /// activated.
+ ///
+ public virtual string InjectAttribute
+ {
+ get { return "Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute"; }
+ }
+
+ ///
+ /// Gets the type name used to represent model expression properties.
+ ///
+ public virtual string ModelExpressionType
+ {
+ get { return "Microsoft.AspNet.Mvc.Rendering.ModelExpression"; }
+ }
+
+ ///
+ /// Gets the method name used to create model expressions.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Locates and parses _ViewImports.cshtml files applying to the given to
+ /// create s.
+ ///
+ /// The path to a Razor file to locate _ViewImports.cshtml for.
+ /// Inherited s.
+ public IReadOnlyList 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
+ {
+ if (incomingCodeParser == null)
+ {
+ throw new ArgumentNullException(nameof(incomingCodeParser));
+ }
+
+ return new MvcRazorCodeParser();
+ }
+
+ ///
+ 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 GetInheritedChunkTrees(string sourceFileName)
+ {
+ var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName)
+ .Select(result => result.ChunkTree)
+ .ToList();
+
+ return inheritedChunkTrees;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorParser.cs
new file mode 100644
index 0000000000..0e8ac8f865
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcRazorParser.cs
@@ -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
+{
+ ///
+ /// A subtype of that uses to support inheritance of tag
+ /// helpers from _ViewImports files.
+ ///
+ public class MvcRazorParser : RazorParser
+ {
+ private readonly IEnumerable _viewImportsDirectiveDescriptors;
+ private readonly string _modelExpressionTypeName;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The to copy properties from.
+ /// The s that are inherited
+ /// from parsed pages from _ViewImports files.
+ /// The inherited by
+ /// default by all Razor pages in the application.
+ public MvcRazorParser(
+ RazorParser parser,
+ IReadOnlyList inheritedChunkTrees,
+ IReadOnlyList 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;
+ }
+
+ ///
+ protected override IEnumerable 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 GetTagHelperDirectiveDescriptors(
+ IReadOnlyList inheritedChunkTrees,
+ IReadOnlyList defaultInheritedChunks)
+ {
+ var descriptors = new List();
+
+ 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 _viewImportsDirectiveDescriptors;
+
+ public ViewImportsTagHelperDirectiveSpanVisitor(
+ ITagHelperDescriptorResolver descriptorResolver,
+ IEnumerable viewImportsDirectiveDescriptors,
+ ErrorSink errorSink)
+ : base(descriptorResolver, errorSink)
+ {
+ _viewImportsDirectiveDescriptors = viewImportsDirectiveDescriptors;
+ }
+
+ protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext(
+ IEnumerable descriptors,
+ ErrorSink errorSink)
+ {
+ var directivesToImport = MergeDirectiveDescriptors(descriptors, _viewImportsDirectiveDescriptors);
+
+ return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink);
+ }
+
+ private static IEnumerable MergeDirectiveDescriptors(
+ IEnumerable descriptors,
+ IEnumerable inheritedDescriptors)
+ {
+ var mergedDescriptors = new List();
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcTagHelperAttributeValueCodeRenderer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcTagHelperAttributeValueCodeRenderer.cs
new file mode 100644
index 0000000000..aa467213ef
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/MvcTagHelperAttributeValueCodeRenderer.cs
@@ -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
+{
+ ///
+ public class MvcTagHelperAttributeValueCodeRenderer : TagHelperAttributeValueCodeRenderer
+ {
+ private const string ModelLambdaVariableName = "__model";
+
+ private readonly GeneratedTagHelperAttributeContext _context;
+
+ ///
+ /// Instantiates a new instance of .
+ ///
+ /// Contains code generation information for rendering attribute values.
+ public MvcTagHelperAttributeValueCodeRenderer(GeneratedTagHelperAttributeContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ _context = context;
+ }
+
+ ///
+ /// If the attribute being rendered is of the type
+ /// , then a model expression will be
+ /// created by calling into .
+ ///
+ public override void RenderAttributeValue(
+ TagHelperAttributeDescriptor attributeDescriptor,
+ CSharpCodeWriter writer,
+ CodeGeneratorContext codeGeneratorContext,
+ Action 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..1bbf723e7d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/AssemblyInfo.cs
@@ -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")]
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..9546523379
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/Properties/Resources.Designer.cs
@@ -0,0 +1,126 @@
+//
+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);
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string ArgumentCannotBeNullOrEmpy
+ {
+ get { return GetString("ArgumentCannotBeNullOrEmpy"); }
+ }
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string FormatArgumentCannotBeNullOrEmpy()
+ {
+ return GetString("ArgumentCannotBeNullOrEmpy");
+ }
+
+ ///
+ /// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
+ ///
+ internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword
+ {
+ get { return GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"); }
+ }
+
+ ///
+ /// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
+ ///
+ internal static string FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0);
+ }
+
+ ///
+ /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+ ///
+ internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired
+ {
+ get { return GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"); }
+ }
+
+ ///
+ /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+ ///
+ internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0);
+ }
+
+ ///
+ /// The '{0}' keyword must be followed by a type name on the same line.
+ ///
+ internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName
+ {
+ get { return GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"); }
+ }
+
+ ///
+ /// The '{0}' keyword must be followed by a type name on the same line.
+ ///
+ internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0);
+ }
+
+ ///
+ /// Only one '{0}' statement is allowed in a file.
+ ///
+ internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed
+ {
+ get { return GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"); }
+ }
+
+ ///
+ /// Only one '{0}' statement is allowed in a file.
+ ///
+ internal static string FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
+ }
+
+ ///
+ /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
+ ///
+ internal static string MvcRazorParser_InvalidPropertyType
+ {
+ get { return GetString("MvcRazorParser_InvalidPropertyType"); }
+ }
+
+ ///
+ /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/PropertyHelper.cs b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/PropertyHelper.cs
new file mode 100644
index 0000000000..3330e4144a
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host.VSRC1/PropertyHelper.cs
@@ -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(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 PropertiesCache =
+ new ConcurrentDictionary();
+
+ private static readonly ConcurrentDictionary VisiblePropertiesCache =
+ new ConcurrentDictionary();
+
+ private Action