diff --git a/Mvc.sln b/Mvc.sln
index 34dc993019..5729755b0d 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -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}
diff --git a/samples/MvcSample.Web/HomeController.cs b/samples/MvcSample.Web/HomeController.cs
index 69c63be9fc..2298c11307 100644
--- a/samples/MvcSample.Web/HomeController.cs
+++ b/samples/MvcSample.Web/HomeController.cs
@@ -23,6 +23,11 @@ namespace MvcSample.Web
return View("ValidationSummary");
}
+ public ActionResult InjectSample()
+ {
+ return View();
+ }
+
///
/// Action that shows metadata when model is null.
///
diff --git a/samples/MvcSample.Web/MvcSample.Web.kproj b/samples/MvcSample.Web/MvcSample.Web.kproj
index ee7589e03e..0f9dbe9769 100644
--- a/samples/MvcSample.Web/MvcSample.Web.kproj
+++ b/samples/MvcSample.Web/MvcSample.Web.kproj
@@ -28,6 +28,7 @@
+
@@ -60,6 +61,7 @@
+
diff --git a/samples/MvcSample.Web/Services/TestService.cs b/samples/MvcSample.Web/Services/TestService.cs
new file mode 100644
index 0000000000..153dfeaa75
--- /dev/null
+++ b/samples/MvcSample.Web/Services/TestService.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs
index e00fe14daa..40508cdc84 100644
--- a/samples/MvcSample.Web/Startup.cs
+++ b/samples/MvcSample.Web/Startup.cs
@@ -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();
services.AddSingleton();
+ services.AddTransient();
});
app.UseMvc(routes =>
diff --git a/samples/MvcSample.Web/Views/Home/InjectSample.cshtml b/samples/MvcSample.Web/Views/Home/InjectSample.cshtml
new file mode 100644
index 0000000000..2f3f14c8c7
--- /dev/null
+++ b/samples/MvcSample.Web/Views/Home/InjectSample.cshtml
@@ -0,0 +1,7 @@
+@using MvcSample.Web.Services
+@inject MvcSample.Web.Services.ITestService MyService
+@inject ITestService MyService2
+
+@MyService.GetFoo()
+@MyService2.GetFoo()
+
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.cs
new file mode 100644
index 0000000000..c58b5f7a8b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunk.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
+{
+ public class InjectChunk : Chunk
+ {
+ ///
+ /// 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.
+ public InjectChunk(string typeName,
+ string propertyName)
+ {
+ TypeName = typeName;
+ MemberName = propertyName;
+ }
+
+ public string TypeName { get; private set; }
+
+ public string MemberName { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs
new file mode 100644
index 0000000000..9194926bcc
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.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.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 _injectChunks = new List();
+
+ public InjectChunkVisitor([NotNull] CSharpCodeWriter writer,
+ [NotNull] CodeGeneratorContext context)
+ : base(writer, context)
+ {
+ }
+
+ public List 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectParameterGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectParameterGenerator.cs
new file mode 100644
index 0000000000..fdca4ab85d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectParameterGenerator.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj b/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj
index 3f79244f82..97bfe5d17d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj
@@ -22,6 +22,11 @@
+
+
+
+
+
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
new file mode 100644
index 0000000000..a3a9ac5c63
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
@@ -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(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(";");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeVistor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeVistor.cs
new file mode 100644
index 0000000000..89a843d307
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeVistor.cs
@@ -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
+ {
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs
index 99cf008f96..6b19279dac 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs
@@ -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);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
index 478d2284d1..6a08c7806a 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
@@ -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);
+ }
}
}
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 7d4618123b..ad6b256874 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs
@@ -27,19 +27,35 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
}
///
- /// 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} <TypeName> <PropertyName>'.
///
- internal static string MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName
+ internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired
{
- get { return GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"); }
+ get { return GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"); }
+ }
+
+ ///
+ /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <TypeName> <PropertyName>'.
+ ///
+ internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0);
}
///
/// The '{0}' keyword must be followed by a type name on the same line.
///
- internal static string FormatMvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(object p0)
+ internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName
{
- return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"), p0);
+ get { return GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"); }
+ }
+
+ ///
+ /// The '{0}' keyword must be followed by a type name on the same line.
+ ///
+ internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0);
}
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
index f95c023e35..f6562cd582 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx
@@ -120,7 +120,10 @@
The 'inherits' keyword is not allowed when a '{0}' keyword is used.
-
+
+ A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+
+
The '{0}' keyword must be followed by a type name on the same line.
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewEngine/VirtualPathViewFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewEngine/VirtualPathViewFactory.cs
index 632db46cea..ab6bfc4dd0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/ViewEngine/VirtualPathViewFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewEngine/VirtualPathViewFactory.cs
@@ -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;
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
new file mode 100644
index 0000000000..a1a3b8556b
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs
@@ -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
+ {
+ 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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj
new file mode 100644
index 0000000000..8b7af35e08
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj
@@ -0,0 +1,41 @@
+
+
+
+ 12.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 7c4f5973-0491-4028-b1dc-a9ba73ff9f77
+ Library
+
+
+ ConsoleDebugger
+
+
+ WebDebugger
+
+
+
+
+
+
+ 2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs
new file mode 100644
index 0000000000..be52271698
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs
@@ -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();
+ 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"))
+ };
+
+ // Act
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ Assert.Equal(expectedSpans, spans);
+ Assert.Empty(errors);
+ }
+
+ [Theory]
+ [InlineData("Foo?", "RazorView")]
+ [InlineData("Foo[[]][]", "RazorView")]
+ [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();
+ 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();
+ 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();
+ 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")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar")
+ .As(new SetBaseTypeCodeGenerator("RazorView"))
+ };
+
+ 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();
+ 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")),
+ 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();
+ 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"))
+ };
+
+ 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 MyHelper ",
+ "Microsoft.AspNet.Mvc.IHtmlHelper", "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();
+ 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();
+ 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();
+ 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();
+ 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 '.",
+ new SourceLocation(20, 0, 20), 1)
+ };
+
+ // Act
+ var spans = ParseDocument(documentContent, errors);
+
+ // Assert
+ Assert.Equal(expectedSpans, spans);
+ Assert.Equal(expectedErrors, errors);
+ }
+
+ private static List ParseDocument(string documentContents,
+ List errors = null,
+ List lineMappings = null)
+ {
+ errors = errors ?? new List();
+ 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);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/RawTextSymbol.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/RawTextSymbol.cs
new file mode 100644
index 0000000000..04cd180354
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/RawTextSymbol.cs
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanConstructor.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanConstructor.cs
new file mode 100644
index 0000000000..12e05f455c
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanConstructor.cs
@@ -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 TestTokenizer(string str)
+ {
+ yield return new RawTextSymbol(SourceLocation.Zero, str);
+ }
+
+ public SpanConstructor(SpanKind kind, IEnumerable 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 generatorConfigurer)
+ {
+ generatorConfigurer(Builder.CodeGenerator);
+ return this;
+ }
+
+ public SpanConstructor With(Action 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactory.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactory.cs
new file mode 100644
index 0000000000..019a1c9d24
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactory.cs
@@ -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 MarkupTokenizerFactory { get; set; }
+ public Func 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 ctor)
+ {
+ var start = LocationTracker.CurrentLocation;
+ LocationTracker.UpdateLocation(content);
+ return new SpanConstructor(kind, new[] { ctor(start) });
+ }
+
+ public void Reset()
+ {
+ LocationTracker.CurrentLocation = SourceLocation.Zero;
+ }
+
+ private IEnumerable Tokenize(IEnumerable contentFragments, bool markup)
+ {
+ return contentFragments.SelectMany(fragment => Tokenize(fragment, markup));
+ }
+
+ private IEnumerable 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);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactoryExtensions.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactoryExtensions.cs
new file mode 100644
index 0000000000..3214b3871c
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/SpanFactoryExtensions.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedSpanConstructor.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedSpanConstructor.cs
new file mode 100644
index 0000000000..d95b53bd8f
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedSpanConstructor.cs
@@ -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 keywords)
+ {
+ return AsImplicitExpression(keywords, acceptTrailingDot: false);
+ }
+
+ public SpanConstructor AsImplicitExpression(ISet 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/StringTextBuffer.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/StringTextBuffer.cs
new file mode 100644
index 0000000000..fd8ed4df8b
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/StringTextBuffer.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Inject.cshtml b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Inject.cshtml
new file mode 100644
index 0000000000..4e90b36808
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Inject.cshtml
@@ -0,0 +1,2 @@
+@using MyNamespace
+@inject MyApp MyPropertyName
\ 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
new file mode 100644
index 0000000000..27799209ba
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Inject.cs
@@ -0,0 +1,40 @@
+namespace Razor
+{
+#line 1 ""
+using MyNamespace
+
+#line default
+#line hidden
+ ;
+ using System.Threading.Tasks;
+
+ public class __CompiledTemplate : RazorView
+ {
+ 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
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json
new file mode 100644
index 0000000000..84a3ddcda2
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json
@@ -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"
+ }
+ }
+ }
+}
\ No newline at end of file