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:
David Fowler 2014-03-08 15:16:44 -08:00
parent 5de1ae578d
commit b482a9e3cb
19 changed files with 158 additions and 200 deletions

View File

@ -10,17 +10,12 @@ namespace MvcSample.Web
{ {
public class Startup public class Startup
{ {
private readonly IApplicationEnvironment _env;
public Startup(IApplicationEnvironment env)
{
_env = env;
}
public void Configuration(IBuilder app) public void Configuration(IBuilder app)
{ {
var configuration = new Configuration(); 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() var routes = new RouteCollection()
{ {

View File

@ -12,6 +12,12 @@
}, },
"configurations": { "configurations": {
"net45": { }, "net45": { },
"k10" : { } "k10" : {
"dependencies": {
"System.Collections" : "4.0.0.0",
"System.Runtime" : "4.0.20.0",
"System.ComponentModel": "4.0.0.0"
}
}
} }
} }

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<configuration> <configuration>
<appSettings> <appSettings>
<add key="K" value="true" /> <add key="K" value="false" />
</appSettings> </appSettings>
<system.web> <system.web>
<compilation debug="true" targetFramework="4.5" /> <compilation debug="true" targetFramework="4.5" />

View File

@ -13,19 +13,16 @@ namespace MvcSample
{ {
const string baseUrl = "http://localhost:9001/"; const string baseUrl = "http://localhost:9001/";
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IApplicationEnvironment _env;
public Program(IServiceProvider serviceProvider, public Program(IServiceProvider serviceProvider)
IApplicationEnvironment env)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_env = env;
} }
public void Main() public void Main()
{ {
#if NET45 #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); Console.WriteLine("Listening at {0}", baseUrl);
Process.Start(baseUrl); Process.Start(baseUrl);

View File

@ -12,14 +12,11 @@ namespace MvcSample
{ {
public class Startup public class Startup
{ {
private readonly IServiceProvider _serviceProvider; private IServiceProvider _serviceProvider;
private readonly IApplicationEnvironment _env;
public Startup(IServiceProvider serviceProvider, public Startup(IServiceProvider serviceProvider)
IApplicationEnvironment env)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_env = env;
} }
public void Configuration(IAppBuilder app) public void Configuration(IAppBuilder app)
@ -33,11 +30,11 @@ namespace MvcSample
private void ConfigureMvc(IBuilder builder) private void ConfigureMvc(IBuilder builder)
{ {
var configuration = new Configuration(); var configuration = new Configuration();
var services = MvcServices.GetDefaultServices(configuration, _env); var services = MvcServices.GetDefaultServices(configuration);
var serviceProvider = new ServiceProvider().Add(services); var serviceProvider = new ServiceProvider(_serviceProvider).Add(services);
serviceProvider.AddInstance<PassThroughAttribute>(new PassThroughAttribute()); serviceProvider.AddInstance<PassThroughAttribute>(new PassThroughAttribute());
var routes = new RouteCollection() var routes = new RouteCollection()
{ {
DefaultHandler = new MvcApplication(serviceProvider), DefaultHandler = new MvcApplication(serviceProvider),

View File

@ -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

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -6,23 +7,22 @@ using System.Threading.Tasks;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Net.Runtime; using Microsoft.Net.Runtime;
using Microsoft.Net.Runtime.Services;
namespace Microsoft.AspNet.Mvc.Razor.Compilation namespace Microsoft.AspNet.Mvc.Razor.Compilation
{ {
public class RoslynCompilationService : ICompilationService public class RoslynCompilationService : ICompilationService
{ {
private readonly IMetadataReferenceProvider _provider; private readonly IDependencyExporter _exporter;
private readonly IApplicationEnvironment _environment; private readonly IApplicationEnvironment _environment;
private readonly IAssemblyLoaderEngine _loader; 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 _environment = environment;
_provider = (IMetadataReferenceProvider)serviceProvider.GetService(typeof(IMetadataReferenceProvider)); _loader = loaderEngine;
_environment = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); _exporter = exporter;
_loader = (IAssemblyLoaderEngine)serviceProvider.GetService(typeof(IAssemblyLoaderEngine));
} }
public Task<CompilationResult> Compile(string content) public Task<CompilationResult> Compile(string content)
@ -30,15 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) }; var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) };
var targetFramework = _environment.TargetFramework; var targetFramework = _environment.TargetFramework;
// Get references from the application itself and from var references = GetApplicationReferences().ToList();
// 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 assemblyName = Path.GetRandomFileName(); var assemblyName = Path.GetRandomFileName();
@ -47,24 +39,75 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
syntaxTrees: syntaxTrees, syntaxTrees: syntaxTrees,
references: references); references: references);
var ms = new MemoryStream(); using (var ms = new MemoryStream())
var result = compilation.Emit(ms);
if (!result.Success)
{ {
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 roslynReference = metadataReference as IRoslynMetadataReference;
var type = _loader.LoadBytes(ms.ToArray(), null)
.GetExportedTypes()
.First();
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) private CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)

View File

@ -1,5 +1,4 @@
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Microsoft.Net.Runtime.Services;
namespace Microsoft.Net.Runtime namespace Microsoft.Net.Runtime
{ {

View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using Microsoft.Net.Runtime.Services;
namespace Microsoft.Net.Runtime namespace Microsoft.Net.Runtime
{ {

View File

@ -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; }
}
}

View File

@ -0,0 +1,10 @@
using System.Runtime.Versioning;
namespace Microsoft.Net.Runtime
{
[AssemblyNeutral]
public interface IDependencyExporter
{
IDependencyExport GetDependencyExport(string name, FrameworkName targetFramework);
}
}

View File

@ -0,0 +1,8 @@
namespace Microsoft.Net.Runtime
{
[AssemblyNeutral]
public interface IMetadataFileReference : IMetadataReference
{
string Path { get; }
}
}

View File

@ -0,0 +1,8 @@

namespace Microsoft.Net.Runtime
{
[AssemblyNeutral]
public interface IMetadataReference
{
}
}

View File

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Versioning; using System.Runtime.Versioning;
namespace Microsoft.Net.Runtime.Services namespace Microsoft.Net.Runtime
{ {
[AssemblyNeutral] [AssemblyNeutral]
public interface IMetadataReferenceProvider public interface IMetadataReferenceProvider

View File

@ -0,0 +1,10 @@
using Microsoft.CodeAnalysis;
namespace Microsoft.Net.Runtime
{
[AssemblyNeutral]
public interface IRoslynMetadataReference : IMetadataReference
{
MetadataReference MetadataReference { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Microsoft.Net.Runtime
{
[AssemblyNeutral]
public interface ISourceReference
{
}
}

View File

@ -1,24 +1,27 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.FileSystems;
using Microsoft.Net.Runtime;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
public class VirtualPathViewFactory : IVirtualPathViewFactory public class VirtualPathViewFactory : IVirtualPathViewFactory
{ {
private readonly IFileSystem _fileSystem; private readonly PhysicalFileSystem _fileSystem;
private readonly IRazorCompilationService _compilationService; 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; _compilationService = compilationService;
} }
public async Task<IView> CreateInstance(string virtualPath) public async Task<IView> CreateInstance(string virtualPath)
{ {
// TODO: We need to glean the approot from HttpContext // TODO: We need to glean the approot from HttpContext
var appRoot = ((PhysicalFileSystem)_fileSystem).Root; var appRoot = _fileSystem.Root;
IFileInfo fileInfo; IFileInfo fileInfo;
if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo)) if (_fileSystem.TryGetFileInfo(virtualPath, out fileInfo))
{ {

View File

@ -35,6 +35,7 @@
"System.Dynamic.Runtime": "4.0.0.0", "System.Dynamic.Runtime": "4.0.0.0",
"System.Globalization": "4.0.10.0", "System.Globalization": "4.0.10.0",
"System.IO": "4.0.0.0", "System.IO": "4.0.0.0",
"System.IO.FileSystem": "4.0.0.0",
"System.Linq": "4.0.0.0", "System.Linq": "4.0.0.0",
"System.Reflection": "4.0.10.0", "System.Reflection": "4.0.10.0",
"System.Reflection.Compatibility": "4.0.0.0", "System.Reflection.Compatibility": "4.0.0.0",

View File

@ -1,20 +1,17 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.AspNet.ConfigurationModel; using Microsoft.AspNet.ConfigurationModel;
using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.DependencyInjection.NestedProviders; using Microsoft.AspNet.DependencyInjection.NestedProviders;
using Microsoft.AspNet.FileSystems;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor;
using Microsoft.Net.Runtime; using Microsoft.AspNet.Mvc.Razor.Compilation;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public class MvcServices public class MvcServices
{ {
public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration configuration, public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration configuration)
IApplicationEnvironment env)
{ {
var describe = new ServiceDescriber(configuration); var describe = new ServiceDescriber(configuration);
@ -27,19 +24,11 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>(); yield return describe.Transient<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>();
yield return describe.Transient<IControllerAssemblyProvider, AppDomainControllerAssemblyProvider>(); yield return describe.Transient<IControllerAssemblyProvider, AppDomainControllerAssemblyProvider>();
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>(); 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)); yield return describe.Instance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorView).FullName));
#if NET45 yield return describe.Transient<ICompilationService, RoslynCompilationService>();
// TODO: Container chaining to flow services from the host to this container
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<IRazorCompilationService, RazorCompilationService>();
yield return describe.Transient<IVirtualPathViewFactory, VirtualPathViewFactory>(); yield return describe.Transient<IVirtualPathViewFactory, VirtualPathViewFactory>();
yield return describe.Transient<IViewEngine, RazorViewEngine>(); yield return describe.Transient<IViewEngine, RazorViewEngine>();