From 346f02e37c38ebcb5c205a48cb929bb2b590f651 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 2 Mar 2014 01:14:01 -0800 Subject: [PATCH] First stab Roslyn based compilation service for razor pages - Flow the host service provider to MvcServices - Use assembly neutral interfaces to access host services - Added RoslynCompilationService to Microsoft.AspNet.Mvc.Razor - Modified self host sample as helios needs to be updated to flow more services --- samples/MvcSample/Program.cs | 10 ++- samples/MvcSample/Startup.cs | 9 ++- .../Compilation/RoslynCompilationService.cs | 79 +++++++++++++++++++ .../Services/IApplicationEnvironment.cs | 13 +++ .../Services/IMetadataReferenceProvider.cs | 16 ++++ src/Microsoft.AspNet.Mvc.Razor/project.json | 15 +++- .../MvcServices.cs | 20 ++++- 7 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Services/IApplicationEnvironment.cs create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Services/IMetadataReferenceProvider.cs diff --git a/samples/MvcSample/Program.cs b/samples/MvcSample/Program.cs index e15a02fdeb..0bf9ce011e 100644 --- a/samples/MvcSample/Program.cs +++ b/samples/MvcSample/Program.cs @@ -9,11 +9,17 @@ namespace MvcSample public class Program { const string baseUrl = "http://localhost:9001/"; + private readonly IServiceProvider _serviceProvider; - public static void Main() + public Program(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public void Main() { #if NET45 - using (WebApp.Start(new StartOptions(baseUrl))) + using (WebApp.Start(baseUrl, app => new Startup(_serviceProvider).Configuration(app))) { Console.WriteLine("Listening at {0}", baseUrl); Process.Start(baseUrl); diff --git a/samples/MvcSample/Startup.cs b/samples/MvcSample/Startup.cs index 43c071ff1c..5e3c5d1fda 100644 --- a/samples/MvcSample/Startup.cs +++ b/samples/MvcSample/Startup.cs @@ -16,6 +16,13 @@ namespace MvcSample { public class Startup { + private IServiceProvider _serviceProvider; + + public Startup(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public void Configuration(IAppBuilder app) { app.UseErrorPage(); @@ -28,7 +35,7 @@ namespace MvcSample { string appRoot = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; - var mvcServices = new MvcServices(appRoot); + var mvcServices = new MvcServices(appRoot, _serviceProvider); var router = builder.UseRouter(); diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs new file mode 100644 index 0000000000..d36c5592c5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Net.Runtime.Services; + +namespace Microsoft.AspNet.Mvc.Razor.Compilation +{ + public class RoslynCompilationService : ICompilationService + { + private readonly IMetadataReferenceProvider _provider; + private readonly IApplicationEnvironment _environment; + + public RoslynCompilationService(IServiceProvider serviceProvider) + { + // 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)); + } + + public Task Compile(string content) + { + 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(); + + var assemblyName = Path.GetRandomFileName(); + + var compilation = CSharpCompilation.Create(assemblyName, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), + syntaxTrees: syntaxTrees, + references: references); + + var ms = new MemoryStream(); + var result = compilation.Emit(ms); + + if (!result.Success) + { + var messages = result.Diagnostics.Where(IsError).Select(d => GetCompilationMessage(d)); + + return Task.FromResult(CompilationResult.Failed(content, messages)); + } + + // TODO: Flow loader to this code so we're not using Load() directly + var type = Assembly.Load(ms.ToArray()) + .GetExportedTypes() + .First(); + + return Task.FromResult(CompilationResult.Successful(String.Empty, type)); + } + + private CompilationMessage GetCompilationMessage(Diagnostic diagnostic) + { +#if NET45 + var formatter = DiagnosticFormatter.Instance; +#else + var formatter = new DiagnosticFormatter(); +#endif + return new CompilationMessage(formatter.Format(diagnostic)); + } + + private bool IsError(Diagnostic diagnostic) + { + return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Services/IApplicationEnvironment.cs b/src/Microsoft.AspNet.Mvc.Razor/Services/IApplicationEnvironment.cs new file mode 100644 index 0000000000..c691d1b039 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Services/IApplicationEnvironment.cs @@ -0,0 +1,13 @@ +using System.Runtime.Versioning; + +namespace Microsoft.Net.Runtime.Services +{ + [AssemblyNeutral] + public interface IApplicationEnvironment + { + string ApplicationName { get; } + string Version { get; } + string ApplicationBasePath { get; } + FrameworkName TargetFramework { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Services/IMetadataReferenceProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/Services/IMetadataReferenceProvider.cs new file mode 100644 index 0000000000..b78dd62d7a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Services/IMetadataReferenceProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; + +namespace Microsoft.Net.Runtime.Services +{ + [AssemblyNeutral] + public interface IMetadataReferenceProvider + { + // REVIEW: This is object because we don't have a reference to roslyn in this assembly + IEnumerable GetReferences(string name, FrameworkName targetFramework); + } + + [AssemblyNeutral] + public class AssemblyNeutralAttribute : Attribute { } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json index 55b6945cf9..9e0332e2ba 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor/project.json @@ -8,10 +8,21 @@ "Microsoft.AspNet.Mvc" : "", "Microsoft.AspNet.Mvc.ModelBinding" : "", "Microsoft.AspNet.Mvc.Rendering" : "", - "Microsoft.AspNet.Mvc.Razor.Host" : "" + "Microsoft.AspNet.Mvc.Razor.Host" : "", + "Microsoft.CodeAnalysis" : "0.6.31123.2", + "Microsoft.CodeAnalysis.CSharp" : "0.6.31123.2", + "System.Reflection.Metadata.Ecma335": "0.6.31123.2", + "System.Collections.Immutable" : "1.1.15.0" }, "configurations": { - "net45": {}, + "net45" : { + "dependencies": { + "System.Xml.Linq": "", + "System.Xml": "", + "System.Runtime" : "", + "System.Collections" : "" + } + }, "k10" : {} } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs b/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs index 15a59966ff..e8c1e28d95 100644 --- a/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs @@ -1,8 +1,10 @@ -using Microsoft.AspNet.DependencyInjection; +using System; +using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.NestedProviders; using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Razor; +using Microsoft.AspNet.Mvc.Razor.Compilation; namespace Microsoft.AspNet.Mvc.Startup { @@ -11,6 +13,11 @@ namespace Microsoft.AspNet.Mvc.Startup public ServiceProvider Services { get; private set; } public MvcServices(string appRoot) + : this(appRoot, null) + { + } + + public MvcServices(string appRoot, IServiceProvider hostServiceProvider) { Services = new ServiceProvider(); @@ -29,7 +36,16 @@ namespace Microsoft.AspNet.Mvc.Startup AddInstance(new MvcRazorHost(typeof(RazorView).FullName)); #if NET45 - Add(); + // TODO: Container chaining to flow services from the host to this container + if (hostServiceProvider == null) + { + Add(); + } + else + { + // TODO: Make this work like normal when we get container chaining + AddInstance(new RoslynCompilationService(hostServiceProvider)); + } #endif Add(); Add();