Adding support for inheriting tag helpers from _ViewStart files
Fixes #1166
This commit is contained in:
parent
0ad959e236
commit
b2a01e7b45
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNet.FileSystems;
|
using Microsoft.AspNet.FileSystems;
|
||||||
using Microsoft.AspNet.Razor;
|
using Microsoft.AspNet.Razor;
|
||||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||||
|
|
@ -13,63 +12,57 @@ using Microsoft.AspNet.Razor.Parser;
|
||||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A utility type for supporting inheritance of chunks into a page from _ViewStart pages that apply to it.
|
/// A utility type for supporting inheritance of tag helpers and chunks into a page from applicable _ViewStart
|
||||||
|
/// pages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChunkInheritanceUtility
|
public class ChunkInheritanceUtility
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<Chunk> _defaultInheritedChunks;
|
private readonly Dictionary<string, CodeTree> _parsedCodeTrees;
|
||||||
|
private readonly MvcRazorHost _razorHost;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IEnumerable<Chunk> _defaultInheritedChunks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new instance of <see cref="ChunkInheritanceUtility"/>.
|
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
|
||||||
/// </summary>
|
|
||||||
/// <param name="codeTree">The <see cref="CodeTree"/> instance to add <see cref="Chunk"/>s to.</param>
|
|
||||||
/// <param name="defaultInheritedChunks">The list of <see cref="Chunk"/>s inherited by default.</param>
|
|
||||||
/// <param name="defaultModel">The model type used in the event no model is specified via the
|
|
||||||
/// <c>@model</c> keyword.</param>
|
|
||||||
public ChunkInheritanceUtility([NotNull] CodeTree codeTree,
|
|
||||||
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks,
|
|
||||||
[NotNull] string defaultModel)
|
|
||||||
{
|
|
||||||
CodeTree = codeTree;
|
|
||||||
_defaultInheritedChunks = defaultInheritedChunks;
|
|
||||||
ChunkMergers = GetMergerMappings(codeTree, defaultModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the CodeTree to add inherited <see cref="Chunk"/> instances to.
|
|
||||||
/// </summary>
|
|
||||||
public CodeTree CodeTree { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a dictionary mapping <see cref="Chunk"/> type to the <see cref="IChunkMerger"/> used to merge
|
|
||||||
/// chunks of that type.
|
|
||||||
/// </summary>
|
|
||||||
public IDictionary<Type, IChunkMerger> ChunkMergers { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of chunks that are to be inherited by a specified page.
|
|
||||||
/// Chunks are inherited from _ViewStarts that are applicable to the page.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse _ViewStart pages.</param>
|
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse _ViewStart pages.</param>
|
||||||
/// <param name="fileSystem">The filesystem that represents the application.</param>
|
/// <param name="fileSystem">The filesystem that represents the application.</param>
|
||||||
|
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
|
||||||
|
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
|
||||||
|
[NotNull] IFileSystem fileSystem,
|
||||||
|
[NotNull] IEnumerable<Chunk> defaultInheritedChunks)
|
||||||
|
{
|
||||||
|
_razorHost = razorHost;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_defaultInheritedChunks = defaultInheritedChunks;
|
||||||
|
_parsedCodeTrees = new Dictionary<string, CodeTree>(StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> containing parsed results of _ViewStart files
|
||||||
|
/// that are used for inheriting tag helpers and chunks to the page located at <paramref name="pagePath"/>.
|
||||||
|
/// </summary>
|
||||||
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
|
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
|
||||||
/// <returns>A list of chunks that are applicable to the given page.</returns>
|
/// <returns>A <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> from _ViewStart pages.</returns>
|
||||||
public List<Chunk> GetInheritedChunks([NotNull] MvcRazorHost razorHost,
|
public IReadOnlyList<Chunk> GetInheritedChunks([NotNull] string pagePath)
|
||||||
[NotNull] IFileSystem fileSystem,
|
|
||||||
[NotNull] string pagePath)
|
|
||||||
{
|
{
|
||||||
var inheritedChunks = new List<Chunk>();
|
var inheritedChunks = new List<Chunk>();
|
||||||
|
|
||||||
var templateEngine = new RazorTemplateEngine(razorHost);
|
var templateEngine = new RazorTemplateEngine(_razorHost);
|
||||||
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(fileSystem, pagePath))
|
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(_fileSystem, pagePath))
|
||||||
{
|
{
|
||||||
|
CodeTree codeTree;
|
||||||
IFileInfo fileInfo;
|
IFileInfo fileInfo;
|
||||||
if (fileSystem.TryGetFileInfo(viewStart, out fileInfo))
|
|
||||||
|
if (_parsedCodeTrees.TryGetValue(viewStart, out codeTree))
|
||||||
{
|
{
|
||||||
var parsedTree = ParseViewFile(templateEngine, fileInfo);
|
inheritedChunks.AddRange(codeTree.Chunks);
|
||||||
var chunksToAdd = parsedTree.Chunks
|
}
|
||||||
.Where(chunk => ChunkMergers.ContainsKey(chunk.GetType()));
|
else if (_fileSystem.TryGetFileInfo(viewStart, out fileInfo))
|
||||||
inheritedChunks.AddRange(chunksToAdd);
|
{
|
||||||
|
codeTree = ParseViewFile(templateEngine, fileInfo);
|
||||||
|
_parsedCodeTrees.Add(viewStart, codeTree);
|
||||||
|
inheritedChunks.AddRange(codeTree.Chunks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,19 +72,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Merges a list of chunks into the <see cref="CodeTree"/> instance.
|
/// Merges a list of chunks into the specified <paramref name="codeTree/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="codeTree">The <see cref="CodeTree"/> to mere</param>
|
||||||
/// <param name="inherited">The list of chunks to merge.</param>
|
/// <param name="inherited">The list of chunks to merge.</param>
|
||||||
public void MergeInheritedChunks(List<Chunk> inherited)
|
public void MergeInheritedChunks([NotNull] CodeTree codeTree,
|
||||||
|
[NotNull] IReadOnlyList<Chunk> inherited,
|
||||||
|
string defaultModel)
|
||||||
{
|
{
|
||||||
var current = CodeTree.Chunks;
|
var mergerMappings = GetMergerMappings(codeTree, defaultModel);
|
||||||
IChunkMerger merger;
|
IChunkMerger merger;
|
||||||
|
|
||||||
// We merge chunks into the codeTree in two passes. In the first pass, we traverse the CodeTree visiting
|
// We merge chunks into the codeTree in two passes. In the first pass, we traverse the CodeTree visiting
|
||||||
// a mapped IChunkMerger for types that are registered.
|
// a mapped IChunkMerger for types that are registered.
|
||||||
foreach (var chunk in current)
|
foreach (var chunk in codeTree.Chunks)
|
||||||
{
|
{
|
||||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out merger))
|
if (mergerMappings.TryGetValue(chunk.GetType(), out merger))
|
||||||
{
|
{
|
||||||
merger.VisitChunk(chunk);
|
merger.VisitChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
@ -102,11 +98,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
// rules.
|
// rules.
|
||||||
foreach (var chunk in inherited)
|
foreach (var chunk in inherited)
|
||||||
{
|
{
|
||||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out merger))
|
if (mergerMappings.TryGetValue(chunk.GetType(), out merger))
|
||||||
{
|
{
|
||||||
// TODO: When mapping chunks, we should remove mapping information since it would be incorrect
|
// TODO: When mapping chunks, we should remove mapping information since it would be incorrect
|
||||||
// to generate it in the page that inherits it. Tracked by #945
|
// to generate it in the page that inherits it. Tracked by #945
|
||||||
merger.Merge(CodeTree, chunk);
|
merger.Merge(codeTree, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,14 +119,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This needs to be cached (#1016)
|
// TODO: This needs to be cached (#1016)
|
||||||
private CodeTree ParseViewFile(RazorTemplateEngine engine,
|
private static CodeTree ParseViewFile(RazorTemplateEngine engine,
|
||||||
IFileInfo fileInfo)
|
IFileInfo fileInfo)
|
||||||
{
|
{
|
||||||
using (var stream = fileInfo.CreateReadStream())
|
using (var stream = fileInfo.CreateReadStream())
|
||||||
{
|
{
|
||||||
using (var streamReader = new StreamReader(stream))
|
using (var streamReader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
var parseResults = engine.ParseTemplate(streamReader);
|
var parseResults = engine.ParseTemplate(streamReader, fileInfo.PhysicalPath);
|
||||||
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
|
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
|
||||||
var language = engine.Host.CodeLanguage;
|
var language = engine.Host.CodeLanguage;
|
||||||
var codeGenerator = language.CreateCodeGenerator(className,
|
var codeGenerator = language.CreateCodeGenerator(className,
|
||||||
|
|
@ -138,6 +134,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
fileInfo.PhysicalPath,
|
fileInfo.PhysicalPath,
|
||||||
engine.Host);
|
engine.Host);
|
||||||
codeGenerator.Visit(parseResults);
|
codeGenerator.Visit(parseResults);
|
||||||
|
|
||||||
return codeGenerator.Context.CodeTreeBuilder.CodeTree;
|
return codeGenerator.Context.CodeTreeBuilder.CodeTree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||||
// This field holds the type name without the generic decoration (MyBaseType)
|
// This field holds the type name without the generic decoration (MyBaseType)
|
||||||
private readonly string _baseType;
|
private readonly string _baseType;
|
||||||
|
private ChunkInheritanceUtility _chunkInheritanceUtility;
|
||||||
|
|
||||||
#if NET45
|
#if NET45
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -154,6 +155,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
get { return "CreateModelExpression"; }
|
get { return "CreateModelExpression"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ChunkInheritanceUtility ChunkInheritanceUtility
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_chunkInheritanceUtility == null)
|
||||||
|
{
|
||||||
|
// This needs to be lazily evaluated to support DefaultInheritedChunks being virtual.
|
||||||
|
_chunkInheritanceUtility = new ChunkInheritanceUtility(this, _fileSystem, DefaultInheritedChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _chunkInheritanceUtility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||||
{
|
{
|
||||||
|
|
@ -163,6 +178,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return engine.GenerateCode(inputStream, className, DefaultNamespace, rootRelativePath);
|
return engine.GenerateCode(inputStream, className, DefaultNamespace, rootRelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override RazorParser DecorateRazorParser([NotNull] RazorParser razorParser, string sourceFileName)
|
||||||
|
{
|
||||||
|
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(sourceFileName);
|
||||||
|
return new MvcRazorParser(razorParser, inheritedChunks);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ParserBase DecorateCodeParser([NotNull] ParserBase incomingCodeParser)
|
public override ParserBase DecorateCodeParser([NotNull] ParserBase incomingCodeParser)
|
||||||
{
|
{
|
||||||
|
|
@ -173,7 +195,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
|
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
|
||||||
[NotNull] CodeBuilderContext context)
|
[NotNull] CodeBuilderContext context)
|
||||||
{
|
{
|
||||||
UpdateCodeBuilder(context);
|
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(context.SourceFile);
|
||||||
|
|
||||||
|
ChunkInheritanceUtility.MergeInheritedChunks(context.CodeTreeBuilder.CodeTree,
|
||||||
|
inheritedChunks,
|
||||||
|
DefaultModel);
|
||||||
|
|
||||||
return new MvcCSharpCodeBuilder(context,
|
return new MvcCSharpCodeBuilder(context,
|
||||||
DefaultModel,
|
DefaultModel,
|
||||||
|
|
@ -184,14 +210,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
CreateModelExpressionMethodName = CreateModelExpressionMethod
|
CreateModelExpressionMethodName = CreateModelExpressionMethod
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCodeBuilder(CodeGeneratorContext context)
|
|
||||||
{
|
|
||||||
var chunkUtility = new ChunkInheritanceUtility(context.CodeTreeBuilder.CodeTree,
|
|
||||||
DefaultInheritedChunks,
|
|
||||||
DefaultModel);
|
|
||||||
var inheritedChunks = chunkUtility.GetInheritedChunks(this, _fileSystem, context.SourceFile);
|
|
||||||
chunkUtility.MergeInheritedChunks(inheritedChunks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||||
|
using Microsoft.AspNet.Razor.Parser;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.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 _viewstart files.
|
||||||
|
/// </summary>
|
||||||
|
public class MvcRazorParser : RazorParser
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<Chunk> _viewStartChunks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="MvcRazorParser"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param>
|
||||||
|
/// <param name="viewStartChunks">The <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/>s that are inherited
|
||||||
|
/// by parsed pages from _ViewStart files.</param>
|
||||||
|
public MvcRazorParser(RazorParser parser, IReadOnlyList<Chunk> viewStartChunks)
|
||||||
|
: base(parser)
|
||||||
|
{
|
||||||
|
_viewStartChunks = viewStartChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot)
|
||||||
|
{
|
||||||
|
var descriptors = base.GetTagHelperDescriptors(documentRoot);
|
||||||
|
|
||||||
|
// TODO: https://github.com/aspnet/Razor/issues/112 Needs to support RemvoeHelperChunks too.
|
||||||
|
|
||||||
|
// Grab all the @addTagHelper chunks from view starts
|
||||||
|
var viewStartDescriptors = _viewStartChunks.OfType<AddTagHelperChunk>()
|
||||||
|
.Select(c => c.LookupText)
|
||||||
|
.SelectMany(TagHelperDescriptorResolver.Resolve);
|
||||||
|
|
||||||
|
return descriptors.Concat(viewStartDescriptors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,5 +51,26 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||||
Assert.Equal(expectedContent, responseContent);
|
Assert.Equal(expectedContent, responseContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("NestedViewStartTagHelper")]
|
||||||
|
[InlineData("ViewWithLayoutAndNestedTagHelper")]
|
||||||
|
public async Task TagHelpersAreInheritedFromViewStartPages(string action)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = string.Join(Environment.NewLine,
|
||||||
|
"<root>root-content</root>",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"<nested>nested-content</nested>");
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetStringAsync("http://localhost/Home/" + action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, result.Trim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>Hello, you've reached the about page.</p>
|
<p>Hello, you've reached the about page.</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>Hello, you've reached the help page. If you're having troubles try visiting our <a href="/?approved=true">My Approved Home Page</a></p>
|
<p>Hello, you've reached the help page. If you're having troubles try visiting our <a href="/?approved=true">My Approved Home Page</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>This website has <strong style="font-size: 1.25em;
|
<p>This website has <strong style="font-size: 1.25em;
|
||||||
text-decoration: underline;">not</strong> been approved yet. Visit <strong><a target="_blank" href="http://www.contoso.com">www.contoso.com</a></strong> for <strong>more</strong> information.</p>
|
text-decoration: underline;">not</strong> been approved yet. Visit <strong><a target="_blank" href="http://www.contoso.com">www.contoso.com</a></strong> for <strong>more</strong> information.</p>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Generator.Compiler;
|
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
|
|
@ -29,24 +26,30 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
|
|
||||||
");
|
");
|
||||||
var host = new MvcRazorHost(fileSystem);
|
var host = new MvcRazorHost(fileSystem);
|
||||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var chunks = utility.GetInheritedChunks(host,
|
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||||
fileSystem,
|
|
||||||
@"x:\myapproot\views\home\Index.cshtml");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(3, chunks.Count);
|
Assert.Equal(8, chunks.Count);
|
||||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[0]);
|
Assert.IsType<LiteralChunk>(chunks[0]);
|
||||||
|
|
||||||
|
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]);
|
||||||
Assert.Equal("MyNamespace", usingChunk.Namespace);
|
Assert.Equal("MyNamespace", usingChunk.Namespace);
|
||||||
|
|
||||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]);
|
Assert.IsType<LiteralChunk>(chunks[2]);
|
||||||
|
Assert.IsType<LiteralChunk>(chunks[3]);
|
||||||
|
|
||||||
|
var injectChunk = Assert.IsType<InjectChunk>(chunks[4]);
|
||||||
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
|
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
|
||||||
Assert.Equal("Helper", injectChunk.MemberName);
|
Assert.Equal("Helper", injectChunk.MemberName);
|
||||||
|
|
||||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[2]);
|
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[5]);
|
||||||
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
|
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
|
||||||
|
|
||||||
|
Assert.IsType<StatementChunk>(chunks[6]);
|
||||||
|
Assert.IsType<LiteralChunk>(chunks[7]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -58,12 +61,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
fileSystem.AddFile(@"x:\myapproot\views\_Layout.cshtml", string.Empty);
|
fileSystem.AddFile(@"x:\myapproot\views\_Layout.cshtml", string.Empty);
|
||||||
fileSystem.AddFile(@"x:\myapproot\views\home\_not-viewstart.cshtml", string.Empty);
|
fileSystem.AddFile(@"x:\myapproot\views\home\_not-viewstart.cshtml", string.Empty);
|
||||||
var host = new MvcRazorHost(fileSystem);
|
var host = new MvcRazorHost(fileSystem);
|
||||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var chunks = utility.GetInheritedChunks(host,
|
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||||
fileSystem,
|
|
||||||
@"x:\myapproot\views\home\Index.cshtml");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(chunks);
|
Assert.Empty(chunks);
|
||||||
|
|
@ -75,73 +76,26 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileSystem = new TestFileSystem();
|
var fileSystem = new TestFileSystem();
|
||||||
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
|
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
|
||||||
@"@inject DifferentHelper<TModel> Html
|
"@inject DifferentHelper<TModel> Html");
|
||||||
@using AppNamespace.Models
|
|
||||||
@{
|
|
||||||
Layout = ""test.cshtml"";
|
|
||||||
}
|
|
||||||
|
|
||||||
");
|
|
||||||
var host = new MvcRazorHost(fileSystem);
|
var host = new MvcRazorHost(fileSystem);
|
||||||
var defaultChunks = new Chunk[]
|
var defaultChunks = new Chunk[]
|
||||||
{
|
{
|
||||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||||
};
|
};
|
||||||
var utility = new ChunkInheritanceUtility(new CodeTree(), defaultChunks, "dynamic");
|
var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var chunks = utility.GetInheritedChunks(host,
|
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||||
fileSystem,
|
|
||||||
@"x:\myapproot\views\home\Index.cshtml");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(4, chunks.Count);
|
Assert.Equal(4, chunks.Count);
|
||||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[0]);
|
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]);
|
||||||
Assert.Equal("DifferentHelper<TModel>", injectChunk.TypeName);
|
Assert.Equal("DifferentHelper<TModel>", injectChunk.TypeName);
|
||||||
Assert.Equal("Html", injectChunk.MemberName);
|
Assert.Equal("Html", injectChunk.MemberName);
|
||||||
|
|
||||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]);
|
Assert.Same(defaultChunks[0], chunks[2]);
|
||||||
Assert.Equal("AppNamespace.Models", usingChunk.Namespace);
|
Assert.Same(defaultChunks[1], chunks[3]);
|
||||||
|
|
||||||
injectChunk = Assert.IsType<InjectChunk>(chunks[2]);
|
|
||||||
Assert.Equal("MyTestHtmlHelper", injectChunk.TypeName);
|
|
||||||
Assert.Equal("Html", injectChunk.MemberName);
|
|
||||||
|
|
||||||
usingChunk = Assert.IsType<UsingChunk>(chunks[3]);
|
|
||||||
Assert.Equal("AppNamespace.Model", usingChunk.Namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeChunks_VisitsChunksPriorToMerging()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var codeTree = new CodeTree();
|
|
||||||
codeTree.Chunks.Add(new LiteralChunk());
|
|
||||||
codeTree.Chunks.Add(new ExpressionBlockChunk());
|
|
||||||
codeTree.Chunks.Add(new ExpressionBlockChunk());
|
|
||||||
|
|
||||||
var merger = new Mock<IChunkMerger>();
|
|
||||||
var mockSequence = new MockSequence();
|
|
||||||
merger.InSequence(mockSequence)
|
|
||||||
.Setup(m => m.VisitChunk(It.IsAny<LiteralChunk>()))
|
|
||||||
.Verifiable();
|
|
||||||
merger.InSequence(mockSequence)
|
|
||||||
.Setup(m => m.Merge(codeTree, It.IsAny<LiteralChunk>()))
|
|
||||||
.Verifiable();
|
|
||||||
var inheritedChunks = new List<Chunk>
|
|
||||||
{
|
|
||||||
new CodeAttributeChunk(),
|
|
||||||
new LiteralChunk()
|
|
||||||
};
|
|
||||||
var utility = new ChunkInheritanceUtility(codeTree, inheritedChunks, "dynamic");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
utility.ChunkMergers[typeof(LiteralChunk)] = merger.Object;
|
|
||||||
utility.MergeInheritedChunks(inheritedChunks);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
merger.Verify();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
var fileInfo = new Mock<IFileInfo>();
|
var fileInfo = new Mock<IFileInfo>();
|
||||||
fileInfo.Setup(f => f.CreateReadStream())
|
fileInfo.Setup(f => f.CreateReadStream())
|
||||||
.Returns(new MemoryStream(Encoding.UTF8.GetBytes(contents)));
|
.Returns(() => new MemoryStream(Encoding.UTF8.GetBytes(contents)));
|
||||||
fileInfo.SetupGet(f => f.PhysicalPath)
|
fileInfo.SetupGet(f => f.PhysicalPath)
|
||||||
.Returns(path);
|
.Returns(path);
|
||||||
fileInfo.SetupGet(f => f.Name)
|
fileInfo.SetupGet(f => f.Name)
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,15 @@ namespace TagHelpersWebSite.Controllers
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ViewResult NestedViewStartTagHelper()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewWithLayoutAndNestedTagHelper()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Runtime.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace TagHelpersWebSite.TagHelpers
|
||||||
|
{
|
||||||
|
[TagName("nested")]
|
||||||
|
[ContentBehavior(ContentBehavior.Modify)]
|
||||||
|
public class NestedViewStartTagHelper : TagHelper
|
||||||
|
{
|
||||||
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
|
{
|
||||||
|
output.Content = "nested-content";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Runtime.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace TagHelpersWebSite.TagHelpers
|
||||||
|
{
|
||||||
|
[TagName("root")]
|
||||||
|
[ContentBehavior(ContentBehavior.Replace)]
|
||||||
|
public class RootViewStartTagHelper : TagHelper
|
||||||
|
{
|
||||||
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
|
{
|
||||||
|
output.Content = "root-content";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
}
|
||||||
|
<root />
|
||||||
|
|
||||||
|
|
||||||
|
<nested>some-content</nested>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@addtaghelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
|
||||||
|
}
|
||||||
|
@addtaghelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||||
|
<nested>some-content</nested>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
<root />
|
||||||
|
@RenderBody()
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
@{
|
@{
|
||||||
Layout = "/Views/Shared/_Layout.cshtml";
|
Layout = "/Views/Shared/_Layout.cshtml";
|
||||||
}
|
}
|
||||||
|
@addtaghelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||||
Loading…
Reference in New Issue