diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs
new file mode 100644
index 0000000000..95879e921d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs
@@ -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
+{
+ ///
+ /// Contains helper methods for dealing with Chunks
+ ///
+ public static class ChunkHelper
+ {
+ private const string TModelToken = "";
+
+ ///
+ /// Attempts to cast the passed in to type and throws if the
+ /// cast fails.
+ ///
+ /// The type to cast to.
+ /// The chunk to cast.
+ /// The cast to .
+ /// is not an instance of
+ /// .
+ public static TChunk EnsureChunk([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;
+ }
+
+ ///
+ /// Returns the used to determine the model name for the page generated
+ /// using the specified
+ ///
+ /// The to scan for s in.
+ /// The last in the if found, null otherwise.
+ ///
+ public static ModelChunk GetModelChunk([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()
+ .LastOrDefault();
+ }
+
+ ///
+ /// Returns the type name of the Model specified via a in the
+ /// if specified or the default model type.
+ ///
+ /// The to scan for s in.
+ /// The name of the default model.
+ /// The model type name for the generated page.
+ public static string GetModelTypeName([NotNull] CodeTree codeTree,
+ [NotNull] string defaultModelName)
+ {
+ var modelChunk = GetModelChunk(codeTree);
+ return modelChunk != null ? modelChunk.ModelType : defaultModelName;
+ }
+
+ ///
+ /// Returns a string with the <TModel> token replaced with the value specified in
+ /// .
+ ///
+ /// The string to replace the token in.
+ /// The model name to replace with.
+ /// A string with the token replaced.
+ public static string ReplaceTModel([NotNull] string value,
+ [NotNull] string modelName)
+ {
+ return value.Replace(TModelToken, modelName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs
new file mode 100644
index 0000000000..20c24d57c5
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs
@@ -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
+{
+ ///
+ /// A utility type for supporting inheritance of chunks into a page from _ViewStart pages that apply to it.
+ ///
+ public class ChunkInheritanceUtility
+ {
+ private readonly IReadOnlyList _defaultInheritedChunks;
+
+ ///
+ /// Instantiates a new instance of .
+ ///
+ /// The instance to add s to.
+ /// The list of s inherited by default.
+ /// The model type used in the event no model is specified via the
+ /// @model keyword.
+ public ChunkInheritanceUtility([NotNull] CodeTree codeTree,
+ [NotNull] IReadOnlyList defaultInheritedChunks,
+ [NotNull] string defaultModel)
+ {
+ CodeTree = codeTree;
+ _defaultInheritedChunks = defaultInheritedChunks;
+ ChunkMergers = GetMergerMappings(codeTree, defaultModel);
+ }
+
+ ///
+ /// Gets the CodeTree to add inherited instances to.
+ ///
+ public CodeTree CodeTree { get; private set; }
+
+ ///
+ /// Gets a dictionary mapping type to the used to merge
+ /// chunks of that type.
+ ///
+ public IDictionary ChunkMergers { get; private set; }
+
+ ///
+ /// 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.
+ ///
+ /// The used to parse _ViewStart pages.
+ /// The filesystem that represents the application.
+ /// The root of the application.
+ /// The path of the page to locate inherited chunks for.
+ /// A list of chunks that are applicable to the given page.
+ public List GetInheritedChunks([NotNull] MvcRazorHost razorHost,
+ [NotNull] IFileSystem fileSystem,
+ [NotNull] string appRoot,
+ [NotNull] string pagePath)
+ {
+ var inheritedChunks = new List();
+
+ 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;
+ }
+
+ ///
+ /// Merges a list of chunks into the instance.
+ ///
+ /// The list of chunks to merge.
+ public void MergeInheritedChunks(List 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 GetMergerMappings(CodeTree codeTree, string defaultModel)
+ {
+ var modelType = ChunkHelper.GetModelTypeName(codeTree, defaultModel);
+ return new Dictionary
+ {
+ { 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;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/IChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/IChunkMerger.cs
new file mode 100644
index 0000000000..d0cc4d6517
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/IChunkMerger.cs
@@ -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
+{
+ ///
+ /// Defines the contract for merging instances from _ViewStart files.
+ ///
+ public interface IChunkMerger
+ {
+ ///
+ /// Visits a from the to merge into.
+ ///
+ /// A from the tree.
+ void VisitChunk(Chunk chunk);
+
+ ///
+ /// Merges an inherited into the .
+ ///
+ /// The to merge into.
+ /// The to merge.
+ void Merge(CodeTree codeTree, Chunk chunk);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs
new file mode 100644
index 0000000000..5f93ca8233
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class InjectChunkMerger : IChunkMerger
+ {
+ private readonly HashSet _addedMemberNames = new HashSet(StringComparer.Ordinal);
+ private string _modelType;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The model type to be used to replace <TModel> tokens.
+ public InjectChunkMerger([NotNull] string modelType)
+ {
+ _modelType = '<' + modelType + '>';
+ }
+
+ ///
+ public void VisitChunk([NotNull] Chunk chunk)
+ {
+ var injectChunk = ChunkHelper.EnsureChunk(chunk);
+ injectChunk.TypeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
+ _addedMemberNames.Add(injectChunk.MemberName);
+ }
+
+ ///
+ public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
+ {
+ var injectChunk = ChunkHelper.EnsureChunk(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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs
new file mode 100644
index 0000000000..72723c8866
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class SetBaseTypeChunkMerger : IChunkMerger
+ {
+ private readonly string _modelType;
+ private bool _isBaseTypeSet;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The type name of the model used by default.
+ public SetBaseTypeChunkMerger(string modelType)
+ {
+ _modelType = '<' + modelType + '>';
+ }
+
+ ///
+ public void VisitChunk([NotNull] Chunk chunk)
+ {
+ var setBaseTypeChunk = ChunkHelper.EnsureChunk(chunk);
+ setBaseTypeChunk.TypeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
+ _isBaseTypeSet = true;
+ }
+
+ ///
+ public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
+ {
+ if (!_isBaseTypeSet)
+ {
+ var baseTypeChunk = ChunkHelper.EnsureChunk(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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/UsingChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/UsingChunkMerger.cs
new file mode 100644
index 0000000000..091cbf04d1
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/UsingChunkMerger.cs
@@ -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
+{
+ ///
+ /// A that merges instances.
+ ///
+ public class UsingChunkMerger : IChunkMerger
+ {
+ private readonly HashSet _currentUsings = new HashSet(StringComparer.Ordinal);
+
+ ///
+ public void VisitChunk([NotNull] Chunk chunk)
+ {
+ var namespaceChunk = ChunkHelper.EnsureChunk(chunk);
+ _currentUsings.Add(namespaceChunk.Namespace);
+ }
+
+ ///
+ public void Merge([NotNull] CodeTree codeTree, [NotNull] Chunk chunk)
+ {
+ var namespaceChunk = ChunkHelper.EnsureChunk(chunk);
+
+ if (!_currentUsings.Contains(namespaceChunk.Namespace))
+ {
+ _currentUsings.Add(namespaceChunk.Namespace);
+ codeTree.Chunks.Add(namespaceChunk);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs
index c58b5f7a8b..6300589a01 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs
@@ -10,8 +10,8 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// Represents the chunk for an @inject statement.
///
- /// The type of object that would be injected
- /// The member name the field is exposed to the page as.
+ /// The type name of the property to be injected
+ /// The member name of the property to be injected.
public InjectChunk(string typeName,
string propertyName)
{
@@ -19,8 +19,14 @@ namespace Microsoft.AspNet.Mvc.Razor
MemberName = propertyName;
}
- public string TypeName { get; private set; }
+ ///
+ /// Gets or sets the type name of the property to be injected.
+ ///
+ public string TypeName { get; set; }
- public string MemberName { get; private set; }
+ ///
+ /// Gets or sets the name of the property to be injected.
+ ///
+ public string MemberName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
index 2c1cbe5e7b..87b6a6f90b 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
@@ -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()
- .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();
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
index 60a96ab33a..522b4b687c 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
@@ -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", "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.
// This field holds the type name without the generic decoration (MyBaseType)
private readonly string _baseType;
- public MvcRazorHost(Type baseType)
- : this(baseType.FullName)
+ ///
+ /// Initializes a new instance of with the specified
+ /// .
+ ///
+ /// Contains information about the executing application.
+ public MvcRazorHost(IApplicationEnvironment appEnvironment)
+ : this(appEnvironment.ApplicationBasePath,
+ new PhysicalFileSystem(appEnvironment.ApplicationBasePath))
{
}
- public MvcRazorHost(string baseType)
+ ///
+ /// Initializes a new instance of at the specified application root
+ /// and .
+ ///
+ /// The base path of the application.
+ ///
+ /// A rooted at the .
+ ///
+ 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
}
}
+ ///
+ /// Gets the model type used by default when no model is specified.
+ ///
+ /// This value is used as the generic type argument for the base type
+ public virtual string DefaultModel
+ {
+ get { return "dynamic"; }
+ }
+
+ ///
+ /// Gets the list of chunks that are injected by default by this host.
+ ///
+ public virtual IReadOnlyList DefaultInheritedChunks
+ {
+ get { return _defaultInheritedChunks; }
+ }
+
+ ///
+ /// Gets or sets the name attribute that is used to decorate properties that are injected and need to be
+ /// activated.
+ ///
+ public virtual string ActivateAttribute
+ {
+ get { return "Microsoft.AspNet.Mvc.ActivateAttribute"; }
+ }
+
+ ///
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)
+ ///
+ public override ParserBase DecorateCodeParser([NotNull] ParserBase incomingCodeParser)
{
return new MvcRazorCodeParser(_baseType);
}
- public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeGeneratorContext context)
+ ///
+ 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(currentChunks.OfType()
- .Select(c => c.MemberName),
- StringComparer.Ordinal);
-
- var modelChunk = currentChunks.OfType()
- .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("", 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);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHostOptions.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHostOptions.cs
deleted file mode 100644
index b95000e62f..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHostOptions.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Represents configuration options for the Razor Host
- ///
- public class MvcRazorHostOptions
- {
- public MvcRazorHostOptions()
- {
- DefaultModel = "dynamic";
- ActivateAttributeName = "Microsoft.AspNet.Mvc.ActivateAttribute";
- DefaultInjectedProperties = new List()
- {
- new InjectDescriptor("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper", "Html"),
- new InjectDescriptor("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
- new InjectDescriptor("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
- };
- }
-
- ///
- /// Gets or sets the model that is used by default for generated views
- /// when no model is explicily specified. Defaults to dynamic.
- ///
- public string DefaultModel { get; set; }
-
- ///
- /// Gets or sets the attribue that is used to decorate properties that are injected and need to
- /// be activated.
- ///
- public string ActivateAttributeName { get; set; }
-
- ///
- /// Gets the list of properties that are injected by default.
- ///
- public IList DefaultInjectedProperties { get; private set; }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..d6cecb56ab
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs
index 4f2663c2b8..b1aa28a735 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs
@@ -26,6 +26,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
return GetString("ArgumentCannotBeNullOrEmpy");
}
+ ///
+ /// Argument must be an instance of type '{0}'.
+ ///
+ internal static string ArgumentMustBeOfType
+ {
+ get { return GetString("ArgumentMustBeOfType"); }
+ }
+
+ ///
+ /// Argument must be an instance of type '{0}'.
+ ///
+ internal static string FormatArgumentMustBeOfType(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeOfType"), p0);
+ }
+
///
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
index cfa565507e..460a42f1cb 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
@@ -120,6 +120,9 @@
Argument cannot be null or empty.
+
+ Argument must be an instance of '{0}'.
+
The 'inherits' keyword is not allowed when a '{0}' keyword is used.
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/ViewStartUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/ViewStartUtility.cs
new file mode 100644
index 0000000000..4e2dda0294
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/ViewStartUtility.cs
@@ -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";
+
+ ///
+ /// Determines if the given path represents a view start file.
+ ///
+ /// The path to inspect.
+ /// True if the path is a view start file, false otherwise.
+ public static bool IsViewStart([NotNull] string path)
+ {
+ var fileName = Path.GetFileName(path);
+ return string.Equals(ViewStartFileName, fileName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Gets the view start locations that are applicable to the specified path.
+ ///
+ /// The base of the application.
+ /// The path to locate view starts for.
+ /// A sequence of paths that represent potential view start locations.
+ ///
+ /// This method returns paths starting from the directory of and moves
+ /// upwards until it hits the application root.
+ /// e.g.
+ /// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
+ ///
+ public static IEnumerable GetViewStartLocations(string applicationBase, string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return Enumerable.Empty();
+ }
+
+ applicationBase = TrimTrailingSlash(applicationBase);
+ var viewStartLocations = new List();
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/project.json b/src/Microsoft.AspNet.Mvc.Razor.Host/project.json
index a687c520db..7961146f73 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/project.json
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/project.json
@@ -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": {
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index 73490d4744..2c3c55990e 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -99,11 +99,26 @@ namespace Microsoft.AspNet.Mvc.Razor
///
public abstract Task ExecuteAsync();
+ ///
+ /// Writes the specified with HTML encoding to .
+ ///
+ /// The to write.
public virtual void Write(object value)
{
WriteTo(Output, value);
}
+ ///
+ /// Writes the specified with HTML encoding to .
+ ///
+ /// The instance to write to.
+ /// The to write.
+ ///
+ /// s of type are written without encoding and the
+ /// is invoked for types.
+ /// For all other types, the encoded result of is written to the
+ /// .
+ ///
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)
+ ///
+ /// Writes the specified without HTML encoding to .
+ ///
+ /// The to write.
+ public virtual void WriteLiteral(object value)
{
WriteLiteralTo(Output, value);
}
+ ///
+ /// Writes the specified without HTML encoding to the .
+ ///
+ /// The instance to write to.
+ /// The to write.
public virtual void WriteLiteralTo(TextWriter writer, object text)
{
if (text != null)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs
index 2f43fd10e3..215cce468e 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs
@@ -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
///
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;
}
///
public IEnumerable 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 GetViewStartLocations(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- return Enumerable.Empty();
- }
-
- var viewStartLocations = new List();
- 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;
- }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index 195f331a5e..beeb20bc60 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -42,7 +42,8 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient();
yield return describe.Transient();
- yield return describe.Instance(new MvcRazorHost(typeof(RazorPage).FullName));
+ // The host is designed to be discarded after consumption and is very inexpensive to initialize.
+ yield return describe.Transient();
yield return describe.Singleton();
yield return describe.Singleton();
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/DirectivesTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/DirectivesTest.cs
new file mode 100644
index 0000000000..3b4c20a562
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/DirectivesTest.cs
@@ -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 _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());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs
new file mode 100644
index 0000000000..38f7e19829
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs
@@ -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 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(chunks[0]);
+ Assert.Equal("MyNamespace", usingChunk.Namespace);
+
+ var injectChunk = Assert.IsType(chunks[1]);
+ Assert.Equal("MyHelper", injectChunk.TypeName);
+ Assert.Equal("Helper", injectChunk.MemberName);
+
+ var setBaseTypeChunk = Assert.IsType(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 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(chunks[0]);
+ Assert.Equal("DifferentHelper", injectChunk.TypeName);
+ Assert.Equal("Html", injectChunk.MemberName);
+
+ var usingChunk = Assert.IsType(chunks[1]);
+ Assert.Equal("AppNamespace.Models", usingChunk.Namespace);
+
+ injectChunk = Assert.IsType(chunks[2]);
+ Assert.Equal("MyTestHtmlHelper", injectChunk.TypeName);
+ Assert.Equal("Html", injectChunk.MemberName);
+
+ usingChunk = Assert.IsType(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();
+ var mockSequence = new MockSequence();
+ merger.InSequence(mockSequence)
+ .Setup(m => m.VisitChunk(It.IsAny()))
+ .Verifiable();
+ merger.InSequence(mockSequence)
+ .Setup(m => m.Merge(codeTree, It.IsAny()))
+ .Verifiable();
+ var inheritedChunks = new List
+ {
+ new CodeAttributeChunk(),
+ new LiteralChunk()
+ };
+ var utility = new ChunkInheritanceUtility(codeTree, inheritedChunks, "dynamic");
+
+ // Act
+ utility.ChunkMergers[typeof(LiteralChunk)] = merger.Object;
+ utility.MergeInheritedChunks(inheritedChunks);
+
+ // Assert
+ merger.Verify();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/InjectChunkMergerTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/InjectChunkMergerTest.cs
new file mode 100644
index 0000000000..a7a4a14ea1
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/InjectChunkMergerTest.cs
@@ -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", "MyApp.TestHelper")]
+ [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(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(codeTree.Chunks[0]);
+ Assert.Equal("MyType", injectChunk.TypeName);
+ Assert.Equal("myproperty", injectChunk.MemberName);
+
+ injectChunk = Assert.IsType(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", "MyProperty"));
+
+ // Assert
+ var chunk = Assert.Single(codeTree.Chunks);
+ var injectChunk = Assert.IsType(chunk);
+ Assert.Equal("MyHelper", 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", "MyProperty"));
+
+ // Assert
+ var chunk = Assert.Single(codeTree.Chunks);
+ var injectChunk = Assert.IsType(chunk);
+ Assert.Equal("MyHelper", 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(chunk);
+ Assert.Equal("SomeType", injectChunk.TypeName);
+ Assert.Equal("Property", injectChunk.MemberName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/SetBaseTypeChunkMergerTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/SetBaseTypeChunkMergerTest.cs
new file mode 100644
index 0000000000..62a3dd5b0b
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/SetBaseTypeChunkMergerTest.cs
@@ -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", "MyApp.BaseType")]
+ [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(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(chunk);
+ Assert.Equal("MyBase1", setBaseTypeChunk.TypeName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/UsingChunkMergerTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/UsingChunkMergerTest.cs
new file mode 100644
index 0000000000..2aa51c0c8b
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/UsingChunkMergerTest.cs
@@ -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(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(codeTree.Chunks[0]);
+ Assert.Equal("Microsoft.AspNet.Mvc", chunk.Namespace);
+ chunk = Assert.IsType(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(codeTree.Chunks[0]);
+ Assert.Equal("Microsoft.AspNet.Mvc", chunk.Namespace);
+ chunk = Assert.IsType(codeTree.Chunks[1]);
+ Assert.Equal("Microsoft.AspNet.mvc", chunk.Namespace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
index 718069b57c..69632a5b58 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
@@ -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())
{
DesignTimeMode = true
};
@@ -124,8 +126,8 @@ MyType2 @MyPropertyName2
var expectedCode = ReadResource("TestFiles/Output/Inject.cs");
var expectedLineMappings = new List
{
- 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())
{
DesignTimeMode = true
};
@@ -156,9 +158,9 @@ MyType2 @MyPropertyName2
var expectedCode = ReadResource("TestFiles/Output/InjectWithModel.cs");
var expectedLineMappings = new List
{
- 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()),
"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(
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs
index e1fe844840..02b8f1b646 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs
@@ -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())
{
DesignTimeMode = true
};
@@ -116,7 +118,7 @@ Environment.NewLine +
var expectedCode = ReadResource("TestFiles/Output/Model.cs");
var expectedLineMappings = new List
{
- 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()),
"MyClass",
"MyNamespace",
string.Empty,
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs
new file mode 100644
index 0000000000..68b2a58644
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs
@@ -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 _lookup =
+ new Dictionary(StringComparer.Ordinal);
+
+ public bool TryGetDirectoryContents(string subpath, out IEnumerable contents)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void AddFile(string path, string contents)
+ {
+ var fileInfo = new Mock();
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Inject.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Inject.cs
index af51698d3c..f8a947a10c 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Inject.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Inject.cs
@@ -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
+ public class __CompiledTemplate : Microsoft.AspNet.Mvc.Razor.RazorPage
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/InjectWithModel.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/InjectWithModel.cs
index 81df5c49e7..cb3fd992d6 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/InjectWithModel.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/InjectWithModel.cs
@@ -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 Html
+ MyService Html
#line default
#line hidden
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs
index 523b36e68f..2cc67b2a6b 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs
@@ -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
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/ViewStartProviderTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs
similarity index 60%
rename from test/Microsoft.AspNet.Mvc.Razor.Test/ViewStartProviderTest.cs
rename to test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs
index 8ccfbdfec9..9ce7fe3f34 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/ViewStartProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs
@@ -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());
// 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 expected)
{
- // Arrange
- var provider = new ViewStartProvider(GetAppEnv(appPath), Mock.Of());
-
// 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();
- appEnv.Setup(p => p.ApplicationBasePath)
- .Returns(appPath);
- return appEnv.Object;
- }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json
index 9289402488..95907e4383 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json
@@ -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": {
diff --git a/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs b/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs
new file mode 100644
index 0000000000..33d85cd3e9
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs
@@ -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" });
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/MyBasePage.cs b/test/WebSites/RazorWebSite/MyBasePage.cs
new file mode 100644
index 0000000000..07039b6caa
--- /dev/null
+++ b/test/WebSites/RazorWebSite/MyBasePage.cs
@@ -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 : RazorPage
+ {
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Services/InjectedHelper.cs b/test/WebSites/RazorWebSite/Services/InjectedHelper.cs
new file mode 100644
index 0000000000..fe98263aa7
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Services/InjectedHelper.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs
index 015d25f562..951a918a2c 100644
--- a/test/WebSites/RazorWebSite/Startup.cs
+++ b/test/WebSites/RazorWebSite/Startup.cs
@@ -14,6 +14,7 @@ namespace RazorWebSite
{
// Add MVC services to the services container
services.AddMvc(configuration);
+ services.AddTransient();
});
// Add MVC to the request pipeline
diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewStarts.cshtml b/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewStarts.cshtml
new file mode 100644
index 0000000000..cdb1b35b31
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewStarts.cshtml
@@ -0,0 +1 @@
+@MyHelper.Greet(Model)
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml b/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml
new file mode 100644
index 0000000000..7e6b5f6553
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml
@@ -0,0 +1 @@
+layout:@RenderBody()
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml b/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml
new file mode 100644
index 0000000000..4867deaa3a
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml
@@ -0,0 +1,4 @@
+@inherits MyBasePage
+@{
+ Layout = "/Views/Directives/Scoped/_Layout.cshtml";
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewStarts.cshtml b/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewStarts.cshtml
new file mode 100644
index 0000000000..e1a2a06716
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewStarts.cshtml
@@ -0,0 +1,2 @@
+@model MyPerson
+@MyHelper.Greet(Model)
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/Directives/_ViewStart.cshtml b/test/WebSites/RazorWebSite/Views/Directives/_ViewStart.cshtml
new file mode 100644
index 0000000000..68a051a32f
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/Directives/_ViewStart.cshtml
@@ -0,0 +1,2 @@
+@using MyPerson = RazorWebSite.Person
+@inject InjectedHelper MyHelper
diff --git a/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml
index 515b4a9995..755e2958b0 100644
--- a/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml
+++ b/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml
@@ -1,2 +1,2 @@
-@model RazorWebSite.Address
+@model Address
@ViewData.Model.ZipCode
diff --git a/test/WebSites/RazorWebSite/Views/_ViewStart.cshtml b/test/WebSites/RazorWebSite/Views/_ViewStart.cshtml
new file mode 100644
index 0000000000..44ab541732
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/_ViewStart.cshtml
@@ -0,0 +1 @@
+@using RazorWebSite
\ No newline at end of file