Adding support for @Inject to Mvc

This commit is contained in:
Pranav K 2014-05-08 15:21:45 -07:00
parent c98bc503e8
commit 59e419ba0a
29 changed files with 1576 additions and 19 deletions

13
Mvc.sln
View File

@ -29,6 +29,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSample.Web", "samples\Mv
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Host", "src\Microsoft.AspNet.Mvc.Razor.Host\Microsoft.AspNet.Mvc.Razor.Host.kproj", "{520B3AA4-363A-497C-8C15-80423C5AFC85}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Host.Test", "test\Microsoft.AspNet.Mvc.Razor.Host.Test\Microsoft.AspNet.Mvc.Razor.Host.Test.kproj", "{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{16703B76-C9F7-4C75-AE6C-53D92E308E3C}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.FunctionalTests", "test\Microsoft.AspNet.Mvc.FunctionalTests\Microsoft.AspNet.Mvc.FunctionalTests.kproj", "{323D0C04-B518-4A8F-8A8E-3546AD153D34}"
@ -145,6 +147,16 @@ Global
{520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|x86.ActiveCfg = Release|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|x86.ActiveCfg = Debug|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Any CPU.Build.0 = Release|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|x86.ActiveCfg = Release|Any CPU
{323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -180,6 +192,7 @@ Global
{A8AA326E-8EE8-4F11-B750-23028E0949D7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{FBB2B86E-972B-4185-9FF2-62CAB5F8388F} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
{520B3AA4-363A-497C-8C15-80423C5AFC85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{16703B76-C9F7-4C75-AE6C-53D92E308E3C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{323D0C04-B518-4A8F-8A8E-3546AD153D34} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{34DF1487-12C6-476C-BE0A-F31DF1939AE5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}

View File

@ -23,6 +23,11 @@ namespace MvcSample.Web
return View("ValidationSummary");
}
public ActionResult InjectSample()
{
return View();
}
/// <summary>
/// Action that shows metadata when model is <c>null</c>.
/// </summary>

View File

@ -28,6 +28,7 @@
<Content Include="project.json" />
<Content Include="Views\Home\Create.cshtml" />
<Content Include="Views\Home\EditorTemplates\IEnumerable`1.cshtml" />
<Content Include="Views\Home\InjectSample.cshtml" />
<Content Include="Views\Home\Test.cshtml" />
<Content Include="Views\Home\ValidationSummary.cshtml" />
<Content Include="Views\Link\Details.cshtml" />
@ -60,6 +61,7 @@
<Compile Include="LinkController.cs" />
<Compile Include="Models\User.cs" />
<Compile Include="OverloadController.cs" />
<Compile Include="Services\TestService.cs" />
<Compile Include="SimplePocoController.cs" />
<Compile Include="SimpleRest.cs" />
<Compile Include="Startup.cs" />

View File

@ -0,0 +1,21 @@
// 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;
namespace MvcSample.Web.Services
{
public interface ITestService
{
string GetFoo();
}
public class TestService : ITestService
{
public string GetFoo()
{
return "Hello world " + DateTime.UtcNow;
}
}
}

View File

@ -4,6 +4,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using MvcSample.Web.Filters;
using MvcSample.Web.Services;
namespace MvcSample.Web
{
@ -16,6 +17,7 @@ namespace MvcSample.Web
services.AddMvc();
services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>();
services.AddTransient<ITestService, TestService>();
});
app.UseMvc(routes =>

View File

@ -0,0 +1,7 @@
@using MvcSample.Web.Services
@inject MvcSample.Web.Services.ITestService MyService
@inject ITestService MyService2
@MyService.GetFoo()
@MyService2.GetFoo()

View File

@ -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
{
public class InjectChunk : Chunk
{
/// <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>
public InjectChunk(string typeName,
string propertyName)
{
TypeName = typeName;
MemberName = propertyName;
}
public string TypeName { get; private set; }
public string MemberName { get; private set; }
}
}

View File

@ -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.Collections.Generic;
using System.Globalization;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
namespace Microsoft.AspNet.Mvc.Razor
{
public class InjectChunkVisitor : MvcCSharpCodeVisitor
{
private readonly List<InjectChunk> _injectChunks = new List<InjectChunk>();
public InjectChunkVisitor([NotNull] CSharpCodeWriter writer,
[NotNull] CodeGeneratorContext context)
: base(writer, context)
{
}
public List<InjectChunk> InjectChunks
{
get { return _injectChunks; }
}
protected override void Visit([NotNull] InjectChunk chunk)
{
if (Context.Host.DesignTimeMode)
{
Writer.WriteLine("public");
var code = string.Format(CultureInfo.InvariantCulture,
"{0} {1}",
chunk.TypeName,
chunk.MemberName);
var csharpVisitor = new CSharpCodeVisitor(Writer, Context);
csharpVisitor.CreateExpressionCodeMapping(code, chunk);
Writer.WriteLine("{ get; private set; }");
}
else
{
Writer.Write("public ")
.Write(chunk.TypeName)
.Write(" ")
.Write(chunk.MemberName)
.WriteLine(" { get; private set; }");
}
_injectChunks.Add(chunk);
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Mvc.Razor
{
public class InjectParameterGenerator : SpanCodeGenerator
{
public InjectParameterGenerator(string typeName, string propertyName)
{
TypeName = typeName;
PropertyName = propertyName;
}
public string TypeName { get; private set; }
public string PropertyName { get; private set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
var injectChunk = new InjectChunk(TypeName, PropertyName);
context.CodeTreeBuilder.AddChunk(injectChunk, target);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "@inject {0} {1}", TypeName, PropertyName);
}
public override bool Equals(object obj)
{
var other = obj as InjectParameterGenerator;
return other != null &&
string.Equals(TypeName, other.TypeName, StringComparison.Ordinal) &&
string.Equals(PropertyName, other.PropertyName, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return TypeName.GetHashCode() +
(PropertyName.GetHashCode() * 13);
}
}
}

View File

@ -22,6 +22,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="IMvcRazorHost.cs" />
<Compile Include="InjectChunk.cs" />
<Compile Include="InjectChunkVisitor.cs" />
<Compile Include="MvcCSharpCodeBuilder.cs" />
<Compile Include="MvcCSharpCodeVistor.cs" />
<Compile Include="InjectParameterGenerator.cs" />
<Compile Include="MvcRazorCodeParser.cs" />
<Compile Include="MvcRazorHost.cs" />
<Compile Include="Properties\Resources.Designer.cs" />

View File

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcCSharpCodeBuilder : CSharpCodeBuilder
{
public MvcCSharpCodeBuilder([NotNull] CodeGeneratorContext context)
: base(context)
{
}
protected override void BuildConstructor([NotNull] CSharpCodeWriter writer)
{
writer.WriteLineHiddenDirective();
var injectVisitor = new InjectChunkVisitor(writer, Context);
injectVisitor.Accept(Context.CodeTreeBuilder.CodeTree.Chunks);
writer.WriteLine();
writer.WriteLineHiddenDirective();
var arguments = injectVisitor.InjectChunks
.Select(chunk => new KeyValuePair<string, string>(chunk.TypeName,
chunk.MemberName));
using (writer.BuildConstructor("public", Context.ClassName, arguments))
{
foreach (var inject in injectVisitor.InjectChunks)
{
writer.WriteStartAssignment("this." + inject.MemberName)
.Write(inject.MemberName)
.WriteLine(";");
}
}
}
}
}

View File

@ -0,0 +1,32 @@
// 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;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
namespace Microsoft.AspNet.Mvc.Razor
{
public abstract class MvcCSharpCodeVisitor : CodeVisitor<CSharpCodeWriter>
{
public MvcCSharpCodeVisitor([NotNull] CSharpCodeWriter writer,
[NotNull] CodeGeneratorContext context)
: base(writer, context)
{
}
public override void Accept(Chunk chunk)
{
if (chunk is InjectChunk)
{
Visit((InjectChunk)chunk);
}
else
{
base.Accept(chunk);
}
}
protected abstract void Visit(InjectChunk chunk);
}
}

View File

@ -1,12 +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.
using System;
using System.Globalization;
using Microsoft.AspNet.Mvc.Razor.Host;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Mvc.Razor.Host;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -14,6 +15,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private const string GenericTypeFormat = "{0}<{1}>";
private const string ModelKeyword = "model";
private const string InjectKeyword = "inject";
private readonly string _baseType;
private SourceLocation? _endInheritsLocation;
private bool _modelStatementFound;
@ -22,6 +24,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
_baseType = baseType;
MapDirectives(ModelDirective, ModelKeyword);
MapDirectives(InjectDirective, InjectKeyword);
}
protected override void InheritsDirective()
@ -39,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
if (_modelStatementFound && _endInheritsLocation.HasValue)
{
Context.OnError(_endInheritsLocation.Value,
Context.OnError(_endInheritsLocation.Value,
Resources.FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(ModelKeyword));
}
}
@ -50,14 +53,14 @@ namespace Microsoft.AspNet.Mvc.Razor
AssertDirective(ModelKeyword);
AcceptAndMoveNext();
SourceLocation endModelLocation = CurrentLocation;
var endModelLocation = CurrentLocation;
BaseTypeDirective(Resources.FormatMvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(ModelKeyword),
BaseTypeDirective(Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(ModelKeyword),
CreateModelCodeGenerator);
if (_modelStatementFound)
{
Context.OnError(endModelLocation,
Context.OnError(endModelLocation,
Resources.FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(ModelKeyword));
}
@ -66,13 +69,80 @@ namespace Microsoft.AspNet.Mvc.Razor
CheckForInheritsAndModelStatements();
}
protected virtual void InjectDirective()
{
// @inject MyApp.MyService MyServicePropertyName
AssertDirective(InjectKeyword);
AcceptAndMoveNext();
Context.CurrentBlock.Type = BlockType.Directive;
// Accept whitespace
var remainingWs = AcceptSingleWhiteSpaceCharacter();
if (Span.Symbols.Count > 1)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Output(SpanKind.MetaCode);
if (remainingWs != null)
{
Accept(remainingWs);
}
// Consume any other whitespace tokens.
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
var hasTypeError = !At(CSharpSymbolType.Identifier);
if (hasTypeError)
{
Context.OnError(CurrentLocation,
Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(InjectKeyword));
}
// Accept 'MyApp.MyService'
NamespaceOrTypeName();
// typeName now contains the token 'MyApp.MyService'
var typeName = Span.GetContent().Value;
var propertyStartLocation = CurrentLocation;
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
if (!hasTypeError && At(CSharpSymbolType.NewLine))
{
// Add an error for the property name only if we successfully read the type name
Context.OnError(propertyStartLocation,
Resources.FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(InjectKeyword));
}
// Read until end of line. Span now contains 'MyApp.MyService MyServiceName'.
AcceptUntil(CSharpSymbolType.NewLine);
if (!Context.DesignTimeMode)
{
// We want the newline to be treated as code, but it causes issues at design-time.
Optional(CSharpSymbolType.NewLine);
}
// Parse out 'MyServicePropertyName' from the Span.
var propertyName = Span.GetContent()
.Value
.Substring(typeName.Length);
Span.CodeGenerator = new InjectParameterGenerator(typeName.Trim(),
propertyName.Trim());
// Output the span and finish the block
Output(SpanKind.Code);
}
private SpanCodeGenerator CreateModelCodeGenerator(string model)
{
// In the event we have an empty model, the name we generate does not matter since it's a parser error.
// We'll use the non-generic version of the base type.
string baseType = String.IsNullOrEmpty(model) ?
_baseType :
String.Format(CultureInfo.InvariantCulture, GenericTypeFormat, _baseType, model);
var baseType = string.IsNullOrEmpty(model) ?
_baseType :
string.Format(CultureInfo.InvariantCulture, GenericTypeFormat, _baseType, model);
return new SetBaseTypeCodeGenerator(baseType);
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.IO;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser;
namespace Microsoft.AspNet.Mvc.Razor
@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
{
string className = ParserHelpers.SanitizeClassName(rootRelativePath);
var className = ParserHelpers.SanitizeClassName(rootRelativePath);
using (var reader = new StreamReader(inputStream))
{
var engine = new RazorTemplateEngine(this);
@ -69,5 +70,10 @@ namespace Microsoft.AspNet.Mvc.Razor
{
return new MvcRazorCodeParser(_baseType);
}
public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeGeneratorContext context)
{
return new MvcCSharpCodeBuilder(context);
}
}
}

View File

@ -27,19 +27,35 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
}
/// <summary>
/// The '{0}' keyword must be followed by a type name on the same line.
/// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} &lt;TypeName&gt; &lt;PropertyName&gt;'.
/// </summary>
internal static string MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName
internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired
{
get { return GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"); }
get { return GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"); }
}
/// <summary>
/// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} &lt;TypeName&gt; &lt;PropertyName&gt;'.
/// </summary>
internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0);
}
/// <summary>
/// The '{0}' keyword must be followed by a type name on the same line.
/// </summary>
internal static string FormatMvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(object p0)
internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName
{
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"), p0);
get { return GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"); }
}
/// <summary>
/// The '{0}' keyword must be followed by a type name on the same line.
/// </summary>
internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0);
}
/// <summary>

View File

@ -120,7 +120,10 @@
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
</data>
<data name="MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName" xml:space="preserve">
<data name="MvcRazorCodeParser_InjectDirectivePropertyNameRequired" xml:space="preserve">
<value>A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} &lt;Type Name&gt; &lt;Property Name&gt;'.</value>
</data>
<data name="MvcRazorCodeParser_KeywordMustBeFollowedByTypeName" xml:space="preserve">
<value>The '{0}' keyword must be followed by a type name on the same line.</value>
</data>
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">

View File

@ -5,6 +5,7 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNet.FileSystems;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
@ -13,22 +14,28 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly PhysicalFileSystem _fileSystem;
private readonly IRazorCompilationService _compilationService;
private readonly ITypeActivator _activator;
private readonly IServiceProvider _serviceProvider;
public VirtualPathViewFactory(IApplicationEnvironment env,
IRazorCompilationService compilationService)
IRazorCompilationService compilationService,
ITypeActivator typeActivator,
IServiceProvider serviceProvider)
{
// TODO: Continue to inject the IFileSystem but only when we get it from the host
_fileSystem = new PhysicalFileSystem(env.ApplicationBasePath);
_compilationService = compilationService;
_activator = typeActivator;
_serviceProvider = serviceProvider;
}
public IView CreateInstance([NotNull]string virtualPath)
public IView CreateInstance([NotNull] string virtualPath)
{
IFileInfo fileInfo;
if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo))
{
CompilationResult result = _compilationService.Compile(fileInfo);
return (IView)Activator.CreateInstance(result.CompiledType);
return (IView)_activator.CreateInstance(_serviceProvider, result.CompiledType);
}
return null;

View File

@ -0,0 +1,181 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
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 Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class InjectChunkVisitorTest
{
[Fact]
public void Visit_IgnoresNonInjectChunks()
{
// Arrange
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context);
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new CodeAttributeChunk()
});
var code = writer.GenerateCode();
// Assert
Assert.Empty(code);
}
[Fact]
public void Visit_GeneratesProperties_ForInjectChunks()
{
// Arrange
var expected =
@"public MyType1 MyPropertyName1 { get; private set; }
public MyType2 @MyPropertyName2 { get; private set; }
";
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context);
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new InjectChunk("MyType1", "MyPropertyName1") { Association = node },
new InjectChunk("MyType2", "@MyPropertyName2") { Association = node }
});
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Fact]
public void Visit_WithDesignTimeHost_GeneratesPropertiesAndLinePragmas_ForInjectChunks()
{
// Arrange
var expected = @"public
#line 1 """"
MyType1 MyPropertyName1
#line default
#line hidden
{ get; private set; }
public
#line 1 """"
MyType2 @MyPropertyName2
#line default
#line hidden
{ get; private set; }
";
var writer = new CSharpCodeWriter();
var context = CreateContext();
context.Host.DesignTimeMode = true;
var visitor = new InjectChunkVisitor(writer, context);
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new InjectChunk("MyType1", "MyPropertyName1") { Association = node },
new InjectChunk("MyType2", "@MyPropertyName2") { Association = node }
});
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Fact]
public void InjectVisitor_GeneratesCorrectLineMappings()
{
// Arrange
var host = new MvcRazorHost("RazorView")
{
DesignTimeMode = true
};
host.NamespaceImports.Clear();
var engine = new RazorTemplateEngine(host);
var source = ReadResource("Inject.cshtml");
var expectedCode = ReadResource("Inject.cs");
var expectedLineMappings = new List<LineMapping>
{
BuildLineMapping(1, 0, 1, 32, 3, 0, 17),
BuildLineMapping(28, 1, 8, 442, 21, 8, 20)
};
// Act
GeneratorResults results;
using (var buffer = new StringTextBuffer(source))
{
results = engine.GenerateCode(buffer);
}
// Assert
Assert.True(results.Success);
Assert.Equal(expectedCode, results.GeneratedCode);
Assert.Empty(results.ParserErrors);
Assert.Equal(expectedLineMappings, results.DesignTimeLineMappings);
}
private string ReadResource(string resourceName)
{
var assembly = typeof(InjectChunkVisitorTest).Assembly;
using (var stream = assembly.GetManifestResourceStream(resourceName))
using (var streamReader = new StreamReader(stream))
{
return streamReader.ReadToEnd();
}
}
private static CodeGeneratorContext CreateContext()
{
return CodeGeneratorContext.Create(new MvcRazorHost("RazorView"),
"MyClass",
"MyNamespace",
string.Empty,
shouldGenerateLinePragmas: true);
}
private static LineMapping BuildLineMapping(int documentAbsoluteIndex,
int documentLineIndex,
int documentCharacterIndex,
int generatedAbsoluteIndex,
int generatedLineIndex,
int generatedCharacterIndex,
int contentLength)
{
var documentLocation = new SourceLocation(documentAbsoluteIndex,
documentLineIndex,
documentCharacterIndex);
var generatedLocation = new SourceLocation(generatedAbsoluteIndex,
generatedLineIndex,
generatedCharacterIndex);
return new LineMapping(
documentLocation: new MappingLocation(documentLocation, contentLength),
generatedLocation: new MappingLocation(generatedLocation, contentLength));
}
}
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>7c4f5973-0491-4028-b1dc-a9ba73ff9f77</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Project.json" />
<Content Include="TestFiles\Input\Inject.cshtml" />
</ItemGroup>
<ItemGroup>
<Compile Include="InjectChunkVisitorTest.cs" />
<Compile Include="MvcCSharpRazorCodeParserTest.cs" />
<Compile Include="SpanFactory\RawTextSymbol.cs" />
<Compile Include="SpanFactory\SpanConstructor.cs" />
<Compile Include="SpanFactory\SpanFactory.cs" />
<Compile Include="SpanFactory\SpanFactoryExtensions.cs" />
<Compile Include="SpanFactory\UnclassifiedSpanConstructor.cs" />
<Compile Include="StringTextBuffer.cs" />
<Compile Include="TestFiles\Output\Inject.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,406 @@
// 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;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcCSharpRazorCodeParserTest
{
[Theory]
[InlineData("model")]
[InlineData("inject")]
public void Constructor_AddsMvcSpecificKeywords(string keyword)
{
// Arrange
var parser = new TestMvcCSharpRazorCodeParser();
// Act
var hasDirective = parser.HasDirective(keyword);
// Assert
Assert.True(hasDirective);
}
[Fact]
public void ParseModelKeyword_HandlesSingleInstance()
{
// Arrange
var document = "@model Foo";
var factory = SpanFactory.CreateCsHtml();
var errors = new List<RazorError>();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code(" Foo")
.As(new SetBaseTypeCodeGenerator("RazorView<Foo>"))
};
// Act
var spans = ParseDocument(document, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Empty(errors);
}
[Theory]
[InlineData("Foo?", "RazorView<Foo?>")]
[InlineData("Foo[[]][]", "RazorView<Foo[[]][]>")]
[InlineData("$rootnamespace$.MyModel", "RazorView<$rootnamespace$.MyModel>")]
public void ParseModelKeyword_InfersBaseType_FromModelName(string modelName,
string expectedBaseType)
{
// Arrange
var documentContent = "@model " + modelName + Environment.NewLine + "Bar";
var factory = SpanFactory.CreateCsHtml();
var errors = new List<RazorError>();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code(modelName + "\r\n")
.As(new SetBaseTypeCodeGenerator(expectedBaseType)),
factory.Markup("Bar")
.With(new MarkupCodeGenerator())
};
// Act
var spans = ParseDocument(documentContent, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Empty(errors);
}
[Fact]
public void ParseModelKeyword_ErrorOnMissingModelType()
{
// Arrange + Act
var errors = new List<RazorError>();
var document = "@model ";
var spans = ParseDocument(document, errors);
// Assert
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code(" ")
.As(new SetBaseTypeCodeGenerator("RazorView")),
};
var expectedErrors = new[]
{
new RazorError("The 'model' keyword must be followed by a type name on the same line.",
new SourceLocation(9, 0, 9), 1)
};
Assert.Equal(expectedSpans, spans);
Assert.Equal(expectedErrors, errors);
}
[Fact]
public void ParseModelKeyword_ErrorOnMultipleModelStatements()
{
// Arrange + Act
var errors = new List<RazorError>();
var document =
"@model Foo" + Environment.NewLine
+ "@model Bar";
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code("Foo\r\n")
.As(new SetBaseTypeCodeGenerator("RazorView<Foo>")),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code("Bar")
.As(new SetBaseTypeCodeGenerator("RazorView<Bar>"))
};
var expectedErrors = new[]
{
new RazorError("Only one 'model' statement is allowed in a file.",
new SourceLocation(18, 1, 6), 1)
};
// Act
var spans = ParseDocument(document, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Equal(expectedErrors, errors);
}
[Fact]
public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
{
// Arrange
var errors = new List<RazorError>();
var document =
"@model Foo" + Environment.NewLine
+ "@inherits Bar";
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code("Foo\r\n")
.As(new SetBaseTypeCodeGenerator("RazorView<Foo>")),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inherits ")
.Accepts(AcceptedCharacters.None),
factory.Code("Bar")
.As(new SetBaseTypeCodeGenerator("Bar"))
};
var expectedErrors = new[]
{
new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.",
new SourceLocation(21, 1, 9), 1)
};
// Act
var spans = ParseDocument(document, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Equal(expectedErrors, errors);
}
[Fact]
public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
{
// Arrange
var errors = new List<RazorError>();
var document =
"@inherits Bar" + Environment.NewLine
+ "@model Foo";
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inherits ")
.Accepts(AcceptedCharacters.None),
factory.Code("Bar" + Environment.NewLine)
.As(new SetBaseTypeCodeGenerator("Bar")),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("model ")
.Accepts(AcceptedCharacters.None),
factory.Code("Foo")
.As(new SetBaseTypeCodeGenerator("RazorView<Foo>"))
};
var expectedErrors = new[]
{
new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.",
new SourceLocation(9, 0, 9), 1)
};
// Act
var spans = ParseDocument(document, errors);
// Assert
Assert.Equal(expectedSpans, spans.ToArray());
Assert.Equal(expectedErrors, errors.ToArray());
}
[Theory]
[InlineData("IMyService Service", "IMyService", "Service")]
[InlineData(" Microsoft.AspNet.Mvc.IHtmlHelper<MyNullableModel[]?> MyHelper ",
"Microsoft.AspNet.Mvc.IHtmlHelper<MyNullableModel[]?>", "MyHelper")]
[InlineData(" TestService @class ", "TestService", "@class")]
public void ParseInjectKeyword_InfersTypeAndPropertyName(string injectStatement,
string expectedService,
string expectedPropertyName)
{
// Arrange
var documentContent = "@inject " + injectStatement;
var factory = SpanFactory.CreateCsHtml();
var errors = new List<RazorError>();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inject ")
.Accepts(AcceptedCharacters.None),
factory.Code(injectStatement)
.As(new InjectParameterGenerator(expectedService, expectedPropertyName))
};
// Act
var spans = ParseDocument(documentContent, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Empty(errors);
}
[Theory]
[InlineData("IMyService Service ", "IMyService", "Service")]
[InlineData(" TestService @namespace ", "TestService", "@namespace")]
public void ParseInjectKeyword_ParsesUpToNewLine(string injectStatement,
string expectedService,
string expectedPropertyName)
{
// Arrange
var documentContent = "@inject " + injectStatement + Environment.NewLine + "Bar";
var factory = SpanFactory.CreateCsHtml();
var errors = new List<RazorError>();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inject ")
.Accepts(AcceptedCharacters.None),
factory.Code(injectStatement + "\r\n")
.As(new InjectParameterGenerator(expectedService, expectedPropertyName)),
factory.Markup("Bar")
.With(new MarkupCodeGenerator())
};
// Act
var spans = ParseDocument(documentContent, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Empty(errors);
}
[Fact]
public void ParseInjectKeyword_ErrorOnMissingTypeName()
{
// Arrange
var errors = new List<RazorError>();
var documentContent = "@inject \r\nBar";
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inject ")
.Accepts(AcceptedCharacters.None),
factory.Code(" \r\n")
.As(new InjectParameterGenerator(string.Empty, string.Empty)),
factory.Markup("Bar")
.With(new MarkupCodeGenerator())
};
var expectedErrors = new[]
{
new RazorError("The 'inject' keyword must be followed by a type name on the same line.",
new SourceLocation(11, 0, 11), 1)
};
// Act
var spans = ParseDocument(documentContent, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Equal(expectedErrors, errors);
}
[Fact]
public void ParseInjectKeyword_ErrorOnMissingPropertyName()
{
// Arrange
var errors = new List<RazorError>();
var documentContent = "@inject IMyService \r\nBar";
var factory = SpanFactory.CreateCsHtml();
var expectedSpans = new Span[]
{
factory.EmptyHtml(),
factory.CodeTransition(SyntaxConstants.TransitionString)
.Accepts(AcceptedCharacters.None),
factory.MetaCode("inject ")
.Accepts(AcceptedCharacters.None),
factory.Code(" IMyService \r\n")
.As(new InjectParameterGenerator("IMyService", string.Empty)),
factory.Markup("Bar")
.With(new MarkupCodeGenerator())
};
var expectedErrors = new[]
{
new RazorError("A property name must be specified when using the 'inject' statement. " +
"Format for a 'inject' statement is '@inject <Type Name> <Property Name>'.",
new SourceLocation(20, 0, 20), 1)
};
// Act
var spans = ParseDocument(documentContent, errors);
// Assert
Assert.Equal(expectedSpans, spans);
Assert.Equal(expectedErrors, errors);
}
private static List<Span> ParseDocument(string documentContents,
List<RazorError> errors = null,
List<LineMapping> lineMappings = null)
{
errors = errors ?? new List<RazorError>();
var markupParser = new HtmlMarkupParser();
var codeParser = new TestMvcCSharpRazorCodeParser();
var reader = new SeekableTextReader(documentContents);
var context = new ParserContext(reader, codeParser, markupParser, markupParser);
codeParser.Context = context;
markupParser.Context = context;
markupParser.ParseDocument();
var results = context.CompleteParse();
errors.AddRange(results.ParserErrors);
return results.Document.Flatten().ToList();
}
private sealed class TestMvcCSharpRazorCodeParser : MvcRazorCodeParser
{
public TestMvcCSharpRazorCodeParser()
: base("RazorView")
{
}
public bool HasDirective(string directive)
{
Action handler;
return TryGetDirectiveHandler(directive, out handler);
}
}
}
}

View File

@ -0,0 +1,72 @@
// 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.Globalization;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
internal class RawTextSymbol : ISymbol
{
public SourceLocation Start { get; private set; }
public string Content { get; private set; }
public RawTextSymbol(SourceLocation start, string content)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
Start = start;
Content = content;
}
public override bool Equals(object obj)
{
var other = obj as RawTextSymbol;
return Equals(Start, other.Start) && Equals(Content, other.Content);
}
internal bool EquivalentTo(ISymbol sym)
{
return Equals(Start, sym.Start) && Equals(Content, sym.Content);
}
public override int GetHashCode()
{
return Start.GetHashCode() +
(13 * Content.GetHashCode());
}
public void OffsetStart(SourceLocation documentStart)
{
Start = documentStart + Start;
}
public void ChangeStart(SourceLocation newStart)
{
Start = newStart;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0} RAW - [{1}]", Start, Content);
}
internal void CalculateStart(Span prev)
{
if (prev == null)
{
Start = SourceLocation.Zero;
}
else
{
Start = new SourceLocationTracker(prev.Start).UpdateLocation(prev.Content).CurrentLocation;
}
}
}
}

View File

@ -0,0 +1,79 @@
// 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.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
public class SpanConstructor
{
public SpanBuilder Builder { get; private set; }
internal static IEnumerable<ISymbol> TestTokenizer(string str)
{
yield return new RawTextSymbol(SourceLocation.Zero, str);
}
public SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)
{
Builder = new SpanBuilder();
Builder.Kind = kind;
Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
foreach (ISymbol sym in symbols)
{
Builder.Accept(sym);
}
}
private Span Build()
{
return Builder.Build();
}
public SpanConstructor With(ISpanCodeGenerator generator)
{
Builder.CodeGenerator = generator;
return this;
}
public SpanConstructor With(SpanEditHandler handler)
{
Builder.EditHandler = handler;
return this;
}
public SpanConstructor With(Action<ISpanCodeGenerator> generatorConfigurer)
{
generatorConfigurer(Builder.CodeGenerator);
return this;
}
public SpanConstructor With(Action<SpanEditHandler> handlerConfigurer)
{
handlerConfigurer(Builder.EditHandler);
return this;
}
public static implicit operator Span(SpanConstructor self)
{
return self.Build();
}
public SpanConstructor Hidden()
{
Builder.CodeGenerator = SpanCodeGenerator.Null;
return this;
}
public SpanConstructor Accepts(AcceptedCharacters accepted)
{
return With(eh => eh.AcceptedCharacters = accepted);
}
}
}

View File

@ -0,0 +1,107 @@
// 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.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
public class SpanFactory
{
public Func<ITextDocument, ITokenizer> MarkupTokenizerFactory { get; set; }
public Func<ITextDocument, ITokenizer> CodeTokenizerFactory { get; set; }
public SourceLocationTracker LocationTracker { get; private set; }
public static SpanFactory CreateCsHtml()
{
return new SpanFactory()
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
public SpanFactory()
{
LocationTracker = new SourceLocationTracker();
}
public SpanConstructor Span(SpanKind kind, string content, CSharpSymbolType type)
{
return CreateSymbolSpan(kind, content, st => new CSharpSymbol(st, content, type));
}
public SpanConstructor Span(SpanKind kind, string content, HtmlSymbolType type)
{
return CreateSymbolSpan(kind, content, st => new HtmlSymbol(st, content, type));
}
public SpanConstructor Span(SpanKind kind, string content, bool markup)
{
return new SpanConstructor(kind, Tokenize(new[] { content }, markup));
}
public SpanConstructor Span(SpanKind kind, string[] content, bool markup)
{
return new SpanConstructor(kind, Tokenize(content, markup));
}
public SpanConstructor Span(SpanKind kind, params ISymbol[] symbols)
{
return new SpanConstructor(kind, symbols);
}
private SpanConstructor CreateSymbolSpan(SpanKind kind, string content, Func<SourceLocation, ISymbol> ctor)
{
var start = LocationTracker.CurrentLocation;
LocationTracker.UpdateLocation(content);
return new SpanConstructor(kind, new[] { ctor(start) });
}
public void Reset()
{
LocationTracker.CurrentLocation = SourceLocation.Zero;
}
private IEnumerable<ISymbol> Tokenize(IEnumerable<string> contentFragments, bool markup)
{
return contentFragments.SelectMany(fragment => Tokenize(fragment, markup));
}
private IEnumerable<ISymbol> Tokenize(string content, bool markup)
{
var tok = MakeTokenizer(markup, new SeekableTextReader(content));
ISymbol sym;
ISymbol last = null;
while ((sym = tok.NextSymbol()) != null)
{
OffsetStart(sym, LocationTracker.CurrentLocation);
last = sym;
yield return sym;
}
LocationTracker.UpdateLocation(content);
}
private ITokenizer MakeTokenizer(bool markup, SeekableTextReader seekableTextReader)
{
if (markup)
{
return MarkupTokenizerFactory(seekableTextReader);
}
else
{
return CodeTokenizerFactory(seekableTextReader);
}
}
private void OffsetStart(ISymbol sym, SourceLocation sourceLocation)
{
sym.OffsetStart(sourceLocation);
}
}
}

View File

@ -0,0 +1,124 @@
// 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;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
public static class SpanFactoryExtensions
{
public static UnclassifiedCodeSpanConstructor EmptyCSharp(this SpanFactory self)
{
var symbol = new CSharpSymbol(self.LocationTracker.CurrentLocation, string.Empty, CSharpSymbolType.Unknown);
return new UnclassifiedCodeSpanConstructor(self.Span(SpanKind.Code, symbol));
}
public static SpanConstructor EmptyHtml(this SpanFactory self)
{
var symbol = new HtmlSymbol(self.LocationTracker.CurrentLocation, string.Empty, HtmlSymbolType.Unknown);
return self.Span(SpanKind.Markup, symbol)
.With(new MarkupCodeGenerator());
}
public static UnclassifiedCodeSpanConstructor Code(this SpanFactory self, string content)
{
return new UnclassifiedCodeSpanConstructor(
self.Span(SpanKind.Code, content, markup: false));
}
public static SpanConstructor CodeTransition(this SpanFactory self)
{
return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: false)
.Accepts(AcceptedCharacters.None);
}
public static SpanConstructor CodeTransition(this SpanFactory self, string content)
{
return self.Span(SpanKind.Transition, content, markup: false).Accepts(AcceptedCharacters.None);
}
public static SpanConstructor CodeTransition(this SpanFactory self, CSharpSymbolType type)
{
return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type)
.Accepts(AcceptedCharacters.None);
}
public static SpanConstructor CodeTransition(this SpanFactory self, string content, CSharpSymbolType type)
{
return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
}
public static SpanConstructor MarkupTransition(this SpanFactory self)
{
return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: true)
.Accepts(AcceptedCharacters.None);
}
public static SpanConstructor MarkupTransition(this SpanFactory self, string content)
{
return self.Span(SpanKind.Transition, content, markup: true).Accepts(AcceptedCharacters.None);
}
public static SpanConstructor MarkupTransition(this SpanFactory self, HtmlSymbolType type)
{
return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type)
.Accepts(AcceptedCharacters.None);
}
public static SpanConstructor MarkupTransition(this SpanFactory self, string content, HtmlSymbolType type)
{
return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
}
public static SpanConstructor MetaCode(this SpanFactory self, string content)
{
return self.Span(SpanKind.MetaCode, content, markup: false);
}
public static SpanConstructor MetaCode(this SpanFactory self, string content, CSharpSymbolType type)
{
return self.Span(SpanKind.MetaCode, content, type);
}
public static SpanConstructor MetaMarkup(this SpanFactory self, string content)
{
return self.Span(SpanKind.MetaCode, content, markup: true);
}
public static SpanConstructor MetaMarkup(this SpanFactory self, string content, HtmlSymbolType type)
{
return self.Span(SpanKind.MetaCode, content, type);
}
public static SpanConstructor Comment(this SpanFactory self, string content, CSharpSymbolType type)
{
return self.Span(SpanKind.Comment, content, type);
}
public static SpanConstructor Comment(this SpanFactory self, string content, HtmlSymbolType type)
{
return self.Span(SpanKind.Comment, content, type);
}
public static SpanConstructor Markup(this SpanFactory self, string content)
{
return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
}
public static SpanConstructor Markup(this SpanFactory self, params string[] content)
{
return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
}
public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
{
var ret = self.CurrentLocation;
self.UpdateLocation(content);
return ret;
}
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Mvc.Razor
{
public class UnclassifiedCodeSpanConstructor
{
private SpanConstructor _self;
public UnclassifiedCodeSpanConstructor(SpanConstructor self)
{
_self = self;
}
public SpanConstructor AsMetaCode()
{
_self.Builder.Kind = SpanKind.MetaCode;
return _self;
}
public SpanConstructor AsStatement()
{
return _self.With(new StatementCodeGenerator());
}
public SpanConstructor AsExpression()
{
return _self.With(new ExpressionCodeGenerator());
}
public SpanConstructor AsImplicitExpression(ISet<string> keywords)
{
return AsImplicitExpression(keywords, acceptTrailingDot: false);
}
public SpanConstructor AsImplicitExpression(ISet<string> keywords, bool acceptTrailingDot)
{
return _self.With(new ImplicitExpressionEditHandler(SpanConstructor.TestTokenizer,
keywords,
acceptTrailingDot))
.With(new ExpressionCodeGenerator());
}
public SpanConstructor AsFunctionsBody()
{
return _self.With(new TypeMemberCodeGenerator());
}
public SpanConstructor AsNamespaceImport(string ns, int namespaceKeywordLength)
{
return _self.With(new AddImportCodeGenerator(ns, namespaceKeywordLength));
}
public SpanConstructor Hidden()
{
return _self.With(SpanCodeGenerator.Null);
}
public SpanConstructor AsBaseType(string baseType)
{
return _self.With(new SetBaseTypeCodeGenerator(baseType));
}
public SpanConstructor AsRazorDirectiveAttribute(string key, string value)
{
return _self.With(new RazorDirectiveAttributeCodeGenerator(key, value));
}
public SpanConstructor As(ISpanCodeGenerator codeGenerator)
{
return _self.With(codeGenerator);
}
}
}

View File

@ -0,0 +1,49 @@
// 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 Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Mvc.Razor
{
public class StringTextBuffer : ITextBuffer, IDisposable
{
private string _buffer;
public bool Disposed { get; set; }
public StringTextBuffer(string buffer)
{
_buffer = buffer;
}
public int Length
{
get { return _buffer.Length; }
}
public int Position { get; set; }
public int Read()
{
if (Position >= _buffer.Length)
{
return -1;
}
return _buffer[Position++];
}
public int Peek()
{
if (Position >= _buffer.Length)
{
return -1;
}
return _buffer[Position];
}
public void Dispose()
{
Disposed = true;
}
}
}

View File

@ -0,0 +1,2 @@
@using MyNamespace
@inject MyApp MyPropertyName

View File

@ -0,0 +1,40 @@
namespace Razor
{
#line 1 ""
using MyNamespace
#line default
#line hidden
;
using System.Threading.Tasks;
public class __CompiledTemplate : RazorView<dynamic>
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
#pragma warning restore 219
}
#line hidden
public
#line 2 ""
MyApp MyPropertyName
#line default
#line hidden
{ get; private set; }
#line hidden
public __CompiledTemplate(MyApp MyPropertyName)
{
this.MyPropertyName = MyPropertyName;
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,22 @@
{
"version" : "0.1-alpha-*",
"compilationOptions": {
"warningsAsErrors": false
},
"resources": "TestFiles\\**",
"dependencies": {
"Microsoft.AspNet.Mvc.Razor.Host" : "",
"xunit.assert": "2.0.0-aspnet-*",
"Xunit.KRunner": "0.1-alpha-*"
},
"commands": {
"test": "Xunit.KRunner"
},
"configurations": {
"net45": {
"dependencies": {
"Moq": "4.2.1312.1622"
}
}
}
}