General razor clean up
* Change Razor compilation to use ApplicationBasePath to determine the app root * Change class name generation to be based on app-relative path.
This commit is contained in:
parent
2731caf476
commit
123089d5c7
14
WebFx.sln
14
WebFx.sln
|
|
@ -55,6 +55,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.net45", "src\Common\
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.k10", "src\Common\Common.k10.csproj", "{D4576205-A5B5-4382-BB34-19DE9855FE94}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.Razor.Test.net45", "test\Microsoft.AspNet.Mvc.Razor.Test\Microsoft.AspNet.Mvc.Razor.Test.net45.csproj", "{3EB2CFF9-6E67-4C03-9AC4-2DD169024938}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCommon.net45", "test\TestCommon\TestCommon.net45.csproj", "{75A07B53-C5EE-4995-A55B-27562C23BCCD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -137,6 +141,14 @@ Global
|
|||
{D4576205-A5B5-4382-BB34-19DE9855FE94}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4576205-A5B5-4382-BB34-19DE9855FE94}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4576205-A5B5-4382-BB34-19DE9855FE94}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -165,5 +177,7 @@ Global
|
|||
{A7D7CD66-A407-4144-8AB7-07F895F87137} = {CE037E26-9EB5-48E2-B73B-06C6FF6CC9F5}
|
||||
{42195A56-42C0-4CFF-A982-B6E24EFC6356} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public interface IMvcRazorHost
|
||||
{
|
||||
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
|
||||
GeneratorResults GenerateCode(string rootNamespace, string rootRelativePath, Stream inputStream);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
|
@ -50,19 +49,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||
public GeneratorResults GenerateCode(string rootNamespace, string rootRelativePath, Stream inputStream)
|
||||
{
|
||||
string className = Path.GetFileNameWithoutExtension(rootRelativePath);
|
||||
if (rootRelativePath.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
rootRelativePath = rootRelativePath.Substring(2);
|
||||
}
|
||||
string classNamespace = GenerateNamespace(rootRelativePath);
|
||||
|
||||
string className = ParserHelpers.SanitizeClassName(rootRelativePath);
|
||||
using (var reader = new StreamReader(inputStream))
|
||||
{
|
||||
var engine = new RazorTemplateEngine(this);
|
||||
return engine.GenerateCode(reader, className, classNamespace, rootRelativePath);
|
||||
return engine.GenerateCode(reader, className, rootNamespace, rootRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,28 +63,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
return new MvcRazorCodeParser(_baseType);
|
||||
}
|
||||
|
||||
private static string GenerateNamespace(string rootRelativePath)
|
||||
{
|
||||
var namespaceBuilder = new StringBuilder(rootRelativePath.Length);
|
||||
rootRelativePath = Path.GetDirectoryName(rootRelativePath);
|
||||
for (int i = 0; i < rootRelativePath.Length; i++)
|
||||
{
|
||||
char c = rootRelativePath[i];
|
||||
if (c == Path.DirectorySeparatorChar)
|
||||
{
|
||||
namespaceBuilder.Append('.');
|
||||
}
|
||||
else if (!Char.IsLetterOrDigit(c))
|
||||
{
|
||||
namespaceBuilder.Append('_');
|
||||
}
|
||||
else
|
||||
{
|
||||
namespaceBuilder.Append(c);
|
||||
}
|
||||
}
|
||||
return namespaceBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,38 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layout view '{0}' could not be located.
|
||||
/// </summary>
|
||||
internal static string LayoutCannotBeLocated
|
||||
{
|
||||
get { return GetString("LayoutCannotBeLocated"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layout view '{0}' could not be located.
|
||||
/// </summary>
|
||||
internal static string FormatLayoutCannotBeLocated(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("LayoutCannotBeLocated"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RenderBody can only be called from a layout page.
|
||||
/// </summary>
|
||||
internal static string RenderBodyCannotBeCalled
|
||||
{
|
||||
get { return GetString("RenderBodyCannotBeCalled"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RenderBody can only be called from a layout page.
|
||||
/// </summary>
|
||||
internal static string FormatRenderBodyCannotBeCalled()
|
||||
{
|
||||
return GetString("RenderBodyCannotBeCalled");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public interface IRazorCompilationService
|
||||
{
|
||||
Task<CompilationResult> Compile(string appRoot, IFileInfo fileInfo);
|
||||
Task<CompilationResult> Compile(IFileInfo fileInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,48 +5,60 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.Net.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorCompilationService : IRazorCompilationService
|
||||
{
|
||||
private static readonly CompilerCache _cache = new CompilerCache();
|
||||
private readonly IApplicationEnvironment _environment;
|
||||
private readonly ICompilationService _baseCompilationService;
|
||||
private readonly IMvcRazorHost _razorHost;
|
||||
private readonly string _appRoot;
|
||||
|
||||
public RazorCompilationService(ICompilationService compilationService, IMvcRazorHost razorHost)
|
||||
public RazorCompilationService(IApplicationEnvironment environment,
|
||||
ICompilationService compilationService,
|
||||
IMvcRazorHost razorHost)
|
||||
{
|
||||
_environment = environment;
|
||||
_baseCompilationService = compilationService;
|
||||
_razorHost = razorHost;
|
||||
_appRoot = EnsureTrailingSlash(environment.ApplicationBasePath);
|
||||
}
|
||||
|
||||
public Task<CompilationResult> Compile(string appRoot, IFileInfo file)
|
||||
public Task<CompilationResult> Compile([NotNull]IFileInfo file)
|
||||
{
|
||||
return _cache.GetOrAdd(file, () => CompileCore(appRoot, file));
|
||||
return _cache.GetOrAdd(file, () => CompileCore(file));
|
||||
}
|
||||
|
||||
private async Task<CompilationResult> CompileCore(string appRoot, IFileInfo file)
|
||||
// TODO: Make this internal
|
||||
public async Task<CompilationResult> CompileCore(IFileInfo file)
|
||||
{
|
||||
GeneratorResults results;
|
||||
using (Stream inputStream = file.CreateReadStream())
|
||||
{
|
||||
Contract.Assert(file.PhysicalPath.StartsWith(appRoot, StringComparison.OrdinalIgnoreCase));
|
||||
// Remove the app name segment so that it appears as part of the root relative path:
|
||||
// work/src/myapp/ -> work/src
|
||||
// root relative path: myapp/views/home/index.cshtml
|
||||
// TODO: The root namespace might be a property we'd have to read via configuration since it
|
||||
// affects other things such as resx files.
|
||||
appRoot = Path.GetDirectoryName(appRoot.TrimEnd(Path.DirectorySeparatorChar));
|
||||
string rootRelativePath = file.PhysicalPath.Substring(appRoot.Length).TrimStart(Path.DirectorySeparatorChar);
|
||||
results = _razorHost.GenerateCode(rootRelativePath, inputStream);
|
||||
Contract.Assert(file.PhysicalPath.StartsWith(_appRoot, StringComparison.OrdinalIgnoreCase));
|
||||
var rootRelativePath = file.PhysicalPath.Substring(_appRoot.Length);
|
||||
results = _razorHost.GenerateCode(_environment.ApplicationName, rootRelativePath, inputStream);
|
||||
}
|
||||
|
||||
if (!results.Success)
|
||||
{
|
||||
return CompilationResult.Failed(results.GeneratedCode, results.ParserErrors.Select(e => new CompilationMessage(e.Message)));
|
||||
var messages = results.ParserErrors.Select(e => new CompilationMessage(e.Message));
|
||||
throw new CompilationFailedException(messages, results.GeneratedCode);
|
||||
}
|
||||
|
||||
return await _baseCompilationService.Compile(results.GeneratedCode);
|
||||
}
|
||||
|
||||
private static string EnsureTrailingSlash([NotNull]string path)
|
||||
{
|
||||
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
{
|
||||
path += Path.DirectorySeparatorChar;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
|
@ -29,8 +28,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Execute();
|
||||
}
|
||||
|
||||
string bodyContent = contentBuilder.ToString();
|
||||
if (!String.IsNullOrEmpty(Layout))
|
||||
var bodyContent = contentBuilder.ToString();
|
||||
if (!string.IsNullOrEmpty(Layout))
|
||||
{
|
||||
await RenderLayoutAsync(context, writer, bodyContent);
|
||||
}
|
||||
|
|
@ -43,10 +42,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
private async Task RenderLayoutAsync(ViewContext context, TextWriter writer, string bodyContent)
|
||||
{
|
||||
var virtualPathFactory = context.ServiceProvider.GetService<IVirtualPathViewFactory>();
|
||||
RazorView layoutView = (RazorView)(await virtualPathFactory.CreateInstance(Layout));
|
||||
var layoutView = (RazorView)(await virtualPathFactory.CreateInstance(Layout));
|
||||
|
||||
if (layoutView == null)
|
||||
{
|
||||
string message = String.Format(CultureInfo.CurrentCulture, "The layout view '{0}' could not be located.", Layout);
|
||||
var message = Resources.FormatLayoutCannotBeLocated(Layout);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
|
|
@ -66,18 +66,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
if (content != null)
|
||||
{
|
||||
var htmlString = content as HtmlString;
|
||||
if (htmlString != null)
|
||||
{
|
||||
writer.Write(content.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NET45
|
||||
WebUtility.HtmlEncode(content.ToString(), writer);
|
||||
#else
|
||||
writer.Write(WebUtility.HtmlEncode(content.ToString()));
|
||||
#endif
|
||||
}
|
||||
var contentToWrite = htmlString != null ? content.ToString() :
|
||||
WebUtility.HtmlEncode(content.ToString());
|
||||
writer.Write(contentToWrite);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
if (BodyContent == null)
|
||||
{
|
||||
throw new InvalidOperationException("RenderBody cannot be called at this point because you're not executing a layout");
|
||||
throw new InvalidOperationException(Resources.RenderBodyCannotBeCalled);
|
||||
}
|
||||
return new HtmlString(BodyContent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,4 +120,10 @@
|
|||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>Value cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="LayoutCannotBeLocated" xml:space="preserve">
|
||||
<value>The layout view '{0}' could not be located.</value>
|
||||
</data>
|
||||
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
||||
<value>RenderBody can only be called from a layout page.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -10,22 +10,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
private readonly PhysicalFileSystem _fileSystem;
|
||||
private readonly IRazorCompilationService _compilationService;
|
||||
|
||||
public VirtualPathViewFactory(IApplicationEnvironment env, IRazorCompilationService compilationService)
|
||||
public VirtualPathViewFactory(IApplicationEnvironment env,
|
||||
IRazorCompilationService compilationService)
|
||||
{
|
||||
// TODO: Continue to inject the IFileSystem but only when we get it from the host
|
||||
_fileSystem = new PhysicalFileSystem(env.ApplicationBasePath);
|
||||
_compilationService = compilationService;
|
||||
}
|
||||
|
||||
public async Task<IView> CreateInstance(string virtualPath)
|
||||
public async Task<IView> CreateInstance([NotNull]string virtualPath)
|
||||
{
|
||||
// TODO: We need to glean the approot from HttpContext
|
||||
var appRoot = _fileSystem.Root;
|
||||
|
||||
IFileInfo fileInfo;
|
||||
if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo))
|
||||
{
|
||||
CompilationResult result = await _compilationService.Compile(appRoot, fileInfo);
|
||||
CompilationResult result = await _compilationService.Compile(fileInfo);
|
||||
return (IView)Activator.CreateInstance(result.CompiledType);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,34 +34,34 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
// TODO: We plan to change this on the next CR, so we don't have a strong depenedency directly on the specific
|
||||
// type of the action descriptor
|
||||
ActionDescriptor actionDescriptor = actionContext.ActionDescriptor;
|
||||
var actionDescriptor = actionContext.ActionDescriptor;
|
||||
|
||||
if (actionDescriptor == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(viewName))
|
||||
if (string.IsNullOrEmpty(viewName))
|
||||
{
|
||||
viewName = actionDescriptor.Name;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(viewName))
|
||||
if (string.IsNullOrEmpty(viewName))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "viewName");
|
||||
}
|
||||
|
||||
bool nameRepresentsPath = IsSpecificPath(viewName);
|
||||
var nameRepresentsPath = IsSpecificPath(viewName);
|
||||
|
||||
if (nameRepresentsPath)
|
||||
{
|
||||
IView view = await _virtualPathFactory.CreateInstance(viewName);
|
||||
var view = await _virtualPathFactory.CreateInstance(viewName);
|
||||
return view != null ? ViewEngineResult.Found(view) :
|
||||
ViewEngineResult.NotFound(new[] { viewName });
|
||||
}
|
||||
else
|
||||
{
|
||||
string controllerName = actionDescriptor.Path;
|
||||
var controllerName = actionDescriptor.Path;
|
||||
var searchedLocations = new List<string>(_viewLocationFormats.Length);
|
||||
for (int i = 0; i < _viewLocationFormats.Length; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.PipelineCore": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding" : "",
|
||||
"TestCommon" : "",
|
||||
"Moq": "4.0.10827",
|
||||
"Xunit": "1.9.1",
|
||||
"Xunit.extensions": "1.9.1"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.Net.Runtime;
|
||||
using Moq;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||
{
|
||||
public class RazorCompilationServiceTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"src\work\myapp", @"src\work\myapp\views\index\home.cshtml")]
|
||||
[InlineData(@"src\work\myapp\", @"src\work\myapp\views\index\home.cshtml")]
|
||||
public void CompileCoreCalculatesRootRelativePath(string appPath, string viewPath)
|
||||
{
|
||||
// Arrange
|
||||
var env = new Mock<IApplicationEnvironment>();
|
||||
env.SetupGet(e => e.ApplicationName).Returns("MyTestApplication");
|
||||
env.SetupGet(e => e.ApplicationBasePath).Returns(appPath);
|
||||
var host = new Mock<IMvcRazorHost>();
|
||||
host.Setup(h => h.GenerateCode("MyTestApplication", @"views\index\home.cshtml", It.IsAny<Stream>()))
|
||||
.Returns(new GeneratorResults(new Block(new BlockBuilder { Type = BlockType.Comment }), new RazorError[0], new CodeBuilderResult("", new LineMapping[0])))
|
||||
.Verifiable();
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(It.IsAny<string>()))
|
||||
.Returns(Task.FromResult(CompilationResult.Successful("", typeof(RazorCompilationServiceTest))));
|
||||
|
||||
var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
|
||||
|
||||
// Act
|
||||
razorService.CompileCore(fileInfo.Object).Wait();
|
||||
|
||||
// Assert
|
||||
host.Verify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version" : "0.1-alpha-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.FileSystems": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.Razor": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.Mvc.Razor" : "",
|
||||
"Microsoft.AspNet.Mvc.Razor.Host" : "",
|
||||
"Moq": "4.0.10827",
|
||||
"Xunit": "1.9.1",
|
||||
"Xunit.extensions": "1.9.1"
|
||||
},
|
||||
"configurations": {
|
||||
"net45": { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"shared": "*.cs",
|
||||
"dependencies": {
|
||||
"Xunit": "1.9.1",
|
||||
"Xunit.extensions": "1.9.1"
|
||||
},
|
||||
"configurations": {
|
||||
"net45": { }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue