Adding support for disk based views with code generation and compilation
This commit is contained in:
parent
b918cb8170
commit
0699e7aa40
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.CoreServices
|
||||
{
|
||||
public interface ICompilationService
|
||||
{
|
||||
Task<CompilationResult> Compile(IFileInfo fileInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public interface IVirtualPathFactory
|
||||
{
|
||||
Task<object> CreateInstance(string virtualPath);
|
||||
}
|
||||
}
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public interface IVirtualPathViewFactory
|
||||
{
|
||||
Task<IView> CreateInstance(string virtualPath);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
var viewContext = new ViewContext(context.HttpContext, context.RouteData, Model)
|
||||
{
|
||||
ServiceProvider = _serviceProvider
|
||||
|
||||
};
|
||||
await view.RenderAsync(viewContext, writer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace MvcSample
|
|||
|
||||
public IActionResult MyView()
|
||||
{
|
||||
return Result.View();
|
||||
return Result.View(User());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
|
||||
@DateTime.UtcNow
|
||||
|
|
@ -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>");
|
||||
|
|
|
|||
|
|
@ -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>");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue