зеркало из https://github.com/aspnet/Mvc.git
Adding support for inheriting chunks from _ViewStarts as part of host
parsing. Fixes #881
This commit is contained in:
Родитель
7a1242f162
Коммит
a490abc6e8
|
@ -0,0 +1,83 @@
|
|||
// 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;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Razor.Host;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helper methods for dealing with Chunks
|
||||
/// </summary>
|
||||
public static class ChunkHelper
|
||||
{
|
||||
private const string TModelToken = "<TModel>";
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to cast the passed in <see cref="Chunk"/> to type <typeparamref name="TChunk"/> and throws if the
|
||||
/// cast fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="TChunk">The type to cast to.</typeparam>
|
||||
/// <param name="chunk">The chunk to cast.</param>
|
||||
/// <returns>The <paramref name="Chunk"/> cast to <typeparamref name="TChunk"/>.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="chunk"/> is not an instance of
|
||||
/// <typeparamref name="TChunk"/>.</exception>
|
||||
public static TChunk EnsureChunk<TChunk>([NotNull] Chunk chunk)
|
||||
where TChunk : Chunk
|
||||
{
|
||||
var chunkOfT = chunk as TChunk;
|
||||
if (chunkOfT == null)
|
||||
{
|
||||
var message = Resources.FormatArgumentMustBeOfType(typeof(TChunk).FullName);
|
||||
throw new ArgumentException(message, "chunk");
|
||||
}
|
||||
|
||||
return chunkOfT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ModelChunk"/> used to determine the model name for the page generated
|
||||
/// using the specified <paramref name="codeTree"/>
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> to scan for <see cref="ModelChunk"/>s in.</param>
|
||||
/// <returns>The last <see cref="ModelChunk"/> in the <see cref="CodeTree"/> if found, null otherwise.
|
||||
/// </returns>
|
||||
public static ModelChunk GetModelChunk([NotNull] CodeTree codeTree)
|
||||
{
|
||||
// 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 codeTree.Chunks
|
||||
.OfType<ModelChunk>()
|
||||
.LastOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type name of the Model specified via a <see cref="ModelChunk"/> in the
|
||||
/// <paramref name="codeTree"/> if specified or the default model type.
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> to scan for <see cref="ModelChunk"/>s in.</param>
|
||||
/// <param name="defaultModelName">The <see cref="Type"/> name of the default model.</param>
|
||||
/// <returns>The model type name for the generated page.</returns>
|
||||
public static string GetModelTypeName([NotNull] CodeTree codeTree,
|
||||
[NotNull] string defaultModelName)
|
||||
{
|
||||
var modelChunk = GetModelChunk(codeTree);
|
||||
return modelChunk != null ? modelChunk.ModelType : defaultModelName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with the <TModel> token replaced with the value specified in
|
||||
/// <paramref name="modelName"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to replace the token in.</param>
|
||||
/// <param name="modelName">The model name to replace with.</param>
|
||||
/// <returns>A string with the token replaced.</returns>
|
||||
public static string ReplaceTModel([NotNull] string value,
|
||||
[NotNull] string modelName)
|
||||
{
|
||||
return value.Replace(TModelToken, modelName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility type for supporting inheritance of chunks into a page from _ViewStart pages that apply to it.
|
||||
/// </summary>
|
||||
public class ChunkInheritanceUtility
|
||||
{
|
||||
private readonly IReadOnlyList<Chunk> _defaultInheritedChunks;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates 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>
|
||||
/// <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="appRoot">The root of the application.</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>
|
||||
public List<Chunk> GetInheritedChunks([NotNull] MvcRazorHost razorHost,
|
||||
[NotNull] IFileSystem fileSystem,
|
||||
[NotNull] string appRoot,
|
||||
[NotNull] string pagePath)
|
||||
{
|
||||
var inheritedChunks = new List<Chunk>();
|
||||
|
||||
var templateEngine = new RazorTemplateEngine(razorHost);
|
||||
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(appRoot, pagePath))
|
||||
{
|
||||
IFileInfo fileInfo;
|
||||
if (fileSystem.TryGetFileInfo(viewStart, out fileInfo))
|
||||
{
|
||||
var parsedTree = ParseViewFile(templateEngine, fileInfo);
|
||||
var chunksToAdd = parsedTree.Chunks
|
||||
.Where(chunk => ChunkMergers.ContainsKey(chunk.GetType()));
|
||||
inheritedChunks.AddRange(chunksToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
inheritedChunks.AddRange(_defaultInheritedChunks);
|
||||
|
||||
return inheritedChunks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges a list of chunks into the <see cref="CodeTree"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="inherited">The list of chunks to merge.</param>
|
||||
public void MergeInheritedChunks(List<Chunk> inherited)
|
||||
{
|
||||
var current = CodeTree.Chunks;
|
||||
|
||||
// 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.
|
||||
foreach (var chunk in current)
|
||||
{
|
||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out var merger))
|
||||
{
|
||||
merger.VisitChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
// In the second phase we invoke IChunkMerger.Merge for each chunk that has a mapped merger.
|
||||
// During this phase, the merger can either add to the CodeTree or ignore the chunk based on the merging
|
||||
// rules.
|
||||
foreach (var chunk in inherited)
|
||||
{
|
||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out var merger))
|
||||
{
|
||||
// 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
|
||||
merger.Merge(CodeTree, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<Type, IChunkMerger> GetMergerMappings(CodeTree codeTree, string defaultModel)
|
||||
{
|
||||
var modelType = ChunkHelper.GetModelTypeName(codeTree, defaultModel);
|
||||
return new Dictionary<Type, IChunkMerger>
|
||||
{
|
||||
{ typeof(UsingChunk), new UsingChunkMerger() },
|
||||
{ typeof(InjectChunk), new InjectChunkMerger(modelType) },
|
||||
{ typeof(SetBaseTypeChunk), new SetBaseTypeChunkMerger(modelType) }
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: This needs to be cached (#1016)
|
||||
private CodeTree ParseViewFile(RazorTemplateEngine engine,
|
||||
IFileInfo fileInfo)
|
||||
{
|
||||
using (var stream = fileInfo.CreateReadStream())
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
var parseResults = engine.ParseTemplate(streamReader);
|
||||
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
|
||||
var language = engine.Host.CodeLanguage;
|
||||
var codeGenerator = language.CreateCodeGenerator(className,
|
||||
engine.Host.DefaultNamespace,
|
||||
fileInfo.PhysicalPath,
|
||||
engine.Host);
|
||||
codeGenerator.Visit(parseResults);
|
||||
return codeGenerator.Context.CodeTreeBuilder.CodeTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// 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.Generator.Compiler;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for merging <see cref="Chunk"/> instances from _ViewStart files.
|
||||
/// </summary>
|
||||
public interface IChunkMerger
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits a <see cref="Chunk"/> from the <see cref="CodeTree"/> to merge into.
|
||||
/// </summary>
|
||||
/// <param name="chunk">A <see cref="Chunk"/> from the tree.</param>
|
||||
void VisitChunk(Chunk chunk);
|
||||
|
||||
/// <summary>
|
||||
/// Merges an inherited <see cref="Chunk"/> into the <see cref="CodeTree"/>.
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> to merge into.</param>
|
||||
/// <param name="chunk">The <see cref="Chunk"/> to merge.</param>
|
||||
void Merge(CodeTree codeTree, Chunk chunk);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="InjectChunk"/> instances.
|
||||
/// </summary>
|
||||
public class InjectChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly HashSet<string> _addedMemberNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
private string _modelType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="InjectChunkMerger"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The model type to be used to replace <TModel> tokens.</param>
|
||||
public InjectChunkMerger([NotNull] string modelType)
|
||||
{
|
||||
_modelType = '<' + modelType + '>';
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk([NotNull] Chunk chunk)
|
||||
{
|
||||
var injectChunk = ChunkHelper.EnsureChunk<InjectChunk>(chunk);
|
||||
injectChunk.TypeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
|
||||
_addedMemberNames.Add(injectChunk.MemberName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
|
||||
{
|
||||
var injectChunk = ChunkHelper.EnsureChunk<InjectChunk>(chunk);
|
||||
if (!_addedMemberNames.Contains(injectChunk.MemberName))
|
||||
{
|
||||
_addedMemberNames.Add(injectChunk.MemberName);
|
||||
codeTree.Chunks.Add(TransformChunk(injectChunk));
|
||||
}
|
||||
}
|
||||
|
||||
private InjectChunk TransformChunk(InjectChunk injectChunk)
|
||||
{
|
||||
var typeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
|
||||
if (typeName != injectChunk.TypeName)
|
||||
{
|
||||
return new InjectChunk(typeName, injectChunk.MemberName)
|
||||
{
|
||||
Start = injectChunk.Start,
|
||||
Association = injectChunk.Association
|
||||
};
|
||||
}
|
||||
return injectChunk;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// 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.Generator.Compiler;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="SetBaseTypeChunk"/> instances.
|
||||
/// </summary>
|
||||
public class SetBaseTypeChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly string _modelType;
|
||||
private bool _isBaseTypeSet;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SetBaseTypeChunkMerger"/>.
|
||||
/// </summary>
|
||||
/// <param name="defaultModelType">The type name of the model used by default.</param>
|
||||
public SetBaseTypeChunkMerger(string modelType)
|
||||
{
|
||||
_modelType = '<' + modelType + '>';
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk([NotNull] Chunk chunk)
|
||||
{
|
||||
var setBaseTypeChunk = ChunkHelper.EnsureChunk<SetBaseTypeChunk>(chunk);
|
||||
setBaseTypeChunk.TypeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
|
||||
_isBaseTypeSet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
|
||||
{
|
||||
if (!_isBaseTypeSet)
|
||||
{
|
||||
var baseTypeChunk = ChunkHelper.EnsureChunk<SetBaseTypeChunk>(chunk);
|
||||
|
||||
// The base type can set exactly once and the first one we encounter wins.
|
||||
_isBaseTypeSet = true;
|
||||
|
||||
codeTree.Chunks.Add(TransformChunk(baseTypeChunk));
|
||||
}
|
||||
}
|
||||
|
||||
private SetBaseTypeChunk TransformChunk(SetBaseTypeChunk setBaseTypeChunk)
|
||||
{
|
||||
var typeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
|
||||
if (typeName != setBaseTypeChunk.TypeName)
|
||||
{
|
||||
return new SetBaseTypeChunk
|
||||
{
|
||||
TypeName = typeName,
|
||||
Start = setBaseTypeChunk.Start,
|
||||
Association = setBaseTypeChunk.Association
|
||||
};
|
||||
}
|
||||
return setBaseTypeChunk;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IChunkMerger"/> that merges <see cref="UsingChunk"/> instances.
|
||||
/// </summary>
|
||||
public class UsingChunkMerger : IChunkMerger
|
||||
{
|
||||
private readonly HashSet<string> _currentUsings = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void VisitChunk([NotNull] Chunk chunk)
|
||||
{
|
||||
var namespaceChunk = ChunkHelper.EnsureChunk<UsingChunk>(chunk);
|
||||
_currentUsings.Add(namespaceChunk.Namespace);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
|
||||
{
|
||||
var namespaceChunk = ChunkHelper.EnsureChunk<UsingChunk>(chunk);
|
||||
|
||||
if (!_currentUsings.Contains(namespaceChunk.Namespace))
|
||||
{
|
||||
_currentUsings.Add(namespaceChunk.Namespace);
|
||||
codeTree.Chunks.Add(namespaceChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,8 +10,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Represents the chunk for an @inject statement.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The type of object that would be injected</param>
|
||||
/// <param name="propertyName">The member name the field is exposed to the page as.</param>
|
||||
/// <param name="typeName">The type name of the property to be injected</param>
|
||||
/// <param name="propertyName">The member name of the property to be injected.</param>
|
||||
public InjectChunk(string typeName,
|
||||
string propertyName)
|
||||
{
|
||||
|
@ -19,8 +19,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
MemberName = propertyName;
|
||||
}
|
||||
|
||||
public string TypeName { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the type name of the property to be injected.
|
||||
/// </summary>
|
||||
public string TypeName { get; set; }
|
||||
|
||||
public string MemberName { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property to be injected.
|
||||
/// </summary>
|
||||
public string MemberName { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
|
||||
|
@ -13,13 +11,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public class MvcCSharpCodeBuilder : CSharpCodeBuilder
|
||||
{
|
||||
private readonly MvcRazorHostOptions _hostOptions;
|
||||
private readonly string _defaultModel;
|
||||
private readonly string _activateAttribute;
|
||||
|
||||
public MvcCSharpCodeBuilder([NotNull] CodeGeneratorContext context,
|
||||
[NotNull] MvcRazorHostOptions hostOptions)
|
||||
public MvcCSharpCodeBuilder([NotNull] CodeGeneratorContext context,
|
||||
[NotNull] string defaultModel,
|
||||
[NotNull] string activateAttribute)
|
||||
: base(context)
|
||||
{
|
||||
_hostOptions = hostOptions;
|
||||
_defaultModel = defaultModel;
|
||||
_activateAttribute = activateAttribute;
|
||||
}
|
||||
|
||||
private string Model { get; set; }
|
||||
|
@ -27,12 +28,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
|
||||
{
|
||||
// Grab the last model chunk so it gets intellisense.
|
||||
// NOTE: 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.
|
||||
var modelChunk = Context.CodeTreeBuilder.CodeTree.Chunks.OfType<ModelChunk>()
|
||||
.LastOrDefault();
|
||||
var modelChunk = ChunkHelper.GetModelChunk(Context.CodeTreeBuilder.CodeTree);
|
||||
|
||||
Model = modelChunk != null ? modelChunk.ModelType : _hostOptions.DefaultModel;
|
||||
Model = modelChunk != null ? modelChunk.ModelType : _defaultModel;
|
||||
|
||||
// If there were any model chunks then we need to modify the class declaration signature.
|
||||
if (modelChunk != null)
|
||||
|
@ -62,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
writer.WriteLineHiddenDirective();
|
||||
|
||||
var injectVisitor = new InjectChunkVisitor(writer, Context, _hostOptions.ActivateAttributeName);
|
||||
var injectVisitor = new InjectChunkVisitor(writer, Context, _activateAttribute);
|
||||
injectVisitor.Accept(Context.CodeTreeBuilder.CodeTree.Chunks);
|
||||
|
||||
writer.WriteLine();
|
||||
|
|
|
@ -1,49 +1,71 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
|
||||
{
|
||||
private const string ViewNamespace = "ASP";
|
||||
|
||||
private static readonly string[] _defaultNamespaces = new[]
|
||||
{
|
||||
private const string BaseType = "Microsoft.AspNet.Mvc.Razor.RazorPage";
|
||||
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[]
|
||||
{
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", "Html"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
|
||||
new InjectChunk("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
|
||||
};
|
||||
|
||||
private readonly MvcRazorHostOptions _hostOptions;
|
||||
|
||||
private readonly string _appRoot;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||
// This field holds the type name without the generic decoration (MyBaseType)
|
||||
private readonly string _baseType;
|
||||
|
||||
public MvcRazorHost(Type baseType)
|
||||
: this(baseType.FullName)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorHost"/> with the specified
|
||||
/// <param name="appEnvironment"/>.
|
||||
/// </summary>
|
||||
/// <param name="appEnvironment">Contains information about the executing application.</param>
|
||||
public MvcRazorHost(IApplicationEnvironment appEnvironment)
|
||||
: this(appEnvironment.ApplicationBasePath,
|
||||
new PhysicalFileSystem(appEnvironment.ApplicationBasePath))
|
||||
{
|
||||
}
|
||||
|
||||
public MvcRazorHost(string baseType)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorHost"/> at the specified application root
|
||||
/// and <paramref name="fileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="applicationBasePath">The base path of the application.</param>
|
||||
/// <param name="fileSystem">
|
||||
/// A <see cref="IFileSystem"/> rooted at the <paramref name="applicationBasePath"/>.
|
||||
/// </param>
|
||||
protected internal MvcRazorHost(string applicationBasePath,
|
||||
IFileSystem fileSystem)
|
||||
: base(new CSharpRazorCodeLanguage())
|
||||
{
|
||||
// TODO: this needs to flow from the application rather than being initialized here.
|
||||
// Tracked by #774
|
||||
_hostOptions = new MvcRazorHostOptions();
|
||||
_baseType = baseType;
|
||||
DefaultBaseClass = baseType + '<' + _hostOptions.DefaultModel + '>';
|
||||
_appRoot = applicationBasePath;
|
||||
_fileSystem = fileSystem;
|
||||
_baseType = BaseType;
|
||||
|
||||
DefaultBaseClass = BaseType + '<' + DefaultModel + '>';
|
||||
DefaultNamespace = "Asp";
|
||||
GeneratedClassContext = new GeneratedClassContext(
|
||||
executeMethodName: "ExecuteAsync",
|
||||
writeMethodName: "Write",
|
||||
|
@ -62,51 +84,64 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model type used by default when no model is specified.
|
||||
/// </summary>
|
||||
/// <remarks>This value is used as the generic type argument for the base type </remarks>
|
||||
public virtual string DefaultModel
|
||||
{
|
||||
get { return "dynamic"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of chunks that are injected by default by this host.
|
||||
/// </summary>
|
||||
public virtual IReadOnlyList<Chunk> DefaultInheritedChunks
|
||||
{
|
||||
get { return _defaultInheritedChunks; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name attribute that is used to decorate properties that are injected and need to be
|
||||
/// activated.
|
||||
/// </summary>
|
||||
public virtual string ActivateAttribute
|
||||
{
|
||||
get { return "Microsoft.AspNet.Mvc.ActivateAttribute"; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||
{
|
||||
var className = ParserHelpers.SanitizeClassName(rootRelativePath);
|
||||
using (var reader = new StreamReader(inputStream))
|
||||
{
|
||||
var engine = new RazorTemplateEngine(this);
|
||||
return engine.GenerateCode(reader, className, ViewNamespace, rootRelativePath);
|
||||
return engine.GenerateCode(reader, className, DefaultNamespace, rootRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
|
||||
/// <inheritdoc />
|
||||
public override ParserBase DecorateCodeParser([NotNull] ParserBase incomingCodeParser)
|
||||
{
|
||||
return new MvcRazorCodeParser(_baseType);
|
||||
}
|
||||
|
||||
public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeGeneratorContext context)
|
||||
/// <inheritdoc />
|
||||
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
|
||||
[NotNull] CodeGeneratorContext context)
|
||||
{
|
||||
UpdateCodeBuilder(context);
|
||||
return new MvcCSharpCodeBuilder(context, _hostOptions);
|
||||
return new MvcCSharpCodeBuilder(context, DefaultModel, ActivateAttribute);
|
||||
}
|
||||
|
||||
private void UpdateCodeBuilder(CodeGeneratorContext context)
|
||||
{
|
||||
var currentChunks = context.CodeTreeBuilder.CodeTree.Chunks;
|
||||
var existingInjects = new HashSet<string>(currentChunks.OfType<InjectChunk>()
|
||||
.Select(c => c.MemberName),
|
||||
StringComparer.Ordinal);
|
||||
|
||||
var modelChunk = currentChunks.OfType<ModelChunk>()
|
||||
.LastOrDefault();
|
||||
var model = _hostOptions.DefaultModel;
|
||||
if (modelChunk != null)
|
||||
{
|
||||
model = modelChunk.ModelType;
|
||||
}
|
||||
model = '<' + model + '>';
|
||||
|
||||
// Locate properties by name that haven't already been injected in to the View.
|
||||
var propertiesToAdd = _hostOptions.DefaultInjectedProperties
|
||||
.Where(c => !existingInjects.Contains(c.MemberName));
|
||||
foreach (var property in propertiesToAdd)
|
||||
{
|
||||
var typeName = property.TypeName.Replace("<TModel>", model);
|
||||
currentChunks.Add(new InjectChunk(typeName, property.MemberName));
|
||||
}
|
||||
var chunkUtility = new ChunkInheritanceUtility(context.CodeTreeBuilder.CodeTree,
|
||||
DefaultInheritedChunks,
|
||||
DefaultModel);
|
||||
var inheritedChunks = chunkUtility.GetInheritedChunks(this, _fileSystem, _appRoot, context.SourceFile);
|
||||
chunkUtility.MergeInheritedChunks(inheritedChunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents configuration options for the Razor Host
|
||||
/// </summary>
|
||||
public class MvcRazorHostOptions
|
||||
{
|
||||
public MvcRazorHostOptions()
|
||||
{
|
||||
DefaultModel = "dynamic";
|
||||
ActivateAttributeName = "Microsoft.AspNet.Mvc.ActivateAttribute";
|
||||
DefaultInjectedProperties = new List<InjectDescriptor>()
|
||||
{
|
||||
new InjectDescriptor("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", "Html"),
|
||||
new InjectDescriptor("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
|
||||
new InjectDescriptor("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model that is used by default for generated views
|
||||
/// when no model is explicily specified. Defaults to dynamic.
|
||||
/// </summary>
|
||||
public string DefaultModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the attribue that is used to decorate properties that are injected and need to
|
||||
/// be activated.
|
||||
/// </summary>
|
||||
public string ActivateAttributeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of properties that are injected by default.
|
||||
/// </summary>
|
||||
public IList<InjectDescriptor> DefaultInjectedProperties { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Host.Test")]
|
|
@ -26,6 +26,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
|
|||
return GetString("ArgumentCannotBeNullOrEmpy");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Argument must be an instance of type '{0}'.
|
||||
/// </summary>
|
||||
internal static string ArgumentMustBeOfType
|
||||
{
|
||||
get { return GetString("ArgumentMustBeOfType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Argument must be an instance of type '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentMustBeOfType(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeOfType"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
|
||||
/// </summary>
|
||||
|
|
|
@ -120,6 +120,9 @@
|
|||
<data name="ArgumentCannotBeNullOrEmpy" xml:space="preserve">
|
||||
<value>Argument cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="ArgumentMustBeOfType" xml:space="preserve">
|
||||
<value>Argument must be an instance of '{0}'.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
|
||||
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
|
||||
</data>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public static class ViewStartUtility
|
||||
{
|
||||
private const string ViewStartFileName = "_viewstart.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the given path represents a view start file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to inspect.</param>
|
||||
/// <returns>True if the path is a view start file, false otherwise.</returns>
|
||||
public static bool IsViewStart([NotNull] string path)
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
return string.Equals(ViewStartFileName, fileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view start locations that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationBase">The base of the application.</param>
|
||||
/// <param name="path">The path to locate view starts for.</param>
|
||||
/// <returns>A sequence of paths that represent potential view start locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="path"/> and moves
|
||||
/// upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewStartLocations(string applicationBase, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
applicationBase = TrimTrailingSlash(applicationBase);
|
||||
var viewStartLocations = new List<string>();
|
||||
var currentDir = GetViewDirectory(applicationBase, path);
|
||||
while (IsSubDirectory(applicationBase, currentDir))
|
||||
{
|
||||
viewStartLocations.Add(Path.Combine(currentDir, ViewStartFileName));
|
||||
currentDir = Path.GetDirectoryName(currentDir);
|
||||
}
|
||||
|
||||
return viewStartLocations;
|
||||
}
|
||||
|
||||
private static bool IsSubDirectory(string appRoot, string currentDir)
|
||||
{
|
||||
return currentDir.StartsWith(appRoot, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetViewDirectory(string appRoot, string viewPath)
|
||||
{
|
||||
if (viewPath.StartsWith("~/"))
|
||||
{
|
||||
viewPath = viewPath.Substring(2);
|
||||
}
|
||||
else if (viewPath[0] == Path.DirectorySeparatorChar ||
|
||||
viewPath[0] == Path.AltDirectorySeparatorChar)
|
||||
{
|
||||
viewPath = viewPath.Substring(1);
|
||||
}
|
||||
|
||||
var viewDir = Path.GetDirectoryName(viewPath);
|
||||
return Path.GetFullPath(Path.Combine(appRoot, viewDir));
|
||||
}
|
||||
|
||||
private static string TrimTrailingSlash(string path)
|
||||
{
|
||||
if (path.Length > 0 &&
|
||||
path[path.Length - 1] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
return path.Substring(0, path.Length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@
|
|||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.FileSystems": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Common": "",
|
||||
"Microsoft.AspNet.Razor": "4.0.0-*"
|
||||
"Microsoft.AspNet.Razor": "4.0.0-*",
|
||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
|
|
|
@ -99,11 +99,26 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <inheritdoc />
|
||||
public abstract Task ExecuteAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
public virtual void Write(object value)
|
||||
{
|
||||
WriteTo(Output, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
/// <remarks>
|
||||
/// <paramref name="value"/>s of type <see cref="HtmlString"/> are written without encoding and the
|
||||
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
|
||||
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
|
||||
/// <paramref name="writer"/>.
|
||||
/// </remarks>
|
||||
public virtual void WriteTo(TextWriter writer, object content)
|
||||
{
|
||||
if (content != null)
|
||||
|
@ -128,11 +143,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
public void WriteLiteral(object value)
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
public virtual void WriteLiteral(object value)
|
||||
{
|
||||
WriteLiteralTo(Output, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> without HTML encoding to the <paramref name="writer"/>.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
public virtual void WriteLiteralTo(TextWriter writer, object text)
|
||||
{
|
||||
if (text != null)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
|
@ -12,21 +11,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <inheritdoc />
|
||||
public class ViewStartProvider : IViewStartProvider
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
private readonly string _appRoot;
|
||||
private readonly IRazorPageFactory _pageFactory;
|
||||
|
||||
public ViewStartProvider(IApplicationEnvironment appEnv,
|
||||
IRazorPageFactory pageFactory)
|
||||
{
|
||||
_appRoot = TrimTrailingSlash(appEnv.ApplicationBasePath);
|
||||
_appRoot = appEnv.ApplicationBasePath;
|
||||
_pageFactory = pageFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
|
||||
{
|
||||
var viewStartLocations = GetViewStartLocations(path);
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(_appRoot, path);
|
||||
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
|
@ -38,55 +36,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
return viewStarts;
|
||||
}
|
||||
|
||||
internal IEnumerable<string> GetViewStartLocations(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var viewStartLocations = new List<string>();
|
||||
var currentDir = GetViewDirectory(_appRoot, path);
|
||||
while (IsSubDirectory(_appRoot, currentDir))
|
||||
{
|
||||
viewStartLocations.Add(Path.Combine(currentDir, ViewStartFileName));
|
||||
currentDir = Path.GetDirectoryName(currentDir);
|
||||
}
|
||||
|
||||
return viewStartLocations;
|
||||
}
|
||||
|
||||
private static bool IsSubDirectory(string appRoot, string currentDir)
|
||||
{
|
||||
return currentDir.StartsWith(appRoot, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetViewDirectory(string appRoot, string viewPath)
|
||||
{
|
||||
if (viewPath.StartsWith("~/"))
|
||||
{
|
||||
viewPath = viewPath.Substring(2);
|
||||
}
|
||||
else if (viewPath[0] == Path.DirectorySeparatorChar ||
|
||||
viewPath[0] == Path.AltDirectorySeparatorChar)
|
||||
{
|
||||
viewPath = viewPath.Substring(1);
|
||||
}
|
||||
|
||||
var viewDir = Path.GetDirectoryName(viewPath);
|
||||
return Path.GetFullPath(Path.Combine(appRoot, viewDir));
|
||||
}
|
||||
|
||||
private static string TrimTrailingSlash(string path)
|
||||
{
|
||||
if (path.Length > 0 &&
|
||||
path[path.Length - 1] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
return path.Substring(0, path.Length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
|
||||
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
||||
|
||||
yield return describe.Instance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorPage).FullName));
|
||||
// The host is designed to be discarded after consumption and is very inexpensive to initialize.
|
||||
yield return describe.Transient<IMvcRazorHost, MvcRazorHost>();
|
||||
|
||||
yield return describe.Singleton<ICompilationService, RoslynCompilationService>();
|
||||
yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>();
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using RazorWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class DirectivesTest
|
||||
{
|
||||
private readonly IServiceProvider _provider = TestHelper.CreateServices("RazorWebSite");
|
||||
private readonly Action<IBuilder> _app = new Startup().Configure;
|
||||
|
||||
[Fact]
|
||||
public async Task ViewsInheritsUsingsAndInjectDirectivesFromViewStarts()
|
||||
{
|
||||
var expected = @"Hello Person1";
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.Handler;
|
||||
|
||||
// Act
|
||||
var result = await client.GetAsync("http://localhost/Directives/ViewInheritsInjectAndUsingsFromViewStarts");
|
||||
|
||||
// Assert
|
||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||
Assert.Equal(expected, body.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewInheritsBasePageFromViewStarts()
|
||||
{
|
||||
var expected = @"WriteLiteral says:layout:Write says:Write says:Hello Person2";
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.Handler;
|
||||
|
||||
// Act
|
||||
var result = await client.GetAsync("http://localhost/Directives/ViewInheritsBasePageFromViewStarts");
|
||||
|
||||
// Assert
|
||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||
Assert.Equal(expected, body.Trim());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
public class ChunkInheritanceUtilityTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReadsChunksFromViewStartsInPath()
|
||||
{
|
||||
// Arrange
|
||||
var appRoot = @"x:\myapproot";
|
||||
var fileSystem = new TestFileSystem();
|
||||
fileSystem.AddFile(@"x:\myapproot\views\accounts\_viewstart.cshtml", "@using AccountModels");
|
||||
fileSystem.AddFile(@"x:\myapproot\views\Shared\_viewstart.cshtml", "@inject SharedHelper Shared");
|
||||
fileSystem.AddFile(@"x:\myapproot\views\home\_viewstart.cshtml", "@using MyNamespace");
|
||||
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
|
||||
@"@inject MyHelper<TModel> Helper
|
||||
@inherits MyBaseType
|
||||
|
||||
@{
|
||||
Layout = ""test.cshtml"";
|
||||
}
|
||||
|
||||
");
|
||||
var host = new MvcRazorHost(appRoot, fileSystem);
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
appRoot,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, chunks.Count);
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[0]);
|
||||
Assert.Equal("MyNamespace", usingChunk.Namespace);
|
||||
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]);
|
||||
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
|
||||
Assert.Equal("Helper", injectChunk.MemberName);
|
||||
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[2]);
|
||||
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReturnsEmptySequenceIfNoViewStartsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var appRoot = @"x:\myapproot";
|
||||
var fileSystem = new TestFileSystem();
|
||||
fileSystem.AddFile(@"x:\myapproot\_viewstart.cs", string.Empty);
|
||||
fileSystem.AddFile(@"x:\myapproot\views\_Layout.cshtml", string.Empty);
|
||||
fileSystem.AddFile(@"x:\myapproot\views\home\_not-viewstart.cshtml", string.Empty);
|
||||
var host = new MvcRazorHost(appRoot, fileSystem);
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
appRoot,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(chunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReturnsDefaultInheritedChunks()
|
||||
{
|
||||
// Arrange
|
||||
var appRoot = @"x:\myapproot";
|
||||
var fileSystem = new TestFileSystem();
|
||||
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
|
||||
@"@inject DifferentHelper<TModel> Html
|
||||
@using AppNamespace.Models
|
||||
@{
|
||||
Layout = ""test.cshtml"";
|
||||
}
|
||||
|
||||
");
|
||||
var host = new MvcRazorHost(appRoot, fileSystem);
|
||||
var defaultChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
};
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), defaultChunks, "dynamic");
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
appRoot,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, chunks.Count);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[0]);
|
||||
Assert.Equal("DifferentHelper<TModel>", injectChunk.TypeName);
|
||||
Assert.Equal("Html", injectChunk.MemberName);
|
||||
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]);
|
||||
Assert.Equal("AppNamespace.Models", usingChunk.Namespace);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
// 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.Generator.Compiler;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
public class InjectChunkMergerTest
|
||||
{
|
||||
[Fact]
|
||||
public void Visit_ThrowsIfThePassedInChunkIsNotAInjectChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of 'Microsoft.AspNet.Mvc.Razor.InjectChunk'.";
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.VisitChunk(new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("MyApp.TestHelper<TModel>", "MyApp.TestHelper<Person>")]
|
||||
[InlineData("TestBaseType", "TestBaseType")]
|
||||
public void Visit_UpdatesTModelTokenToMatchModelType(string typeName, string expectedValue)
|
||||
{
|
||||
// Arrange
|
||||
var chunk = new InjectChunk(typeName, "TestHelper");
|
||||
var merger = new InjectChunkMerger("Person");
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(chunk);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedValue, chunk.TypeName);
|
||||
Assert.Equal("TestHelper", chunk.MemberName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_ThrowsIfThePassedInChunkIsNotAInjectChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of 'Microsoft.AspNet.Mvc.Razor.InjectChunk'.";
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.Merge(new CodeTree(), new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_AddsChunkIfChunkWithMatchingPropertyNameWasNotVisitedInCodeTree()
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = "MyApp.MyHelperType";
|
||||
var expectedProperty = "MyHelper";
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new InjectChunk(expectedType, expectedProperty));
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunk);
|
||||
Assert.Equal(expectedType, injectChunk.TypeName);
|
||||
Assert.Equal(expectedProperty, injectChunk.MemberName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresChunkIfChunkWithMatchingPropertyNameWasVisitedInCodeTree()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(new InjectChunk("MyTypeA", "MyProperty"));
|
||||
merger.Merge(codeTree, new InjectChunk("MyTypeB", "MyProperty"));
|
||||
|
||||
// Assert
|
||||
Assert.Empty(codeTree.Chunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_MatchesPropertyNameInCaseSensitiveManner()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(new InjectChunk("MyType", "MyProperty"));
|
||||
merger.Merge(codeTree, new InjectChunk("MyType", "myproperty"));
|
||||
merger.Merge(codeTree, new InjectChunk("MyTypeB", "different-property"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, codeTree.Chunks.Count);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(codeTree.Chunks[0]);
|
||||
Assert.Equal("MyType", injectChunk.TypeName);
|
||||
Assert.Equal("myproperty", injectChunk.MemberName);
|
||||
|
||||
injectChunk = Assert.IsType<InjectChunk>(codeTree.Chunks[1]);
|
||||
Assert.Equal("MyTypeB", injectChunk.TypeName);
|
||||
Assert.Equal("different-property", injectChunk.MemberName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_ResolvesModelNameInTypesWithTModelToken()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new InjectChunk("MyHelper<TModel>", "MyProperty"));
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunk);
|
||||
Assert.Equal("MyHelper<dynamic>", injectChunk.TypeName);
|
||||
Assert.Equal("MyProperty", injectChunk.MemberName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_ReplacesTModelTokensWithModel()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new InjectChunkMerger("MyTestModel2");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new InjectChunk("MyHelper<TModel>", "MyProperty"));
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunk);
|
||||
Assert.Equal("MyHelper<MyTestModel2>", injectChunk.TypeName);
|
||||
Assert.Equal("MyProperty", injectChunk.MemberName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresChunkIfChunkWithMatchingPropertyNameWasPreviouslyMerged()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new InjectChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new InjectChunk("SomeType", "Property"));
|
||||
merger.Merge(codeTree, new InjectChunk("SomeOtherType", "Property"));
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunk);
|
||||
Assert.Equal("SomeType", injectChunk.TypeName);
|
||||
Assert.Equal("Property", injectChunk.MemberName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
// 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.Generator.Compiler;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
public class SetBaseTypeChunkMergerTest
|
||||
{
|
||||
[Fact]
|
||||
public void Visit_ThrowsIfThePassedInChunkIsNotASetBaseTypeChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of "+
|
||||
"'Microsoft.AspNet.Razor.Generator.Compiler.SetBaseTypeChunk'.";
|
||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.VisitChunk(new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("MyApp.BaseType<TModel>", "MyApp.BaseType<Person>")]
|
||||
[InlineData("TestBaseType", "TestBaseType")]
|
||||
public void Visit_UpdatesTModelTokenToMatchModelType(string typeName, string expectedValue)
|
||||
{
|
||||
// Arrange
|
||||
var chunk = new SetBaseTypeChunk
|
||||
{
|
||||
TypeName = typeName,
|
||||
};
|
||||
var merger = new SetBaseTypeChunkMerger("Person");
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(chunk);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedValue, chunk.TypeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_ThrowsIfThePassedInChunkIsNotASetBaseTypeChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of " +
|
||||
"'Microsoft.AspNet.Razor.Generator.Compiler.SetBaseTypeChunk'.";
|
||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.Merge(new CodeTree(), new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_SetsBaseTypeIfItHasNotBeenSetInCodeTree()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "MyApp.Razor.MyBaseType";
|
||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new SetBaseTypeChunk { TypeName = expected });
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
||||
Assert.Equal(expected, setBaseTypeChunk.TypeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresSetBaseTypeChunksIfCodeTreeContainsOne()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(new SetBaseTypeChunk { TypeName = "MyBaseType1" });
|
||||
merger.Merge(codeTree, new SetBaseTypeChunk { TypeName = "MyBaseType2" });
|
||||
|
||||
// Assert
|
||||
Assert.Empty(codeTree.Chunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresSetBaseTypeChunksIfSetBaseTypeWasPreviouslyMerged()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new SetBaseTypeChunk { TypeName = "MyBase1" });
|
||||
merger.Merge(codeTree, new SetBaseTypeChunk { TypeName = "MyBase2" });
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
||||
Assert.Equal("MyBase1", setBaseTypeChunk.TypeName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// 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.Generator.Compiler;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
public class UsingChunkMergerTest
|
||||
{
|
||||
[Fact]
|
||||
public void Visit_ThrowsIfThePassedInChunkIsNotAUsingChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of 'Microsoft.AspNet.Razor.Generator.Compiler.UsingChunk'.";
|
||||
var merger = new UsingChunkMerger();
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.VisitChunk(new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_ThrowsIfThePassedInChunkIsNotAUsingChunk()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Argument must be an instance of 'Microsoft.AspNet.Razor.Generator.Compiler.UsingChunk'.";
|
||||
var merger = new UsingChunkMerger();
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(() => merger.Merge(new CodeTree(), new LiteralChunk()), "chunk", expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_AddsNamespacesThatHaveNotBeenVisitedInCodeTree()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "MyApp.Models";
|
||||
var merger = new UsingChunkMerger();
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = expected });
|
||||
|
||||
// Assert
|
||||
var chunk = Assert.Single(codeTree.Chunks);
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunk);
|
||||
Assert.Equal(expected, usingChunk.Namespace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresNamespacesThatHaveBeenVisitedInCodeTree()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new UsingChunkMerger();
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.VisitChunk(new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
|
||||
// Assert
|
||||
Assert.Empty(codeTree.Chunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_IgnoresNamespacesThatHaveBeenVisitedDuringMerge()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new UsingChunkMerger();
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.Mvc.Razor" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, codeTree.Chunks.Count);
|
||||
var chunk = Assert.IsType<UsingChunk>(codeTree.Chunks[0]);
|
||||
Assert.Equal("Microsoft.AspNet.Mvc", chunk.Namespace);
|
||||
chunk = Assert.IsType<UsingChunk>(codeTree.Chunks[1]);
|
||||
Assert.Equal("Microsoft.AspNet.Mvc.Razor", chunk.Namespace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_MacthesNamespacesInCaseSensitiveManner()
|
||||
{
|
||||
// Arrange
|
||||
var merger = new UsingChunkMerger();
|
||||
var codeTree = new CodeTree();
|
||||
|
||||
// Act
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
|
||||
merger.Merge(codeTree, new UsingChunk { Namespace = "Microsoft.AspNet.mvc" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, codeTree.Chunks.Count);
|
||||
var chunk = Assert.IsType<UsingChunk>(codeTree.Chunks[0]);
|
||||
Assert.Equal("Microsoft.AspNet.Mvc", chunk.Namespace);
|
||||
chunk = Assert.IsType<UsingChunk>(codeTree.Chunks[1]);
|
||||
Assert.Equal("Microsoft.AspNet.mvc", chunk.Namespace);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
|
@ -114,7 +116,7 @@ MyType2 @MyPropertyName2
|
|||
public void InjectVisitor_GeneratesCorrectLineMappings()
|
||||
{
|
||||
// Arrange
|
||||
var host = new MvcRazorHost("RazorView")
|
||||
var host = new MvcRazorHost("appRoot", Mock.Of<IFileSystem>())
|
||||
{
|
||||
DesignTimeMode = true
|
||||
};
|
||||
|
@ -124,8 +126,8 @@ MyType2 @MyPropertyName2
|
|||
var expectedCode = ReadResource("TestFiles/Output/Inject.cs");
|
||||
var expectedLineMappings = new List<LineMapping>
|
||||
{
|
||||
BuildLineMapping(1, 0, 1, 32, 3, 0, 17),
|
||||
BuildLineMapping(28, 1, 8, 573, 26, 8, 20)
|
||||
BuildLineMapping(1, 0, 1, 30, 3, 0, 17),
|
||||
BuildLineMapping(28, 1, 8, 598, 26, 8, 20)
|
||||
};
|
||||
|
||||
// Act
|
||||
|
@ -146,7 +148,7 @@ MyType2 @MyPropertyName2
|
|||
public void InjectVisitorWithModel_GeneratesCorrectLineMappings()
|
||||
{
|
||||
// Arrange
|
||||
var host = new MvcRazorHost("RazorView")
|
||||
var host = new MvcRazorHost("appRoot", Mock.Of<IFileSystem>())
|
||||
{
|
||||
DesignTimeMode = true
|
||||
};
|
||||
|
@ -156,9 +158,9 @@ MyType2 @MyPropertyName2
|
|||
var expectedCode = ReadResource("TestFiles/Output/InjectWithModel.cs");
|
||||
var expectedLineMappings = new List<LineMapping>
|
||||
{
|
||||
BuildLineMapping(7, 0, 7, 126, 6, 7, 7),
|
||||
BuildLineMapping(24, 1, 8, 562, 26, 8, 20),
|
||||
BuildLineMapping(54, 2, 8, 732, 34, 8, 22)
|
||||
BuildLineMapping(7, 0, 7, 151, 6, 7, 7),
|
||||
BuildLineMapping(24, 1, 8, 587, 26, 8, 20),
|
||||
BuildLineMapping(54, 2, 8, 757, 34, 8, 23)
|
||||
};
|
||||
|
||||
// Act
|
||||
|
@ -188,7 +190,7 @@ MyType2 @MyPropertyName2
|
|||
|
||||
private static CodeGeneratorContext CreateContext()
|
||||
{
|
||||
return CodeGeneratorContext.Create(new MvcRazorHost("RazorView"),
|
||||
return CodeGeneratorContext.Create(new MvcRazorHost("appRoot", Mock.Of<IFileSystem>()),
|
||||
"MyClass",
|
||||
"MyNamespace",
|
||||
string.Empty,
|
||||
|
@ -203,11 +205,11 @@ MyType2 @MyPropertyName2
|
|||
int generatedCharacterIndex,
|
||||
int contentLength)
|
||||
{
|
||||
var documentLocation = new SourceLocation(documentAbsoluteIndex,
|
||||
documentLineIndex,
|
||||
var documentLocation = new SourceLocation(documentAbsoluteIndex,
|
||||
documentLineIndex,
|
||||
documentCharacterIndex);
|
||||
var generatedLocation = new SourceLocation(generatedAbsoluteIndex,
|
||||
generatedLineIndex,
|
||||
var generatedLocation = new SourceLocation(generatedAbsoluteIndex,
|
||||
generatedLineIndex,
|
||||
generatedCharacterIndex);
|
||||
|
||||
return new LineMapping(
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
|
@ -106,7 +108,7 @@ Environment.NewLine +
|
|||
public void ModelVisitor_GeneratesCorrectLineMappings()
|
||||
{
|
||||
// Arrange
|
||||
var host = new MvcRazorHost("RazorView")
|
||||
var host = new MvcRazorHost("appRoot", Mock.Of<IFileSystem>())
|
||||
{
|
||||
DesignTimeMode = true
|
||||
};
|
||||
|
@ -116,7 +118,7 @@ Environment.NewLine +
|
|||
var expectedCode = ReadResource("TestFiles/Output/Model.cs");
|
||||
var expectedLineMappings = new List<LineMapping>
|
||||
{
|
||||
BuildLineMapping(7, 0, 7, 126, 6, 7, 30),
|
||||
BuildLineMapping(7, 0, 7, 151, 6, 7, 30),
|
||||
};
|
||||
|
||||
// Act
|
||||
|
@ -146,7 +148,7 @@ Environment.NewLine +
|
|||
|
||||
private static CodeGeneratorContext CreateContext()
|
||||
{
|
||||
return CodeGeneratorContext.Create(new MvcRazorHost("RazorView"),
|
||||
return CodeGeneratorContext.Create(new MvcRazorHost("appRoot", Mock.Of<IFileSystem>()),
|
||||
"MyClass",
|
||||
"MyNamespace",
|
||||
string.Empty,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class TestFileSystem : IFileSystem
|
||||
{
|
||||
private readonly Dictionary<string, IFileInfo> _lookup =
|
||||
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
||||
|
||||
public bool TryGetDirectoryContents(string subpath, out IEnumerable<IFileInfo> contents)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void AddFile(string path, string contents)
|
||||
{
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.CreateReadStream())
|
||||
.Returns(new MemoryStream(Encoding.UTF8.GetBytes(contents)));
|
||||
fileInfo.SetupGet(f => f.PhysicalPath)
|
||||
.Returns(path);
|
||||
fileInfo.SetupGet(f => f.Name)
|
||||
.Returns(Path.GetFileName(path));
|
||||
AddFile(path, fileInfo.Object);
|
||||
}
|
||||
|
||||
public void AddFile(string path, IFileInfo contents)
|
||||
{
|
||||
_lookup.Add(path, contents);
|
||||
}
|
||||
|
||||
public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
|
||||
{
|
||||
return _lookup.TryGetValue(subpath, out fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace Razor
|
||||
namespace Asp
|
||||
{
|
||||
#line 1 ""
|
||||
using MyNamespace
|
||||
|
@ -8,7 +8,7 @@ using MyNamespace
|
|||
;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class __CompiledTemplate : RazorView<dynamic>
|
||||
public class __CompiledTemplate : Microsoft.AspNet.Mvc.Razor.RazorPage<dynamic>
|
||||
{
|
||||
private static object @__o;
|
||||
private void @__RazorDesignTimeHelpers__()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace Razor
|
||||
namespace Asp
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class __CompiledTemplate : RazorView<
|
||||
public class __CompiledTemplate : Microsoft.AspNet.Mvc.Razor.RazorPage<
|
||||
#line 1 ""
|
||||
MyModel
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
[Microsoft.AspNet.Mvc.ActivateAttribute]
|
||||
public
|
||||
#line 3 ""
|
||||
MyService<TModel> Html
|
||||
MyService<MyModel> Html
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace Razor
|
||||
namespace Asp
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class __CompiledTemplate : RazorView<
|
||||
public class __CompiledTemplate : Microsoft.AspNet.Mvc.Razor.RazorPage<
|
||||
#line 1 ""
|
||||
System.Collections.IEnumerable
|
||||
|
||||
|
|
|
@ -2,12 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class ViewStartProviderTest
|
||||
{
|
||||
|
@ -18,10 +15,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var appPath = @"x:\test";
|
||||
var provider = new ViewStartProvider(GetAppEnv(appPath), Mock.Of<IRazorPageFactory>());
|
||||
|
||||
// Act
|
||||
var result = provider.GetViewStartLocations(viewPath);
|
||||
var result = ViewStartUtility.GetViewStartLocations(appPath, viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
|
@ -37,9 +33,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
"/Views/Home/View.cshtml",
|
||||
new[]
|
||||
{
|
||||
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\Home\_viewstart.cshtml",
|
||||
@"x:\test\myapp\Views\_viewstart.cshtml",
|
||||
@"x:\test\myapp\_viewstart.cshtml",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -49,9 +45,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
"Views/Home/View.cshtml",
|
||||
new[]
|
||||
{
|
||||
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\Home\_viewstart.cshtml",
|
||||
@"x:\test\myapp\Views\_viewstart.cshtml",
|
||||
@"x:\test\myapp\_viewstart.cshtml",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -61,9 +57,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
"Views/Home/View.cshtml",
|
||||
new[]
|
||||
{
|
||||
@"x:\test\myapp\Views\Home\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\_ViewStart.cshtml",
|
||||
@"x:\test\myapp\Views\Home\_viewstart.cshtml",
|
||||
@"x:\test\myapp\Views\_viewstart.cshtml",
|
||||
@"x:\test\myapp\_viewstart.cshtml",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -75,22 +71,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
string viewPath,
|
||||
IEnumerable<string> expected)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ViewStartProvider(GetAppEnv(appPath), Mock.Of<IRazorPageFactory>());
|
||||
|
||||
// Act
|
||||
var result = provider.GetViewStartLocations(viewPath);
|
||||
var result = ViewStartUtility.GetViewStartLocations(appPath, viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
private static IApplicationEnvironment GetAppEnv(string appPath)
|
||||
{
|
||||
var appEnv = new Mock<IApplicationEnvironment>();
|
||||
appEnv.Setup(p => p.ApplicationBasePath)
|
||||
.Returns(appPath);
|
||||
return appEnv.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,8 @@
|
|||
},
|
||||
"resources": "TestFiles\\**",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Razor.Host" : "",
|
||||
"Microsoft.AspNet.Mvc.Razor.Host": "",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Xunit.KRunner": "1.0.0-*"
|
||||
},
|
||||
"commands": {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Diagnostics;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace RazorWebSite
|
||||
{
|
||||
public class DirectivesController : Controller
|
||||
{
|
||||
public ViewResult ViewInheritsInjectAndUsingsFromViewStarts()
|
||||
{
|
||||
return View(new Person { Name = "Person1" });
|
||||
}
|
||||
|
||||
public ViewResult ViewInheritsBasePageFromViewStarts()
|
||||
{
|
||||
return View("/views/directives/scoped/ViewInheritsBasePageFromViewStarts.cshtml",
|
||||
new Person { Name = "Person2" });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
|
||||
namespace RazorWebSite
|
||||
{
|
||||
public abstract class MyBasePage<TModel> : RazorPage<TModel>
|
||||
{
|
||||
public override void WriteLiteral(object value)
|
||||
{
|
||||
base.WriteLiteral("WriteLiteral says:");
|
||||
base.WriteLiteral(value);
|
||||
}
|
||||
|
||||
public override void Write(object value)
|
||||
{
|
||||
base.WriteLiteral("Write says:");
|
||||
base.Write(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
namespace RazorWebSite
|
||||
{
|
||||
public class InjectedHelper
|
||||
{
|
||||
public string Greet(Person person)
|
||||
{
|
||||
return "Hello " + person.Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ namespace RazorWebSite
|
|||
{
|
||||
// Add MVC services to the services container
|
||||
services.AddMvc(configuration);
|
||||
services.AddTransient<InjectedHelper>();
|
||||
});
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@MyHelper.Greet(Model)
|
|
@ -0,0 +1 @@
|
|||
layout:@RenderBody()
|
|
@ -0,0 +1,4 @@
|
|||
@inherits MyBasePage<TModel>
|
||||
@{
|
||||
Layout = "/Views/Directives/Scoped/_Layout.cshtml";
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@model MyPerson
|
||||
@MyHelper.Greet(Model)
|
|
@ -0,0 +1,2 @@
|
|||
@using MyPerson = RazorWebSite.Person
|
||||
@inject InjectedHelper MyHelper
|
|
@ -1,2 +1,2 @@
|
|||
@model RazorWebSite.Address
|
||||
@model Address
|
||||
@ViewData.Model.ZipCode
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@using RazorWebSite
|
Загрузка…
Ссылка в новой задаче