Made roslyn based compilation of razor files work
- Now that services are being flowed through the entire stack we can nuke the csc service and always use the roslyn based compilation for views - The RoslynCompilationService is still very much a prototype but it's a good step to see what other services might may or may not need to flow. - Removed the PhysicalFileSystem construction from Mvc itself. This will come from the hosting layer and is newed up temporarily in the PathBasedViewFactory itself.
This commit is contained in:
parent
5de1ae578d
commit
b482a9e3cb
|
|
@ -10,17 +10,12 @@ namespace MvcSample.Web
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly IApplicationEnvironment _env;
|
||||
|
||||
public Startup(IApplicationEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public void Configuration(IBuilder app)
|
||||
{
|
||||
var configuration = new Configuration();
|
||||
var serviceProvider = new ServiceProvider().Add(MvcServices.GetDefaultServices(configuration, _env));
|
||||
var services = MvcServices.GetDefaultServices(configuration);
|
||||
var serviceProvider =
|
||||
DefaultServiceProvider.Create(app.ServiceProvider, services);
|
||||
|
||||
var routes = new RouteCollection()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
},
|
||||
"configurations": {
|
||||
"net45": { },
|
||||
"k10" : { }
|
||||
"k10" : {
|
||||
"dependencies": {
|
||||
"System.Collections" : "4.0.0.0",
|
||||
"System.Runtime" : "4.0.20.0",
|
||||
"System.ComponentModel": "4.0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="K" value="true" />
|
||||
<add key="K" value="false" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<compilation debug="true" targetFramework="4.5" />
|
||||
|
|
|
|||
|
|
@ -13,19 +13,16 @@ namespace MvcSample
|
|||
{
|
||||
const string baseUrl = "http://localhost:9001/";
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IApplicationEnvironment _env;
|
||||
|
||||
public Program(IServiceProvider serviceProvider,
|
||||
IApplicationEnvironment env)
|
||||
public Program(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public void Main()
|
||||
{
|
||||
#if NET45
|
||||
using (WebApp.Start(baseUrl, app => new Startup(_serviceProvider, _env).Configuration(app)))
|
||||
using (WebApp.Start(baseUrl, app => new Startup(_serviceProvider).Configuration(app)))
|
||||
{
|
||||
Console.WriteLine("Listening at {0}", baseUrl);
|
||||
Process.Start(baseUrl);
|
||||
|
|
|
|||
|
|
@ -12,14 +12,11 @@ namespace MvcSample
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IApplicationEnvironment _env;
|
||||
private IServiceProvider _serviceProvider;
|
||||
|
||||
public Startup(IServiceProvider serviceProvider,
|
||||
IApplicationEnvironment env)
|
||||
public Startup(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public void Configuration(IAppBuilder app)
|
||||
|
|
@ -33,11 +30,11 @@ namespace MvcSample
|
|||
private void ConfigureMvc(IBuilder builder)
|
||||
{
|
||||
var configuration = new Configuration();
|
||||
var services = MvcServices.GetDefaultServices(configuration, _env);
|
||||
var serviceProvider = new ServiceProvider().Add(services);
|
||||
var services = MvcServices.GetDefaultServices(configuration);
|
||||
var serviceProvider = new ServiceProvider(_serviceProvider).Add(services);
|
||||
|
||||
serviceProvider.AddInstance<PassThroughAttribute>(new PassThroughAttribute());
|
||||
|
||||
|
||||
var routes = new RouteCollection()
|
||||
{
|
||||
DefaultHandler = new MvcApplication(serviceProvider),
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
#if NET45
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
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(string contents)
|
||||
{
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
string inFile = Path.Combine(_tempDir, Path.GetRandomFileName() + ".cs");
|
||||
string outFile = Path.Combine(_tempDir, Path.GetRandomFileName() + ".dll");
|
||||
StringBuilder args = new StringBuilder("/target:library ");
|
||||
args.AppendFormat("/out:\"{0}\" ", outFile);
|
||||
|
||||
string binDir = Path.Combine(Directory.GetCurrentDirectory(), "bin");
|
||||
// In the k-world, CurrentDir happens to be the bin dir
|
||||
binDir = Directory.Exists(binDir) ? binDir : Directory.GetCurrentDirectory();
|
||||
foreach (var file in Directory.EnumerateFiles(binDir, "*.dll"))
|
||||
{
|
||||
args.AppendFormat("/R:\"{0}\" ", file);
|
||||
}
|
||||
args.AppendFormat("\"{0}\"", inFile);
|
||||
var outputStream = new MemoryStream();
|
||||
|
||||
// common execute
|
||||
Process process = CreateProcess(args.ToString());
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
File.WriteAllText(inFile, contents);
|
||||
exitCode = await Start(process, outputStream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(inFile);
|
||||
}
|
||||
|
||||
|
||||
string output = GetString(outputStream);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
IEnumerable<CompilationMessage> messages = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Skip(3)
|
||||
.Select(e => new CompilationMessage(e));
|
||||
return CompilationResult.Failed(String.Empty, messages);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += (sender, eventArgs) =>
|
||||
{
|
||||
tcs.SetResult(process.ExitCode);
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
var copyTask = process.StandardOutput.BaseStream.CopyToAsync(output);
|
||||
await Task.WhenAll(tcs.Task, copyTask);
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -6,23 +7,22 @@ using System.Threading.Tasks;
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Net.Runtime;
|
||||
using Microsoft.Net.Runtime.Services;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
||||
{
|
||||
public class RoslynCompilationService : ICompilationService
|
||||
{
|
||||
private readonly IMetadataReferenceProvider _provider;
|
||||
private readonly IDependencyExporter _exporter;
|
||||
private readonly IApplicationEnvironment _environment;
|
||||
private readonly IAssemblyLoaderEngine _loader;
|
||||
|
||||
public RoslynCompilationService(IServiceProvider serviceProvider)
|
||||
public RoslynCompilationService(IApplicationEnvironment environment,
|
||||
IAssemblyLoaderEngine loaderEngine,
|
||||
IDependencyExporter exporter)
|
||||
{
|
||||
// TODO: Get these services via ctor injection when we get container chaining implemented
|
||||
_provider = (IMetadataReferenceProvider)serviceProvider.GetService(typeof(IMetadataReferenceProvider));
|
||||
_environment = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment));
|
||||
_loader = (IAssemblyLoaderEngine)serviceProvider.GetService(typeof(IAssemblyLoaderEngine));
|
||||
|
||||
_environment = environment;
|
||||
_loader = loaderEngine;
|
||||
_exporter = exporter;
|
||||
}
|
||||
|
||||
public Task<CompilationResult> Compile(string content)
|
||||
|
|
@ -30,15 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) };
|
||||
var targetFramework = _environment.TargetFramework;
|
||||
|
||||
// Get references from the application itself and from
|
||||
// this assembly for the base types etc
|
||||
var referenceNames = new[] {
|
||||
_environment.ApplicationName,
|
||||
typeof(RoslynCompilationService).GetTypeInfo().Assembly.GetName().Name
|
||||
};
|
||||
|
||||
var references = referenceNames.SelectMany(name => _provider.GetReferences(name, targetFramework))
|
||||
.Cast<MetadataReference>();
|
||||
var references = GetApplicationReferences().ToList();
|
||||
|
||||
var assemblyName = Path.GetRandomFileName();
|
||||
|
||||
|
|
@ -47,24 +39,75 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
syntaxTrees: syntaxTrees,
|
||||
references: references);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var result = compilation.Emit(ms);
|
||||
|
||||
if (!result.Success)
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var formatter = new DiagnosticFormatter();
|
||||
using (var pdb = new MemoryStream())
|
||||
{
|
||||
var result = compilation.Emit(ms, pdbStream: pdb);
|
||||
|
||||
var messages = result.Diagnostics.Where(IsError).Select(d => GetCompilationMessage(formatter, d));
|
||||
if (!result.Success)
|
||||
{
|
||||
var formatter = new DiagnosticFormatter();
|
||||
|
||||
return Task.FromResult(CompilationResult.Failed(content, messages));
|
||||
var messages = result.Diagnostics.Where(IsError).Select(d => GetCompilationMessage(formatter, d));
|
||||
|
||||
return Task.FromResult(CompilationResult.Failed(content, messages));
|
||||
}
|
||||
|
||||
var type = _loader.LoadBytes(ms.ToArray(), pdb.ToArray())
|
||||
.GetExportedTypes()
|
||||
.First();
|
||||
|
||||
return Task.FromResult(CompilationResult.Successful(String.Empty, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataReference> GetApplicationReferences()
|
||||
{
|
||||
var assemblyNames = new[] {
|
||||
_environment.ApplicationName,
|
||||
typeof(RoslynCompilationService).GetTypeInfo().Assembly.GetName().Name
|
||||
};
|
||||
|
||||
return assemblyNames.Select(a => _exporter.GetDependencyExport(a, _environment.TargetFramework))
|
||||
.SelectMany(e => e.MetadataReferences.SelectMany(ConvertMetadataReference));
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataReference> ConvertMetadataReference(IMetadataReference metadataReference)
|
||||
{
|
||||
var fileMetadataReference = metadataReference as IMetadataFileReference;
|
||||
|
||||
if (fileMetadataReference != null)
|
||||
{
|
||||
string path = fileMetadataReference.Path;
|
||||
#if NET45
|
||||
return new[] { new MetadataFileReference(path) };
|
||||
#else
|
||||
// TODO: What about access to the file system? We need to be able to
|
||||
// read files from anywhere on disk, not just under the web root
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
return new[] { new MetadataImageReference(stream) };
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO: Flow loader to this code so we're not using Load() directly
|
||||
var type = _loader.LoadBytes(ms.ToArray(), null)
|
||||
.GetExportedTypes()
|
||||
.First();
|
||||
var roslynReference = metadataReference as IRoslynMetadataReference;
|
||||
|
||||
return Task.FromResult(CompilationResult.Successful(String.Empty, type));
|
||||
if (roslynReference != null)
|
||||
{
|
||||
// REVIEW: We should really only compile against the app's closure
|
||||
var compilatonReference = roslynReference.MetadataReference as CompilationReference;
|
||||
if (compilatonReference != null)
|
||||
{
|
||||
return new[] { compilatonReference }.Concat(compilatonReference.Compilation.References);
|
||||
}
|
||||
|
||||
return new[] { roslynReference.MetadataReference };
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Runtime.Versioning;
|
||||
using Microsoft.Net.Runtime.Services;
|
||||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.Net.Runtime.Services;
|
||||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IDependencyExport
|
||||
{
|
||||
IList<IMetadataReference> MetadataReferences { get; }
|
||||
IList<ISourceReference> SourceReferences { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IDependencyExporter
|
||||
{
|
||||
IDependencyExport GetDependencyExport(string name, FrameworkName targetFramework);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IMetadataFileReference : IMetadataReference
|
||||
{
|
||||
string Path { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IMetadataReference
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Microsoft.Net.Runtime.Services
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IMetadataReferenceProvider
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IRoslynMetadataReference : IMetadataReference
|
||||
{
|
||||
MetadataReference MetadataReference { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Microsoft.Net.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface ISourceReference
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,27 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.Net.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class VirtualPathViewFactory : IVirtualPathViewFactory
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly PhysicalFileSystem _fileSystem;
|
||||
private readonly IRazorCompilationService _compilationService;
|
||||
|
||||
public VirtualPathViewFactory(IFileSystem fileSystem, IRazorCompilationService compilationService)
|
||||
public VirtualPathViewFactory(IApplicationEnvironment env, IRazorCompilationService compilationService)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
// TODO: Continue to inject the IFileSystem but only when we get it from the host
|
||||
_fileSystem = new PhysicalFileSystem(env.ApplicationBasePath);
|
||||
_compilationService = compilationService;
|
||||
}
|
||||
|
||||
public async Task<IView> CreateInstance(string virtualPath)
|
||||
{
|
||||
// TODO: We need to glean the approot from HttpContext
|
||||
var appRoot = ((PhysicalFileSystem)_fileSystem).Root;
|
||||
var appRoot = _fileSystem.Root;
|
||||
|
||||
IFileInfo fileInfo;
|
||||
if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"System.Dynamic.Runtime": "4.0.0.0",
|
||||
"System.Globalization": "4.0.10.0",
|
||||
"System.IO": "4.0.0.0",
|
||||
"System.IO.FileSystem": "4.0.0.0",
|
||||
"System.Linq": "4.0.0.0",
|
||||
"System.Reflection": "4.0.10.0",
|
||||
"System.Reflection.Compatibility": "4.0.0.0",
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.ConfigurationModel;
|
||||
using Microsoft.AspNet.DependencyInjection;
|
||||
using Microsoft.AspNet.DependencyInjection.NestedProviders;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.Net.Runtime;
|
||||
using Microsoft.AspNet.Mvc.Razor.Compilation;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class MvcServices
|
||||
{
|
||||
public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration configuration,
|
||||
IApplicationEnvironment env)
|
||||
public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration configuration)
|
||||
{
|
||||
var describe = new ServiceDescriber(configuration);
|
||||
|
||||
|
|
@ -27,19 +24,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Transient<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>();
|
||||
yield return describe.Transient<IControllerAssemblyProvider, AppDomainControllerAssemblyProvider>();
|
||||
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
||||
yield return describe.Instance<IFileSystem>(new PhysicalFileSystem(env.ApplicationBasePath));
|
||||
|
||||
yield return describe.Instance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorView).FullName));
|
||||
|
||||
#if NET45
|
||||
// TODO: Container chaining to flow services from the host to this container
|
||||
yield return describe.Transient<ICompilationService, RoslynCompilationService>();
|
||||
|
||||
yield return describe.Transient<ICompilationService, CscBasedCompilationService>();
|
||||
|
||||
// TODO: Make this work like normal when we get container chaining
|
||||
// TODO: Update this when we have the new host services
|
||||
// yield return describe.Instance<ICompilationService>(new RoslynCompilationService(hostServiceProvider));
|
||||
#endif
|
||||
yield return describe.Transient<IRazorCompilationService, RazorCompilationService>();
|
||||
yield return describe.Transient<IVirtualPathViewFactory, VirtualPathViewFactory>();
|
||||
yield return describe.Transient<IViewEngine, RazorViewEngine>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue