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>
</PropertyGroup>
<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.Core" />
<Reference Include="System.Runtime.Caching" />
</ItemGroup>
<ItemGroup>
<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="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceProviderExtensions.cs" />
@ -42,6 +53,9 @@
<ItemGroup>
<Content Include="project.json" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

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>
<HintPath>..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll</HintPath>
</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">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<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.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -48,12 +55,20 @@
</ItemGroup>
<ItemGroup>
<Compile Include="HtmlString.cs" />
<Compile Include="IVirtualPathFactory.cs" />
<Compile Include="MetadataVirtualPathProvider.cs" />
<Compile Include="RazorViewOfT.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="RazorViewEngine.cs" />
<Compile Include="ViewEngine\RazorViewEngine.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>
<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 object Model { get; set; }
public string Layout { get; set; }
protected TextWriter Output { 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);
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)
{
var virtualPathFactory = context.ServiceProvider.GetService<IVirtualPathFactory>();
var virtualPathFactory = context.ServiceProvider.GetService<IVirtualPathViewFactory>();
RazorView razorView = (RazorView)(await virtualPathFactory.CreateInstance(Layout));
if (razorView == null)
{
@ -57,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Razor
await razorView.RenderAsync(context, writer);
}
protected abstract void Execute();
public abstract void Execute();
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
{
public class MetadataVirtualPathProvider : IVirtualPathFactory
public class MetadataVirtualPathViewFactory : IVirtualPathViewFactory
{
private readonly Dictionary<string, Type> _viewMetadata;
public MetadataVirtualPathProvider(Assembly assembly)
public MetadataVirtualPathViewFactory(Assembly assembly)
{
var metadataType = assembly.GetType("ViewMetadata");
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;
object view = null;
IView view = null;
if (_viewMetadata.TryGetValue(virtualPath, out type))
{
view = (RazorView)Activator.CreateInstance(type);
view = (IView)Activator.CreateInstance(type);
}
return Task.FromResult(view);
}

View File

@ -13,10 +13,10 @@ namespace Microsoft.AspNet.Mvc.Razor
"~/Views/Shared/{0}.cshtml",
};
private readonly IActionDescriptorProvider _actionDescriptorProvider;
private readonly IVirtualPathFactory _virtualPathFactory;
private readonly IVirtualPathViewFactory _virtualPathFactory;
public RazorViewEngine(IActionDescriptorProvider actionDescriptorProvider,
IVirtualPathFactory virtualPathFactory)
IVirtualPathViewFactory virtualPathFactory)
{
_actionDescriptorProvider = actionDescriptorProvider;
_virtualPathFactory = virtualPathFactory;
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.Razor
for (int i = 0; i < _viewLocationFormats.Length; i++)
{
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)
{
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"?>
<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.FileSystems" version="2.1.0-rc1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>

View File

@ -36,12 +36,12 @@ namespace Microsoft.AspNet.Mvc
throw new NotImplementedException();
}
public IActionResult View()
public IActionResult View(string view, object model)
{
return new ViewResult(_serviceProvider, _viewEngine)
{
ViewName = null,
Model = null
ViewName = view,
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, string contentType);
IActionResult Json(object value);
IActionResult View();
IActionResult View(string view, object model);
}
}

View File

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

View File

@ -12,8 +12,8 @@ namespace Microsoft.AspNet.Mvc
Model = model;
}
public object Model { get; private 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)
{
ServiceProvider = _serviceProvider
};
await view.RenderAsync(viewContext, writer);
}

View File

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

View File

@ -46,6 +46,10 @@
<Reference Include="Microsoft.Owin.Diagnostics">
<HintPath>..\packages\Microsoft.Owin.Diagnostics.2.0.2\lib\net40\Microsoft.Owin.Diagnostics.dll</HintPath>
</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">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
@ -90,6 +94,8 @@
<ItemGroup>
<Content Include="web.config" />
<Content Include="project.json" />
<Content Include="Views\Home\MyView.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" />
<None Include="web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>

View File

@ -5,6 +5,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
using Microsoft.Owin.FileSystems;
using Owin;
[assembly: OwinStartup(typeof(MvcSample.Startup))]
@ -17,12 +18,17 @@ namespace MvcSample
{
app.UseErrorPage();
string appRoot = Environment.GetEnvironmentVariable("WEB_ROOT") ??
AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
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>();
var handler = new MvcHandler(serviceProvider);
app.Run(async context =>
{
// 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")]
public class Layout : RazorView
{
protected override void Execute()
public override void Execute()
{
WriteLiteral("<html>");
WriteLiteral("<body>");

View File

@ -4,13 +4,16 @@ using Microsoft.AspNet.Mvc.Razor;
namespace MvcSample.Views
{
[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";
WriteLiteral("<div style=\"border: 1px solid black\">The time is now");
Write(new HtmlString(DateTime.UtcNow.ToString()));
WriteLiteral("<em>");
Write(Model.Name);
Write("</em>");
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.Owin" 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="Owin" version="1.0" targetFramework="net45" />
<package id="OwinHost" version="2.0.2" targetFramework="net45" />