Adding support for disk based views with code generation and compilation

This commit is contained in:
Pranav K 2014-01-16 21:09:59 -08:00
parent b918cb8170
commit 0699e7aa40
39 changed files with 754 additions and 46 deletions

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.CoreServices
{
public class CompilationFailedException : Exception
{
public CompilationFailedException(IEnumerable<CompilationMessage> messages, string generatedCode)
: base(FormatMessage(messages))
{
Messages = messages.ToList();
GeneratedCode = generatedCode;
}
public string GeneratedCode { get; private set; }
public IEnumerable<CompilationMessage> Messages { get; private set; }
public string CompilationSource
{
get { return GeneratedCode; }
}
public override string Message
{
get
{
return "Compilation Failed:" + FormatMessage(Messages);
}
}
private static string FormatMessage(IEnumerable<CompilationMessage> messages)
{
return String.Join(Environment.NewLine, messages);
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace Microsoft.AspNet.CoreServices
{
public class CompilationMessage
{
public CompilationMessage(string message)
{
Message = message;
}
public string Message {get; private set;}
public override string ToString()
{
return Message;
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.CoreServices
{
public class CompilationResult
{
private readonly Type _type;
private CompilationResult(string generatedCode, Type type, IEnumerable<CompilationMessage> messages)
{
_type = type;
GeneratedCode = generatedCode;
Messages = messages.ToList();
}
public IEnumerable<CompilationMessage> Messages { get; private set; }
public string GeneratedCode { get; private set; }
public Type CompiledType
{
get
{
if (_type == null)
{
throw new CompilationFailedException(Messages, GeneratedCode);
}
return _type;
}
}
public static CompilationResult Failed(string generatedCode, IEnumerable<CompilationMessage> messages)
{
return new CompilationResult(generatedCode, type: null, messages: messages);
}
public static CompilationResult Successful(string generatedCode, Type type)
{
return new CompilationResult(generatedCode, type, Enumerable.Empty<CompilationMessage>());
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.CoreServices
{
public class CompilerCache
{
private readonly MemoryCache _cache;
public CompilerCache()
{
_cache = MemoryCache.Default;
}
public Task<CompilationResult> GetOrAdd(IFileInfo file, Func<Task<CompilationResult>> compile)
{
// Generate a content id
string contentId = file.PhysicalPath + '|' + file.LastModified.Ticks;
var cachedType = _cache[contentId] as Type;
if (cachedType == null)
{
return CompileWith(contentId, file, compile);
}
return Task.FromResult(CompilationResult.Successful(generatedCode: null, type: cachedType));
}
private async Task<CompilationResult> CompileWith(string contentId, IFileInfo file, Func<Task<CompilationResult>> compile)
{
CompilationResult result = await compile();
Type compiledType = result.CompiledType;
var filePaths = new [] { file.PhysicalPath };
var policy = new CacheItemPolicy();
policy.ChangeMonitors.Add(new HostFileChangeMonitor(filePaths));
_cache.Set(contentId, result.CompiledType, policy);
return result;
}
}
}

View File

@ -0,0 +1,120 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.CoreServices
{
public class CscBasedCompilationService : ICompilationService
{
private static readonly string _tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
private readonly string _path;
public CscBasedCompilationService()
{
_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows),
@"Microsoft.NET\Framework\v4.0.30319\csc.exe");
}
public async Task<CompilationResult> Compile(IFileInfo fileInfo)
{
Directory.CreateDirectory(_tempDir);
string outFile = Path.Combine(_tempDir, Path.GetRandomFileName() + ".dll");
StringBuilder args = new StringBuilder("/target:library ");
args.AppendFormat("/out:\"{0}\" ", outFile);
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll"))
{
args.AppendFormat("/R:\"{0}\" ", file);
}
args.AppendFormat("\"{0}\"", fileInfo.PhysicalPath);
var outputStream = new MemoryStream();
var errorStream = new MemoryStream();
// common execute
var process = CreateProcess(args.ToString());
int exitCode = await Start(process, outputStream, errorStream);
string output = GetString(outputStream);
string error = GetString(errorStream);
if (exitCode != 0)
{
return CompilationResult.Failed(String.Empty, error.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => new CompilationMessage(e)));
}
var type = Assembly.LoadFrom(outFile)
.GetExportedTypes()
.First();
return CompilationResult.Successful(String.Empty, type);
}
private string GetString(MemoryStream stream)
{
if (stream.Length > 0)
{
return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
}
return String.Empty;
}
private static async Task<int> Start(Process process, Stream output, Stream error)
{
var tcs = new TaskCompletionSource<int>();
process.EnableRaisingEvents = true;
process.Exited += (sender, eventArgs) =>
{
tcs.SetResult(process.ExitCode);
};
process.Start();
var tasks = new[]
{
process.StandardOutput.BaseStream.CopyToAsync(output),
process.StandardError.BaseStream.CopyToAsync(error)
};
int result = await tcs.Task;
// Process has exited, draining the stdout and stderr
await Task.WhenAll(tasks);
return result;
}
internal Process CreateProcess(string arguments)
{
var psi = new ProcessStartInfo
{
FileName = _path,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
ErrorDialog = false,
Arguments = arguments
};
psi.StandardOutputEncoding = Encoding.UTF8;
psi.StandardErrorEncoding = Encoding.UTF8;
var process = new Process()
{
StartInfo = psi
};
return process;
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.CoreServices
{
public class DefaultCompilationService : ICompilationService
{
public Task<CompilationResult> Compile(IFileInfo fileInfo)
{
return null;
}
}
}

View File

@ -0,0 +1,11 @@

using System.Threading.Tasks;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.CoreServices
{
public interface ICompilationService
{
Task<CompilationResult> Compile(IFileInfo fileInfo);
}
}

View File

@ -30,11 +30,22 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.Owin.FileSystems">
<HintPath>..\packages\Microsoft.Owin.FileSystems.2.1.0-rc1\lib\net40\Microsoft.Owin.FileSystems.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ActivatorUtilities.cs" /> <Compile Include="ActivatorUtilities.cs" />
<Compile Include="Compilation\CompilationFailedException.cs" />
<Compile Include="Compilation\CompilationMessage.cs" />
<Compile Include="Compilation\CompilationResult.cs" />
<Compile Include="Compilation\CompilerCache.cs" />
<Compile Include="Compilation\CscBasedCompilationService.cs" />
<Compile Include="Compilation\DefaultCompilationService.cs" />
<Compile Include="Compilation\ICompilationService.cs" />
<Compile Include="ServiceProvider.cs" /> <Compile Include="ServiceProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceProviderExtensions.cs" /> <Compile Include="ServiceProviderExtensions.cs" />
@ -42,6 +53,9 @@
<ItemGroup> <ItemGroup>
<Content Include="project.json" /> <Content Include="project.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin.FileSystems" version="2.1.0-rc1" targetFramework="net45" />
</packages>

View File

@ -1,10 +0,0 @@

using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor
{
public interface IVirtualPathFactory
{
Task<object> CreateInstance(string virtualPath);
}
}

View File

@ -34,12 +34,19 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.Owin.FileSystems">
<HintPath>..\packages\Microsoft.Owin.FileSystems.2.1.0-rc1\lib\net40\Microsoft.Owin.FileSystems.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath> <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.Razor.3.0.0\lib\net45\System.Web.Razor.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -48,12 +55,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="HtmlString.cs" /> <Compile Include="HtmlString.cs" />
<Compile Include="IVirtualPathFactory.cs" /> <Compile Include="RazorViewOfT.cs" />
<Compile Include="MetadataVirtualPathProvider.cs" /> <Compile Include="Razor\MvcCSharpRazorCodeGenerator.cs" />
<Compile Include="Razor\MvcCSharpRazorCodeParser.cs" />
<Compile Include="Razor\MvcRazorHost.cs" />
<Compile Include="Razor\RazorCompilationService.cs" />
<Compile Include="Razor\SetModelTypeCodeGenerator.cs" />
<Compile Include="ViewEngine\IVirtualPathViewFactory.cs" />
<Compile Include="ViewEngine\MetadataVirtualPathViewFactory.cs" />
<Compile Include="RazorView.cs" /> <Compile Include="RazorView.cs" />
<Compile Include="RazorViewEngine.cs" /> <Compile Include="ViewEngine\RazorViewEngine.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VirtualPathAttribute.cs" /> <Compile Include="ViewEngine\VirtualFileSystem.cs" />
<Compile Include="ViewEngine\VirtualPathAttribute.cs" />
<Compile Include="ViewEngine\VirtualPathViewFactory.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Microsoft.AspNet.CoreServices\Microsoft.AspNet.CoreServices.csproj"> <ProjectReference Include="..\Microsoft.AspNet.CoreServices\Microsoft.AspNet.CoreServices.csproj">

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.CodeDom;
using System.Web.Razor;
using System.Web.Razor.Generator;
namespace Microsoft.AspNet.Mvc.Razor
{
internal class MvcCSharpRazorCodeGenerator : CSharpRazorCodeGenerator
{
private const string DefaultModelTypeName = "dynamic";
public MvcCSharpRazorCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
: base(className, rootNamespaceName, sourceFileName, host)
{
// set the default model type to "dynamic" (Dev10 bug 935656)
// don't set it for "special" pages (such as "_viewStart.cshtml")
SetBaseType(DefaultModelTypeName);
}
private void SetBaseType(string modelTypeName)
{
var baseType = new CodeTypeReference(Context.Host.DefaultBaseClass + "<" + modelTypeName + ">");
Context.GeneratedClass.BaseTypes.Clear();
Context.GeneratedClass.BaseTypes.Add(baseType);
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Web.Razor.Generator;
using System.Web.Razor.Parser;
using System.Web.Razor.Text;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcCSharpRazorCodeParser : CSharpCodeParser
{
private const string ModelKeyword = "model";
private SourceLocation? _endInheritsLocation;
private bool _modelStatementFound;
public MvcCSharpRazorCodeParser()
{
MapDirectives(ModelDirective, ModelKeyword);
}
protected override void InheritsDirective()
{
// Verify we're on the right keyword and accept
AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
AcceptAndMoveNext();
_endInheritsLocation = CurrentLocation;
InheritsDirectiveCore();
CheckForInheritsAndModelStatements();
}
protected virtual void ModelDirective()
{
// Verify we're on the right keyword and accept
AssertDirective(ModelKeyword);
AcceptAndMoveNext();
SourceLocation endModelLocation = CurrentLocation;
BaseTypeDirective(
String.Format(CultureInfo.CurrentCulture,
"The '{0}' keyword must be followed by a type name on the same line.", ModelKeyword),
CreateModelCodeGenerator);
if (_modelStatementFound)
{
Context.OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture,
"Only one '{0}' statement is allowed in a file.", ModelKeyword));
}
_modelStatementFound = true;
CheckForInheritsAndModelStatements();
}
private void CheckForInheritsAndModelStatements()
{
if (_modelStatementFound && _endInheritsLocation.HasValue)
{
Context.OnError(_endInheritsLocation.Value, String.Format(CultureInfo.CurrentCulture,
"The 'inherits' keyword is not allowed when a '{0}' keyword is used.", ModelKeyword));
}
}
private SpanCodeGenerator CreateModelCodeGenerator(string model)
{
return new SetModelTypeCodeGenerator(model);
}
}
}

View File

@ -0,0 +1,53 @@
using System.Web.Razor;
using System.Web.Razor.Generator;
using System.Web.Razor.Parser;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcRazorHost : RazorEngineHost
{
private static readonly string[] _namespaces = new[]
{
"System",
"System.Linq",
"System.Collections.Generic",
"Microsoft.AspNet.Mvc",
"Microsoft.AspNet.Mvc.Razor"
};
public MvcRazorHost()
: base(new CSharpRazorCodeLanguage())
{
DefaultBaseClass = typeof(RazorView).FullName;
GeneratedClassContext = new GeneratedClassContext(
executeMethodName: "Execute",
writeMethodName: "Write",
writeLiteralMethodName: "WriteLiteral",
writeToMethodName: "WriteTo",
writeLiteralToMethodName: "WriteLiteralTo",
templateTypeName: "Template",
defineSectionMethodName: "DefineSection")
{
ResolveUrlMethodName = "Href"
};
foreach (var ns in _namespaces)
{
NamespaceImports.Add(ns);
}
}
public override RazorCodeGenerator DecorateCodeGenerator(RazorCodeGenerator incomingCodeGenerator)
{
return new MvcCSharpRazorCodeGenerator(incomingCodeGenerator.ClassName,
incomingCodeGenerator.RootNamespaceName,
incomingCodeGenerator.SourceFileName,
incomingCodeGenerator.Host);
}
public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
{
return new MvcCSharpRazorCodeParser();
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Razor;
using System.Web.Razor.Generator;
using Microsoft.AspNet.CoreServices;
using Microsoft.CSharp;
using Microsoft.Owin;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
public class RazorCompilationService : ICompilationService
{
private static readonly string _tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
private readonly IFileSystem _tempFileSystem = new PhysicalFileSystem(Path.GetTempPath());
private readonly ICompilationService _baseCompilationService;
private readonly CompilerCache _cache = new CompilerCache();
public RazorCompilationService(ICompilationService compilationService)
{
_baseCompilationService = compilationService;
}
public Task<CompilationResult> Compile(IFileInfo file)
{
return _cache.GetOrAdd(file, () => CompileCore(file));
}
private async Task<CompilationResult> CompileCore(IFileInfo file)
{
var host = new MvcRazorHost();
var engine = new RazorTemplateEngine(host);
GeneratorResults results;
using (TextReader rdr = new StreamReader(file.CreateReadStream()))
{
results = engine.GenerateCode(rdr, '_' + Path.GetFileNameWithoutExtension(file.Name), "Asp", file.PhysicalPath ?? file.Name);
}
string generatedCode;
using (var writer = new StringWriter())
using (var codeProvider = new CSharpCodeProvider())
{
codeProvider.GenerateCodeFromCompileUnit(results.GeneratedCode, writer, new CodeGeneratorOptions());
generatedCode = writer.ToString();
}
if (!results.Success)
{
return CompilationResult.Failed(generatedCode, results.ParserErrors.Select(e => new CompilationMessage(e.Message)));
}
Directory.CreateDirectory(_tempPath);
string tempFile = Path.Combine(_tempPath, Path.GetRandomFileName() + ".cs");
File.WriteAllText(tempFile, generatedCode);
_tempFileSystem.TryGetFileInfo(tempFile, out file);
return await _baseCompilationService.Compile(file);
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Web.Razor.Generator;
namespace Microsoft.AspNet.Mvc.Razor
{
internal class SetModelTypeCodeGenerator : SetBaseTypeCodeGenerator
{
private const string GenericTypeFormatString = "{0}<{1}>";
public SetModelTypeCodeGenerator(string modelType)
: base(modelType)
{
}
protected override string ResolveType(CodeGeneratorContext context, string baseType)
{
return String.Format(
CultureInfo.InvariantCulture,
GenericTypeFormatString,
context.Host.DefaultBaseClass,
baseType);
}
}
}

View File

@ -13,18 +13,14 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
public IOwinContext Context { get; set; } public IOwinContext Context { get; set; }
public object Model { get; set; }
public string Layout { get; set; } public string Layout { get; set; }
protected TextWriter Output { get; set; } protected TextWriter Output { get; set; }
private string BodyContent { get; set; } private string BodyContent { get; set; }
public async Task RenderAsync(ViewContext context, TextWriter writer) public virtual async Task RenderAsync(ViewContext context, TextWriter writer)
{ {
Model = context.Model;
var contentBuilder = new StringBuilder(1024); var contentBuilder = new StringBuilder(1024);
using (var bodyWriter = new StringWriter(contentBuilder)) using (var bodyWriter = new StringWriter(contentBuilder))
{ {
@ -45,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private async Task RenderLayoutAsync(ViewContext context, TextWriter writer, string bodyContent) private async Task RenderLayoutAsync(ViewContext context, TextWriter writer, string bodyContent)
{ {
var virtualPathFactory = context.ServiceProvider.GetService<IVirtualPathFactory>(); var virtualPathFactory = context.ServiceProvider.GetService<IVirtualPathViewFactory>();
RazorView razorView = (RazorView)(await virtualPathFactory.CreateInstance(Layout)); RazorView razorView = (RazorView)(await virtualPathFactory.CreateInstance(Layout));
if (razorView == null) if (razorView == null)
{ {
@ -57,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Razor
await razorView.RenderAsync(context, writer); await razorView.RenderAsync(context, writer);
} }
protected abstract void Execute(); public abstract void Execute();
public virtual void Write(object value) public virtual void Write(object value)
{ {
@ -93,12 +89,13 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
protected virtual void RenderBody() protected virtual string RenderBody()
{ {
if (BodyContent != null) if (BodyContent == null)
{ {
WriteLiteral(BodyContent); throw new InvalidOperationException("RenderBody cannot be called at this point because you're not executing a layout");
} }
return BodyContent;
} }
} }
} }

View File

@ -0,0 +1,16 @@
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor
{
public abstract class RazorView<TModel> : RazorView
{
public TModel Model { get; set; }
public override Task RenderAsync(ViewContext context, TextWriter writer)
{
Model = (TModel)context.Model;
return base.RenderAsync(context, writer);
}
}
}

View File

@ -0,0 +1,10 @@

using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor
{
public interface IVirtualPathViewFactory
{
Task<IView> CreateInstance(string virtualPath);
}
}

View File

@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
public class MetadataVirtualPathProvider : IVirtualPathFactory public class MetadataVirtualPathViewFactory : IVirtualPathViewFactory
{ {
private readonly Dictionary<string, Type> _viewMetadata; private readonly Dictionary<string, Type> _viewMetadata;
public MetadataVirtualPathProvider(Assembly assembly) public MetadataVirtualPathViewFactory(Assembly assembly)
{ {
var metadataType = assembly.GetType("ViewMetadata"); var metadataType = assembly.GetType("ViewMetadata");
if (metadataType != null) if (metadataType != null)
@ -29,13 +29,13 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
public Task<object> CreateInstance(string virtualPath) public Task<IView> CreateInstance(string virtualPath)
{ {
Type type; Type type;
object view = null; IView view = null;
if (_viewMetadata.TryGetValue(virtualPath, out type)) if (_viewMetadata.TryGetValue(virtualPath, out type))
{ {
view = (RazorView)Activator.CreateInstance(type); view = (IView)Activator.CreateInstance(type);
} }
return Task.FromResult(view); return Task.FromResult(view);
} }

View File

@ -13,10 +13,10 @@ namespace Microsoft.AspNet.Mvc.Razor
"~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.cshtml",
}; };
private readonly IActionDescriptorProvider _actionDescriptorProvider; private readonly IActionDescriptorProvider _actionDescriptorProvider;
private readonly IVirtualPathFactory _virtualPathFactory; private readonly IVirtualPathViewFactory _virtualPathFactory;
public RazorViewEngine(IActionDescriptorProvider actionDescriptorProvider, public RazorViewEngine(IActionDescriptorProvider actionDescriptorProvider,
IVirtualPathFactory virtualPathFactory) IVirtualPathViewFactory virtualPathFactory)
{ {
_actionDescriptorProvider = actionDescriptorProvider; _actionDescriptorProvider = actionDescriptorProvider;
_virtualPathFactory = virtualPathFactory; _virtualPathFactory = virtualPathFactory;
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.Razor
for (int i = 0; i < _viewLocationFormats.Length; i++) for (int i = 0; i < _viewLocationFormats.Length; i++)
{ {
string path = String.Format(CultureInfo.InvariantCulture, _viewLocationFormats[i], viewName, controllerName); string path = String.Format(CultureInfo.InvariantCulture, _viewLocationFormats[i], viewName, controllerName);
RazorView view = (RazorView)(await _virtualPathFactory.CreateInstance(path)); IView view = await _virtualPathFactory.CreateInstance(path);
if (view != null) if (view != null)
{ {
return ViewEngineResult.Found(view); return ViewEngineResult.Found(view);

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
public class VirtualFileSystem : IFileSystem
{
private readonly IFileSystem _fileSystem;
public VirtualFileSystem(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
{
string translated = TranslatePath(subpath);
return _fileSystem.TryGetFileInfo(translated, out fileInfo);
}
public bool TryGetDirectoryContents(string subpath, out IEnumerable<IFileInfo> contents)
{
string translated = TranslatePath(subpath);
return _fileSystem.TryGetDirectoryContents(translated, out contents);
}
private static string TranslatePath(string path)
{
if (path.StartsWith("~/", StringComparison.Ordinal))
{
path = path.Substring(2);
}
return path;
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.CoreServices;
using Microsoft.Owin.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
public class VirtualPathViewFactory : IVirtualPathViewFactory
{
private readonly IFileSystem _fileSystem;
private readonly ICompilationService _compilationService;
public VirtualPathViewFactory(IFileSystem fileSystem, ICompilationService compilationService)
{
_fileSystem = fileSystem;
_compilationService = compilationService;
}
public async Task<IView> CreateInstance(string virtualPath)
{
IFileInfo fileInfo;
if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo))
{
CompilationResult result = await _compilationService.Compile(fileInfo);
return (IView)Activator.CreateInstance(result.CompiledType);
}
return null;
}
}
}

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.AspNet.Razor" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin" version="2.0.2" targetFramework="net45" /> <package id="Microsoft.Owin" version="2.0.2" targetFramework="net45" />
<package id="Microsoft.Owin.FileSystems" version="2.1.0-rc1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" /> <package id="Owin" version="1.0" targetFramework="net45" />
</packages> </packages>

View File

@ -36,12 +36,12 @@ namespace Microsoft.AspNet.Mvc
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IActionResult View() public IActionResult View(string view, object model)
{ {
return new ViewResult(_serviceProvider, _viewEngine) return new ViewResult(_serviceProvider, _viewEngine)
{ {
ViewName = null, ViewName = view,
Model = null Model = model
}; };
} }
} }

View File

@ -0,0 +1,21 @@

namespace Microsoft.AspNet.Mvc
{
public static class ActionResultHelperExtensions
{
public static IActionResult View(this IActionResultHelper actionResultHelper)
{
return actionResultHelper.View(view: null, model: null);
}
public static IActionResult View(this IActionResultHelper actionResultHelper, string view)
{
return actionResultHelper.View(view, model: null);
}
public static IActionResult View(this IActionResultHelper actionResultHelper, object model)
{
return actionResultHelper.View(view: null, model: model);
}
}
}

View File

@ -6,6 +6,6 @@ namespace Microsoft.AspNet.Mvc
IActionResult Content(string value); IActionResult Content(string value);
IActionResult Content(string value, string contentType); IActionResult Content(string value, string contentType);
IActionResult Json(object value); IActionResult Json(object value);
IActionResult View(); IActionResult View(string view, object model);
} }
} }

View File

@ -53,6 +53,7 @@
<Compile Include="ActionInvokerProvider.cs" /> <Compile Include="ActionInvokerProvider.cs" />
<Compile Include="ActionResultFactory.cs" /> <Compile Include="ActionResultFactory.cs" />
<Compile Include="ActionResultHelper.cs" /> <Compile Include="ActionResultHelper.cs" />
<Compile Include="ActionResultHelperExtensions.cs" />
<Compile Include="ActionResults\NoContentResult.cs" /> <Compile Include="ActionResults\NoContentResult.cs" />
<Compile Include="ActionResults\NegotiatedContentResult.cs" /> <Compile Include="ActionResults\NegotiatedContentResult.cs" />
<Compile Include="ActionResults\ContentResult.cs" /> <Compile Include="ActionResults\ContentResult.cs" />

View File

@ -12,8 +12,8 @@ namespace Microsoft.AspNet.Mvc
Model = model; Model = model;
} }
public object Model { get; private set; }
public IServiceProvider ServiceProvider { get; set; } public IServiceProvider ServiceProvider { get; set; }
public object Model { get; private set; }
} }
} }

View File

@ -38,7 +38,6 @@ namespace Microsoft.AspNet.Mvc
var viewContext = new ViewContext(context.HttpContext, context.RouteData, Model) var viewContext = new ViewContext(context.HttpContext, context.RouteData, Model)
{ {
ServiceProvider = _serviceProvider ServiceProvider = _serviceProvider
}; };
await view.RenderAsync(viewContext, writer); await view.RenderAsync(viewContext, writer);
} }

View File

@ -50,7 +50,7 @@ namespace MvcSample
public IActionResult MyView() public IActionResult MyView()
{ {
return Result.View(); return Result.View(User());
} }
} }
} }

View File

@ -46,6 +46,10 @@
<Reference Include="Microsoft.Owin.Diagnostics"> <Reference Include="Microsoft.Owin.Diagnostics">
<HintPath>..\packages\Microsoft.Owin.Diagnostics.2.0.2\lib\net40\Microsoft.Owin.Diagnostics.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.Diagnostics.2.0.2\lib\net40\Microsoft.Owin.Diagnostics.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.Owin.FileSystems, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.FileSystems.2.1.0-rc1\lib\net40\Microsoft.Owin.FileSystems.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
@ -90,6 +94,8 @@
<ItemGroup> <ItemGroup>
<Content Include="web.config" /> <Content Include="web.config" />
<Content Include="project.json" /> <Content Include="project.json" />
<Content Include="Views\Home\MyView.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" />
<None Include="web.Debug.config"> <None Include="web.Debug.config">
<DependentUpon>web.config</DependentUpon> <DependentUpon>web.config</DependentUpon>
</None> </None>

View File

@ -5,6 +5,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin; using Microsoft.Owin;
using Microsoft.Owin.FileSystems;
using Owin; using Owin;
[assembly: OwinStartup(typeof(MvcSample.Startup))] [assembly: OwinStartup(typeof(MvcSample.Startup))]
@ -17,12 +18,17 @@ namespace MvcSample
{ {
app.UseErrorPage(); app.UseErrorPage();
string appRoot = Environment.GetEnvironmentVariable("WEB_ROOT") ??
AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
var serviceProvider = MvcServices.Create(); var serviceProvider = MvcServices.Create();
serviceProvider.AddInstance<IVirtualPathFactory>(new MetadataVirtualPathProvider(GetType().Assembly)); var fileSystem = new PhysicalFileSystem(appRoot);
serviceProvider.AddInstance<IFileSystem>(new VirtualFileSystem(fileSystem));
serviceProvider.AddInstance<ICompilationService>(new RazorCompilationService(new CscBasedCompilationService()));
serviceProvider.Add<IVirtualPathViewFactory, VirtualPathViewFactory>();
serviceProvider.Add<IViewEngine, RazorViewEngine>(); serviceProvider.Add<IViewEngine, RazorViewEngine>();
var handler = new MvcHandler(serviceProvider); var handler = new MvcHandler(serviceProvider);
app.Run(async context => app.Run(async context =>
{ {
// Pretending to be routing // Pretending to be routing

View File

@ -0,0 +1,2 @@
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
@DateTime.UtcNow

View File

@ -6,7 +6,7 @@ namespace MvcSample.Views
[VirtualPath("~/Views/Shared/_Layout.cshtml")] [VirtualPath("~/Views/Shared/_Layout.cshtml")]
public class Layout : RazorView public class Layout : RazorView
{ {
protected override void Execute() public override void Execute()
{ {
WriteLiteral("<html>"); WriteLiteral("<html>");
WriteLiteral("<body>"); WriteLiteral("<body>");

View File

@ -4,13 +4,16 @@ using Microsoft.AspNet.Mvc.Razor;
namespace MvcSample.Views namespace MvcSample.Views
{ {
[VirtualPath("~/Views/Home/MyView.cshtml")] [VirtualPath("~/Views/Home/MyView.cshtml")]
public class MyView : RazorView public class MyView : RazorView<User>
{ {
protected override void Execute() public override void Execute()
{ {
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
WriteLiteral("<div style=\"border: 1px solid black\">The time is now"); WriteLiteral("<div style=\"border: 1px solid black\">The time is now");
Write(new HtmlString(DateTime.UtcNow.ToString())); Write(new HtmlString(DateTime.UtcNow.ToString()));
WriteLiteral("<em>");
Write(Model.Name);
Write("</em>");
WriteLiteral("</div>"); WriteLiteral("</div>");
} }
} }

View File

@ -0,0 +1,11 @@
<html>
<head>
<title>No title</title>
</head>
<body>
<div style="margin: auto; width: 900px">
<h2>Hello world</h2>
@RenderBody()
</div>
</body>
</html>

View File

@ -3,6 +3,7 @@
<package id="Microsoft.AspNet.WebApi.Client" version="5.0.0" targetFramework="net45" /> <package id="Microsoft.AspNet.WebApi.Client" version="5.0.0" targetFramework="net45" />
<package id="Microsoft.Owin" version="2.0.2" targetFramework="net45" /> <package id="Microsoft.Owin" version="2.0.2" targetFramework="net45" />
<package id="Microsoft.Owin.Diagnostics" version="2.0.2" targetFramework="net45" /> <package id="Microsoft.Owin.Diagnostics" version="2.0.2" targetFramework="net45" />
<package id="Microsoft.Owin.FileSystems" version="2.1.0-rc1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" /> <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" /> <package id="Owin" version="1.0" targetFramework="net45" />
<package id="OwinHost" version="2.0.2" targetFramework="net45" /> <package id="OwinHost" version="2.0.2" targetFramework="net45" />