diff --git a/.gitignore b/.gitignore index 06cfaa0cbe..1b6e30413e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ nuget.exe *.sln.ide node_modules *launchSettings.json -*.orig \ No newline at end of file +*.orig +BuildInfo.generated.cs \ No newline at end of file diff --git a/MvcPrecompilation.sln b/MvcPrecompilation.sln new file mode 100644 index 0000000000..91c5e92da2 --- /dev/null +++ b/MvcPrecompilation.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj", "{4339FC9B-AEC6-442A-B413-A41555ED76C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{01707B64-7DC7-4B5A-B0BB-7CD2773AA297}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + NuGet.config = NuGet.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02F7AA35-91AF-491E-9F0E-03CFAF86C720}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj", "{F8BF7D95-0633-407F-BB0B-02563F13C068}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0398AFFF-505E-4283-89DA-BBD9D28B53DB}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests", "test\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests.xproj", "{46C9A4B2-8B1C-451B-B670-C194901D66AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.Build.0 = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.Build.0 = Release|Any CPU + {46C9A4B2-8B1C-451B-B670-C194901D66AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46C9A4B2-8B1C-451B-B670-C194901D66AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46C9A4B2-8B1C-451B-B670-C194901D66AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46C9A4B2-8B1C-451B-B670-C194901D66AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4339FC9B-AEC6-442A-B413-A41555ED76C7} = {02F7AA35-91AF-491E-9F0E-03CFAF86C720} + {F8BF7D95-0633-407F-BB0B-02563F13C068} = {02F7AA35-91AF-491E-9F0E-03CFAF86C720} + {46C9A4B2-8B1C-451B-B670-C194901D66AC} = {0398AFFF-505E-4283-89DA-BBD9D28B53DB} + EndGlobalSection +EndGlobal diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json new file mode 100644 index 0000000000..91876a048a --- /dev/null +++ b/NuGetPackageVerifier.json @@ -0,0 +1,16 @@ +{ + "adx": { // Packages written by the ADX team and that ship on NuGet.org + "rules": [ + "AdxVerificationCompositeRule" + ], + "packages": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { }, + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": { } + } + }, + "Default": { // Rules to run for packages not listed in any other set. + "rules": [ + "DefaultCompositeRule" + ] + } +} diff --git a/TestApps.sln b/TestApps.sln new file mode 100644 index 0000000000..cc683f4dd6 --- /dev/null +++ b/TestApps.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SimpleApp", "testapps\SimpleApp\SimpleApp.xproj", "{8FA176ED-C29E-48D6-BC7A-1C3A862CB15F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{43488AEE-CCF2-4A90-B890-05320282BE29}" + ProjectSection(SolutionItems) = preProject + testapps\global.json = testapps\global.json + testapps\NuGet.config = testapps\NuGet.config + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApplicationWithTagHelpers", "testapps\ApplicationWithTagHelpers\ApplicationWithTagHelpers.xproj", "{2079872F-E8F9-4DB1-A340-C0D897807B86}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ClassLibraryTagHelper", "testapps\ClassLibraryTagHelper\ClassLibraryTagHelper.xproj", "{39EFA075-3673-49AB-95F3-AA5E88DF6C30}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApplicationWithConfigureMvc", "testapps\ApplicationWithConfigureMvc\ApplicationWithConfigureMvc.xproj", "{E2EAEB85-91D5-478E-9CE2-964F68DE20D0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FA176ED-C29E-48D6-BC7A-1C3A862CB15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FA176ED-C29E-48D6-BC7A-1C3A862CB15F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FA176ED-C29E-48D6-BC7A-1C3A862CB15F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FA176ED-C29E-48D6-BC7A-1C3A862CB15F}.Release|Any CPU.Build.0 = Release|Any CPU + {2079872F-E8F9-4DB1-A340-C0D897807B86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2079872F-E8F9-4DB1-A340-C0D897807B86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2079872F-E8F9-4DB1-A340-C0D897807B86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2079872F-E8F9-4DB1-A340-C0D897807B86}.Release|Any CPU.Build.0 = Release|Any CPU + {39EFA075-3673-49AB-95F3-AA5E88DF6C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39EFA075-3673-49AB-95F3-AA5E88DF6C30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39EFA075-3673-49AB-95F3-AA5E88DF6C30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39EFA075-3673-49AB-95F3-AA5E88DF6C30}.Release|Any CPU.Build.0 = Release|Any CPU + {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..7d4894cb4a --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/global.json b/global.json index fad3dfeab0..feb51cb71d 100644 --- a/global.json +++ b/global.json @@ -1,3 +1,3 @@ { - "projects": ["src", "test/WebSites", "samples"] + "projects": ["src", "samples"] } diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 0000000000..99c11d9c68 --- /dev/null +++ b/makefile.shade @@ -0,0 +1,9 @@ +use namespace="System.IO" + +-BuildQuality = "preview3"; + +use-standard-lifecycle +k-standard-goals + +#repo-initialize target='initialize' + dotnet command='restore src test ${E("KOREBUILD_DOTNET_RESTORE_OPTIONS")}' workingDir='${Directory.GetCurrentDirectory()}' if='!NoRestore' diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs new file mode 100644 index 0000000000..9540724a97 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class CommonOptions + { + public static readonly string ConfigureCompilationTypeTemplate = "--configure-compilation-type"; + public static readonly string ContentRootTemplate = "--content-root"; + + public CommandArgument ProjectArgument { get; private set; } + + public CommandOption ConfigureCompilationType { get; private set; } + + public CommandOption ContentRootOption { get; private set; } + + public void Configure(CommandLineApplication app) + { + app.Description = "Precompiles an application."; + app.HelpOption("-?|-h|--help"); + + ProjectArgument = app.Argument( + "project", + "The path to the project (project folder or project.json) with precompilation."); + + ConfigureCompilationType = app.Option( + ConfigureCompilationTypeTemplate, + "Type with Configure method", + CommandOptionType.SingleValue); + + ContentRootOption = app.Option( + ContentRootTemplate, + "The application's content root.", + CommandOptionType.SingleValue); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs new file mode 100644 index 0000000000..dde371bb33 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if DEBUG +using System; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public static class DebugHelper + { + public static void HandleDebugSwitch(ref string[] args) + { + if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase)) + { + args = args.Skip(1).ToArray(); + Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue"); + Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); + Console.ReadLine(); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServiceProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServiceProvider.cs new file mode 100644 index 0000000000..7cc105ccc3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServiceProvider.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class MvcServiceProvider + { + private readonly string _projectPath; + private readonly string _contentRoot; + private readonly string _applicationName; + + public MvcServiceProvider( + string projectPath, + string applicationName, + string contentRoot, + string configureCompilationType) + { + _projectPath = projectPath; + _contentRoot = contentRoot; + _applicationName = applicationName; + + var mvcBuilderConfiguration = GetConfigureCompilationAction(configureCompilationType); + var serviceProvider = GetProvider(mvcBuilderConfiguration); + + Host = serviceProvider.GetRequiredService(); + Compiler = serviceProvider.GetRequiredService(); + ViewEngineOptions = serviceProvider.GetRequiredService>().Value; + FileProvider = serviceProvider.GetRequiredService().FileProvider; + } + + public IMvcRazorHost Host { get; } + + public CSharpCompiler Compiler { get; } + + public IFileProvider FileProvider { get; } + + public RazorViewEngineOptions ViewEngineOptions { get; } + + private IDesignTimeMvcBuilderConfiguration GetConfigureCompilationAction(string configureCompilationType) + { + Type type; + if (!string.IsNullOrEmpty(configureCompilationType)) + { + type = Type.GetType(configureCompilationType); + if (type == null) + { + throw new InvalidOperationException($"Unable to find type '{type}."); + } + } + else + { + var assemblyName = new AssemblyName(_applicationName); + var assembly = Assembly.Load(assemblyName); + type = assembly + .GetExportedTypes() + .FirstOrDefault(typeof(IDesignTimeMvcBuilderConfiguration).IsAssignableFrom); + } + + if (type == null) + { + return null; + } + + var instance = Activator.CreateInstance(type) as IDesignTimeMvcBuilderConfiguration; + if (instance == null) + { + throw new InvalidOperationException($"Type {configureCompilationType} does not implement " + + $"{typeof(IDesignTimeMvcBuilderConfiguration)}."); + } + + return instance; + } + + private IServiceProvider GetProvider(IDesignTimeMvcBuilderConfiguration mvcBuilderConfiguration) + { + var services = new ServiceCollection(); + + var hostingEnvironment = new HostingEnvironment + { + ApplicationName = _applicationName, + WebRootFileProvider = new PhysicalFileProvider(_projectPath), + ContentRootFileProvider = new PhysicalFileProvider(_contentRoot), + ContentRootPath = _contentRoot, + }; + var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); + + services + .AddSingleton(hostingEnvironment) + .AddSingleton(diagnosticSource) + .AddLogging() + .AddSingleton(); + + var mvcCoreBuilder = services + .AddMvcCore() + .AddRazorViewEngine(); + + var mvcBuilder = new MvcBuilder(mvcCoreBuilder.Services, mvcCoreBuilder.PartManager); + mvcBuilderConfiguration?.ConfigureMvc(mvcBuilder); + + return mvcBuilder.Services.BuildServiceProvider(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs new file mode 100644 index 0000000000..72e4861055 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class PrecompilationApplication : CommandLineApplication + { + private readonly Type _callingType; + + public PrecompilationApplication(Type callingType) + { + _callingType = callingType; + + Name = "razor-precompile"; + FullName = "Microsoft Razor Precompilation Utility"; + Description = "Precompiles Razor views."; + ShortVersionGetter = GetInformationalVersion; + + HelpOption("-?|-h|--help"); + + OnExecute(() => + { + ShowHelp(); + return 2; + }); + } + + public new int Execute(params string[] args) + { + try + { + return base.Execute(args); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.Message); +#if DEBUG + Console.Error.WriteLine(ex); +#endif + return 1; + } + } + + private string GetInformationalVersion() + { + var assembly = _callingType.GetTypeInfo().Assembly; + var attributes = assembly.GetCustomAttributes( + typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute[]; + + var versionAttribute = attributes.Length == 0 ? + assembly.GetName().Version.ToString() : + attributes[0].InformationalVersion; + + return versionAttribute; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs new file mode 100644 index 0000000000..1e599b1a81 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -0,0 +1,244 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +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; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class PrecompileRunCommand + { + public static readonly string ApplicationNameTemplate = "--applicationName"; + public static readonly string OutputPathTemplate = "--output-path"; + private static readonly ParallelOptions ParalellOptions = new ParallelOptions + { + MaxDegreeOfParallelism = 4 + }; + + private CommandOption OutputPathOption { get; set; } + + private CommandOption ApplicationNameOption { get; set; } + + private MvcServiceProvider MvcServiceProvider { get; set; } + + private CommonOptions Options { get; } = new CommonOptions(); + + private StrongNameOptions StrongNameOptions { get; } = new StrongNameOptions(); + + private string ProjectPath { get; set; } + + public void Configure(CommandLineApplication app) + { + Options.Configure(app); + StrongNameOptions.Configure(app); + + OutputPathOption = app.Option( + OutputPathTemplate, + "Path to the emit the precompiled assembly to.", + CommandOptionType.SingleValue); + + ApplicationNameOption = app.Option( + ApplicationNameTemplate, + "Name of the application to produce precompiled assembly for.", + CommandOptionType.SingleValue); + + app.OnExecute(() => Execute()); + } + + private int Execute() + { + ParseArguments(); + + MvcServiceProvider = new MvcServiceProvider( + ProjectPath, + ApplicationNameOption.Value(), + Options.ContentRootOption.Value(), + Options.ConfigureCompilationType.Value()); + + Console.WriteLine("Running Razor view precompilation."); + + var stopWatch = Stopwatch.StartNew(); + var results = GenerateCode(); + var success = true; + foreach (var result in results) + { + if (!result.GeneratorResults.Success) + { + success = false; + foreach (var error in result.GeneratorResults.ParserErrors) + { + Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + } + } + } + + if (!success) + { + return 1; + } + + var precompileAssemblyName = $"{ApplicationNameOption.Value()}{AssemblyPart.PrecompiledViewsAssemblySuffix}"; + var compilation = CompileViews(results, precompileAssemblyName); + + var assemblyPath = Path.Combine(OutputPathOption.Value(), precompileAssemblyName + ".dll"); + var emitResult = EmitAssembly(compilation, assemblyPath); + + if (!emitResult.Success) + { + foreach (var diagnostic in emitResult.Diagnostics) + { + Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(diagnostic)); + } + + return 1; + } + + stopWatch.Stop(); + Console.WriteLine($"Precompiled views emitted to {assemblyPath}."); + Console.WriteLine($"Successfully compiled {results.Length} Razor views in {stopWatch.ElapsedMilliseconds}ms."); + return 0; + } + + private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPath) + { + Directory.CreateDirectory(Path.GetDirectoryName(assemblyPath)); + + EmitResult emitResult; + using (var assemblyStream = File.OpenWrite(assemblyPath)) + { + using (var pdbStream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ".pdb"))) + { + emitResult = compilation.Emit( + assemblyStream, + pdbStream, + options: MvcServiceProvider.Compiler.EmitOptions); + } + } + + return emitResult; + } + + private CSharpCompilation CompileViews(ViewCompilationInfo[] results, string assemblyname) + { + var compiler = MvcServiceProvider.Compiler; + var compilation = compiler.CreateCompilation(assemblyname); + var syntaxTrees = new SyntaxTree[results.Length]; + + Parallel.For(0, results.Length, ParalellOptions, i => + { + var result = results[i]; + var sourceText = SourceText.From(result.GeneratorResults.GeneratedCode, Encoding.UTF8); + var fileInfo = result.RelativeFileInfo; + var syntaxTree = compiler.CreateSyntaxTree(sourceText) + .WithFilePath(fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath); + syntaxTrees[i] = syntaxTree; + }); + + compilation = compilation.AddSyntaxTrees(syntaxTrees); + Parallel.For(0, results.Length, ParalellOptions, i => + { + results[i].TypeName = ReadTypeInfo(compilation, syntaxTrees[i]); + }); + + // Post process the compilation - run ExpressionRewritter and any user specified callbacks. + compilation = ExpressionRewriter.Rewrite(compilation); + var compilationContext = new RoslynCompilationContext(compilation); + MvcServiceProvider.ViewEngineOptions.CompilationCallback(compilationContext); + compilation = compilationContext.Compilation; + + var codeGenerator = new ViewInfoContainerCodeGenerator(compiler, compilation); + codeGenerator.AddViewFactory(results); + + var assemblyName = new AssemblyName(ApplicationNameOption.Value()); + assemblyName = Assembly.Load(assemblyName).GetName(); + codeGenerator.AddAssemblyMetadata(assemblyName, StrongNameOptions); + + return codeGenerator.Compilation; + } + + private void ParseArguments() + { + ProjectPath = Options.ProjectArgument.Value; + if (string.IsNullOrEmpty(ProjectPath)) + { + throw new ArgumentException("Project path not specified."); + } + + if (!OutputPathOption.HasValue()) + { + throw new ArgumentException($"Option {OutputPathTemplate} does not specify a value."); + } + + if (!ApplicationNameOption.HasValue()) + { + throw new ArgumentException($"Option {ApplicationNameTemplate} does not specify a value."); + } + } + + private ViewCompilationInfo[] GenerateCode() + { + var files = new List(); + GetRazorFiles(MvcServiceProvider.FileProvider, files, root: string.Empty); + var results = new ViewCompilationInfo[files.Count]; + Parallel.For(0, results.Length, ParalellOptions, i => + { + var fileInfo = files[i]; + using (var fileStream = fileInfo.FileInfo.CreateReadStream()) + { + var result = MvcServiceProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream); + results[i] = new ViewCompilationInfo(fileInfo, result); + } + }); + + return results; + } + + private static void GetRazorFiles(IFileProvider fileProvider, List razorFiles, string root) + { + foreach (var fileInfo in fileProvider.GetDirectoryContents(root)) + { + var relativePath = Path.Combine(root, fileInfo.Name); + if (fileInfo.IsDirectory) + { + GetRazorFiles(fileProvider, razorFiles, relativePath); + } + else if (fileInfo.Name.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) + { + razorFiles.Add(new RelativeFileInfo(fileInfo, relativePath)); + } + } + } + + private string ReadTypeInfo(CSharpCompilation compilation, SyntaxTree syntaxTree) + { + var semanticModel = compilation.GetSemanticModel(syntaxTree, ignoreAccessibility: true); + var classDeclarations = syntaxTree.GetRoot().DescendantNodes().OfType(); + foreach (var declaration in classDeclarations) + { + var typeSymbol = semanticModel.GetDeclaredSymbol(declaration); + if (typeSymbol.ContainingType == null && typeSymbol.DeclaredAccessibility == Accessibility.Public) + { + return typeSymbol.ToDisplayString(); + } + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs new file mode 100644 index 0000000000..db72ea864c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.IO; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + // Copied from https://github.com/dotnet/cli/blob/rel/1.0.0/src/Microsoft.DotNet.ProjectModel.Workspaces/SnkUtils.cs + public static class SnkUtils + { + const byte PUBLICKEYBLOB = 0x06; + const byte PRIVATEKEYBLOB = 0x07; + + private const uint CALG_RSA_SIGN = 0x00002400; + private const uint CALG_SHA = 0x00008004; + + private const uint RSA1 = 0x31415352; //"RSA1" publickeyblob + private const uint RSA2 = 0x32415352; //"RSA2" privatekeyblob + + private const int VersionOffset = 1; + private const int ModulusLengthOffset = 12; + private const int ExponentOffset = 16; + private const int MagicPrivateKeyOffset = 8; + private const int MagicPublicKeyOffset = 20; + + public static ImmutableArray ExtractPublicKey(byte[] snk) + { + ValidateBlob(snk); + + if (snk[0] != PRIVATEKEYBLOB) + { + return ImmutableArray.Create(snk); + } + + var version = snk[VersionOffset]; + int modulusBitLength = ReadInt32(snk, ModulusLengthOffset); + uint exponent = (uint)ReadInt32(snk, ExponentOffset); + var modulus = new byte[modulusBitLength >> 3]; + + Array.Copy(snk, 20, modulus, 0, modulus.Length); + + return CreatePublicKey(version, exponent, modulus); + } + + private static void ValidateBlob(byte[] snk) + { + // 160 - the size of public key + if (snk.Length >= 160) + { + if (snk[0] == PRIVATEKEYBLOB && ReadInt32(snk, MagicPrivateKeyOffset) == RSA2 || // valid private key + snk[12] == PUBLICKEYBLOB && ReadInt32(snk, MagicPublicKeyOffset) == RSA1) // valid public key + { + return; + } + } + + throw new InvalidOperationException("Invalid key file."); + } + + private static int ReadInt32(byte[] array, int index) + { + return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24; + } + + private static ImmutableArray CreatePublicKey(byte version, uint exponent, byte[] modulus) + { + using (var ms = new MemoryStream(160)) + using (var binaryWriter = new BinaryWriter(ms)) + { + binaryWriter.Write(CALG_RSA_SIGN); + binaryWriter.Write(CALG_SHA); + // total size of the rest of the blob (20 - size of RSAPUBKEY) + binaryWriter.Write(modulus.Length + 20); + binaryWriter.Write(PUBLICKEYBLOB); + binaryWriter.Write(version); + binaryWriter.Write((ushort)0x00000000); // reserved + binaryWriter.Write(CALG_RSA_SIGN); + binaryWriter.Write(RSA1); + binaryWriter.Write(modulus.Length << 3); + binaryWriter.Write(exponent); + binaryWriter.Write(modulus); + return ImmutableArray.Create(ms.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs new file mode 100644 index 0000000000..e079707018 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class StrongNameOptions + { + public static readonly string StrongNameKeyPath = "--key-file"; + public static readonly string DelaySignTemplate = "--delay-sign"; + public static readonly string PublicSignTemplate = "--public-sign"; + + public CommandOption KeyFileOption { get; set; } + + public CommandOption DelaySignOption { get; private set; } + + public CommandOption PublicSignOption { get; private set; } + + public void Configure(CommandLineApplication app) + { + KeyFileOption = app.Option( + StrongNameKeyPath, + "Strong name key path", + CommandOptionType.SingleValue); + + DelaySignOption = app.Option( + DelaySignTemplate, + "Determines if the precompiled view assembly is to be delay signed.", + CommandOptionType.NoValue); + + PublicSignOption = app.Option( + PublicSignTemplate, + "Determines if the precompiled view assembly is to be public signed.", + CommandOptionType.NoValue); + } + + public string KeyFile => KeyFileOption.Value(); + + public bool DelaySign => DelaySignOption.HasValue(); + + public bool PublicSign => PublicSignOption.HasValue(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs new file mode 100644 index 0000000000..f2ac3b44e3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.CodeGenerators; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class ViewCompilationInfo + { + public ViewCompilationInfo( + RelativeFileInfo relativeFileInfo, + GeneratorResults generatorResults) + { + RelativeFileInfo = relativeFileInfo; + GeneratorResults = generatorResults; + } + + public RelativeFileInfo RelativeFileInfo { get; } + + public GeneratorResults GeneratorResults { get; } + + public string TypeName { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewInfoContainerCodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewInfoContainerCodeGenerator.cs new file mode 100644 index 0000000000..00619f6ada --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewInfoContainerCodeGenerator.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class ViewInfoContainerCodeGenerator + { + public ViewInfoContainerCodeGenerator( + CSharpCompiler compiler, + CSharpCompilation compilation) + { + Compiler = compiler; + Compilation = compilation; + } + + public CSharpCompiler Compiler { get; } + + public CSharpCompilation Compilation { get; private set; } + + public void AddViewFactory(IList result) + { + var precompiledViewsArray = new StringBuilder(); + foreach (var item in result) + { + var path = item.RelativeFileInfo.RelativePath; + precompiledViewsArray.AppendLine( + $"new global::{typeof(ViewInfo).FullName}(@\"{path}\", typeof({item.TypeName})),"); + } + + var factoryContent = $@" +namespace {AssemblyPart.ViewInfoContainerNamespace} +{{ + public class {AssemblyPart.ViewInfoContainerTypeName} : global::{typeof(ViewInfoContainer).FullName} + {{ + public {AssemblyPart.ViewInfoContainerTypeName}() : base(new[] + {{ + {precompiledViewsArray} + }}) + {{ + }} + }} +}}"; + var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(factoryContent)); + Compilation = Compilation.AddSyntaxTrees(syntaxTree); + } + + public void AddAssemblyMetadata( + AssemblyName applicationAssemblyName, + StrongNameOptions strongNameOptions) + { + if (!string.IsNullOrEmpty(strongNameOptions.KeyFile)) + { + var updatedOptions = Compilation.Options.WithStrongNameProvider(new DesktopStrongNameProvider()); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || strongNameOptions.PublicSign) + { + updatedOptions = updatedOptions.WithCryptoPublicKey( + SnkUtils.ExtractPublicKey(File.ReadAllBytes(strongNameOptions.KeyFile))); + } + else + { + updatedOptions = updatedOptions.WithCryptoKeyFile(strongNameOptions.KeyFile) + .WithDelaySign(strongNameOptions.DelaySign); + } + + Compilation = Compilation.WithOptions(updatedOptions); + } + + var assemblyVersionContent = $"[assembly:{typeof(AssemblyVersionAttribute).FullName}(\"{applicationAssemblyName.Version}\")]"; + var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(assemblyVersionContent)); + Compilation = Compilation.AddSyntaxTrees(syntaxTree); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj new file mode 100644 index 0000000000..253d7491f8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj @@ -0,0 +1,18 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4339fc9b-aec6-442a-b413-a41555ed76c7 + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs new file mode 100644 index 0000000000..197fcefc7b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design +{ + public class Program + { + private readonly static Type ProgramType = typeof(Program); + + public static int Main(string[] args) + { +#if DEBUG + DebugHelper.HandleDebugSwitch(ref args); +#endif + + EnsureValidDispatchRecipient(ref args); + + var app = new PrecompilationApplication(ProgramType); + new PrecompileRunCommand().Configure(app); + return app.Execute(args); + } + + private static void EnsureValidDispatchRecipient(ref string[] args) + { + const string DispatcherVersionArgumentName = "--dispatcher-version"; + + var dispatcherArgumentIndex = Array.FindIndex( + args, + (value) => string.Equals(value, DispatcherVersionArgumentName, StringComparison.OrdinalIgnoreCase)); + + if (dispatcherArgumentIndex < 0) + { + return; + } + + var dispatcherArgumentValueIndex = dispatcherArgumentIndex + 1; + if (dispatcherArgumentValueIndex < args.Length) + { + var dispatcherVersion = args[dispatcherArgumentValueIndex]; + + var thisAssembly = ProgramType.GetTypeInfo().Assembly; + var version = thisAssembly.GetCustomAttribute() + ?.InformationalVersion + ?? thisAssembly.GetName().Version.ToString(); + + if (string.Equals(dispatcherVersion, version, StringComparison.Ordinal)) + { + // Remove dispatcher arguments from + var preDispatcherArgument = args.Take(dispatcherArgumentIndex); + var postDispatcherArgument = args.Skip(dispatcherArgumentIndex + 2); + var newProgramArguments = preDispatcherArgument.Concat(postDispatcherArgument); + args = newProgramArguments.ToArray(); + return; + } + } + + // Could not validate the dispatcher version. + throw new InvalidOperationException("Could not invoke tool"); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2dc4003a17 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json new file mode 100644 index 0000000000..26565cfcec --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json @@ -0,0 +1,41 @@ +{ + "version": "1.0.0-*", + "description": "Razor precompilation", + "packOptions": { + "repository": { + "type": "git", + "url": "git://github.com/AspNet/Mvc" + }, + "tags": [ + "cshtml", + "razor", + "precompilation", + "aspnetcore" + ] + }, + "buildOptions": { + "keyFile": "../../tools/Key.snk", + "warningsAsErrors": true, + "emitEntryPoint": true, + "nowarn": [ + "CS1591" + ], + "xmlDoc": true + }, + "dependencies": { + "Microsoft.AspNetCore.Hosting": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor": "1.1.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.1.0-*" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-*" + } + } + }, + "net451": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs new file mode 100644 index 0000000000..7a95538c45 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -0,0 +1,188 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; +using Microsoft.DotNet.InternalAbstractions; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Internal; +using NuGet.Frameworks; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class PrecompileDispatchCommand + { + private CommonOptions Options { get; } = new CommonOptions(); + + private CommandOption FrameworkOption { get; set; } + + private CommandOption ConfigurationOption { get; set; } + + private CommandOption OutputPathOption { get; set; } + + private NuGetFramework TargetFramework { get; set; } + + private CommandOption BuildBasePathOption { get; set; } + + private string ProjectPath { get; set; } + + private string Configuration { get; set; } + + private string OutputPath { get; set; } + + public void Configure(CommandLineApplication app) + { + Options.Configure(app); + FrameworkOption = app.Option( + "-f|--framework", + "Target Framework", + CommandOptionType.SingleValue); + + ConfigurationOption = app.Option( + "-c|--configuration", + "Configuration", + CommandOptionType.SingleValue); + + OutputPathOption = app.Option( + "-o|--output-path", + "Published path of the application.", + CommandOptionType.SingleValue); + + app.OnExecute(() => Execute()); + } + + private int Execute() + { + ParseArguments(); + + var runtimeContext = GetRuntimeContext(); + + var outputPaths = runtimeContext.GetOutputPaths(Configuration); + var applicationName = Path.GetFileNameWithoutExtension(outputPaths.CompilationFiles.Assembly); + var dispatchArgs = new List + { + ProjectPath, + PrecompileRunCommand.ApplicationNameTemplate, + applicationName, + PrecompileRunCommand.OutputPathTemplate, + OutputPath, + CommonOptions.ContentRootTemplate, + Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(), + }; + + if (Options.ConfigureCompilationType.HasValue()) + { + dispatchArgs.Add(CommonOptions.ConfigureCompilationTypeTemplate); + dispatchArgs.Add(Options.ConfigureCompilationType.Value()); + } + + var compilerOptions = runtimeContext.ProjectFile.GetCompilerOptions(TargetFramework, Configuration); + if (!string.IsNullOrEmpty(compilerOptions.KeyFile)) + { + dispatchArgs.Add(StrongNameOptions.StrongNameKeyPath); + var keyFilePath = Path.GetFullPath(Path.Combine(runtimeContext.ProjectDirectory, compilerOptions.KeyFile)); + dispatchArgs.Add(keyFilePath); + + if (compilerOptions.DelaySign ?? false) + { + dispatchArgs.Add(StrongNameOptions.DelaySignTemplate); + } + + if (compilerOptions.PublicSign ?? false) + { + dispatchArgs.Add(StrongNameOptions.PublicSignTemplate); + } + } + +#if DEBUG + var commandLineArgs = Environment.GetCommandLineArgs(); + if (commandLineArgs.Length > 0 && commandLineArgs[0] == "--debug") + { + dispatchArgs.Insert(0, commandLineArgs[0]); + } +#endif + + var toolName = typeof(Design.Program).GetTypeInfo().Assembly.GetName().Name; + var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand( + dispatchArgs, + TargetFramework, + Configuration, + outputPath: outputPaths.RuntimeOutputPath, + buildBasePath: null, + projectDirectory: ProjectPath, + toolName: toolName); + + var commandExitCode = dispatchCommand + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute() + .ExitCode; + + return commandExitCode; + } + + private void ParseArguments() + { + ProjectPath = GetProjectPath(); + Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; + + if (!FrameworkOption.HasValue()) + { + throw new Exception($"Option {FrameworkOption.Template} does not have a value."); + } + TargetFramework = NuGetFramework.Parse(FrameworkOption.Value()); + + if (!OutputPathOption.HasValue()) + { + throw new Exception($"Option {OutputPathOption.Template} does not have a value."); + } + OutputPath = OutputPathOption.Value(); + } + + private string GetProjectPath() + { + string projectPath; + if (!string.IsNullOrEmpty(Options.ProjectArgument.Value)) + { + projectPath = Path.GetFullPath(Options.ProjectArgument.Value); + if (string.Equals(Path.GetFileName(ProjectPath), "project.json", StringComparison.OrdinalIgnoreCase)) + { + projectPath = Path.GetDirectoryName(ProjectPath); + } + + if (!Directory.Exists(projectPath)) + { + throw new InvalidOperationException($"Could not find directory {projectPath}."); + } + } + else + { + projectPath = Directory.GetCurrentDirectory(); + } + + return projectPath; + } + + private ProjectContext GetRuntimeContext() + { + var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); + + var projectContext = workspace.GetProjectContext(ProjectPath, TargetFramework); + if (projectContext == null) + { + Debug.Assert(FrameworkOption.HasValue()); + throw new InvalidOperationException($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); + } + + var runtimeContext = workspace.GetRuntimeContext( + projectContext, + RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); + return runtimeContext; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj new file mode 100644 index 0000000000..6b5961a59f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj @@ -0,0 +1,18 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + f8bf7d95-0633-407f-bb0b-02563f13c068 + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs new file mode 100644 index 0000000000..3ca5029d70 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools +{ + public class Program + { + public static int Main(string[] args) + { +#if DEBUG + DebugHelper.HandleDebugSwitch(ref args); +#endif + + var app = new PrecompilationApplication(typeof(Program)); + new PrecompileDispatchCommand().Configure(app); + return app.Execute(args); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2dc4003a17 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json new file mode 100644 index 0000000000..e19f3cf655 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json @@ -0,0 +1,49 @@ +{ + "version": "1.0.0-*", + "description": "Razor precompilation", + "packOptions": { + "repository": { + "type": "git", + "url": "git://github.com/AspNet/Mvc" + }, + "tags": [ + "cshtml", + "razor", + "precompilation", + "aspnetcore" + ] + }, + "buildOptions": { + "outputName": "dotnet-razor-precompile", + "keyFile": "../../tools/Key.snk", + "warningsAsErrors": true, + "emitEntryPoint": true, + "nowarn": [ + "CS1591" + ], + + "xmlDoc": true + }, + "dependencies": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { "target": "project" }, + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", + "Microsoft.Extensions.DotnetToolDispatcher.Sources": { + "type": "build", + "version": "1.1.0-*" + } + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.DotNet.ProjectModel.Loader": "1.0.0-*", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-*" + }, + "System.Runtime.Serialization.Primitives": "4.1.1-*" + } + }, + "net451": {} + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithConfigureMvcTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithConfigureMvcTest.cs new file mode 100644 index 0000000000..f19c4d15fd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithConfigureMvcTest.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests +{ + public class ApplicationWithConfigureMvcTest + : IClassFixture + { + public ApplicationWithConfigureMvcTest(ApplicationWithConfigureMvcFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + public static IEnumerable ApplicationWithTagHelpersData + { + get + { + var runtimeFlavors = new[] + { + RuntimeFlavor.Clr, + RuntimeFlavor.CoreClr, + }; + + var urls = new[] + { + "Index", + "ViewWithPreprocessor", + }; + + return Enumerable.Zip(urls, runtimeFlavors, (a, b) => new object[] { a, b }); + } + } + + [Theory] + [InlineData(RuntimeFlavor.Clr)] + [InlineData(RuntimeFlavor.CoreClr)] + public async Task Precompilation_RunsConfiguredCompilationCallbacks(RuntimeFlavor flavor) + { + // Arrange + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var httpClient = new HttpClient() + { + BaseAddress = new Uri(deploymentResult.ApplicationBaseUri) + }; + + // Act + var response = await httpClient.GetStringAsync(""); + + // Assert + TestEmbeddedResource.AssertContent("ApplicationWithConfigureMvc.Home.Index.txt", response); + } + } + + [Theory] + [InlineData(RuntimeFlavor.Clr)] + [InlineData(RuntimeFlavor.CoreClr)] + public async Task Precompilation_UsesConfiguredParseOptions(RuntimeFlavor flavor) + { + // Arrange + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var httpClient = new HttpClient() + { + BaseAddress = new Uri(deploymentResult.ApplicationBaseUri) + }; + + // Act + var response = await httpClient.GetStringAsync("Home/ViewWithPreprocessor"); + + // Assert + TestEmbeddedResource.AssertContent( + "ApplicationWithConfigureMvc.Home.ViewWithPreprocessor.txt", + response); + } + } + + public class ApplicationWithConfigureMvcFixture : ApplicationTestFixture + { + public ApplicationWithConfigureMvcFixture() + : base("ApplicationWithConfigureMvc") + { + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithTagHelpersTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithTagHelpersTest.cs new file mode 100644 index 0000000000..b2fddca721 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationWithTagHelpersTest.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public class TagHelperTest : IClassFixture + { + public TagHelperTest(ApplicationWithTagHelpersFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + public static IEnumerable ApplicationWithTagHelpersData + { + get + { + var runtimeFlavors = new[] + { + RuntimeFlavor.Clr, + RuntimeFlavor.CoreClr, + }; + + var urls = new[] + { + "ClassLibraryTagHelper", + "LocalTagHelper", + "NuGetPackageTagHelper", + }; + + return Enumerable.Zip(urls, runtimeFlavors, (a, b) => new object[] { a, b }); + } + } + + [Theory] + [MemberData(nameof(ApplicationWithTagHelpersData))] + public async Task Precompilation_WorksForViewsThatUseTagHelpers(string url, RuntimeFlavor flavor) + { + // Arrange + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var httpClient = new HttpClient() + { + BaseAddress = new Uri(deploymentResult.ApplicationBaseUri) + }; + + // Act + var response = await httpClient.GetStringAsync($"Home/{url}"); + + // Assert + TestEmbeddedResource.AssertContent($"ApplicationWithTagHelpers.Home.{url}.txt", response); + } + } + + public class ApplicationWithTagHelpersFixture : ApplicationTestFixture + { + public ApplicationWithTagHelpersFixture() + : base("ApplicationWithTagHelpers") + { + } + + protected override void Restore() + { + RestoreProject(Path.GetFullPath(Path.Combine(ApplicationPath, "..", "ClassLibraryTagHelper"))); + base.Restore(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationPaths.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationPaths.cs new file mode 100644 index 0000000000..d56cb498cd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationPaths.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public static class ApplicationPaths + { + private const string SolutionName = "MvcPrecompilation.sln"; + + public static string SolutionDirectory { get; } = GetSolutionDirectory(); + + public static string ArtifactPackagesDirectory => Path.Combine(SolutionDirectory, "artifacts", "build"); + + public static string GetTestAppDirectory(string appName) => + Path.Combine(SolutionDirectory, "testapps", appName); + + private static string GetSolutionDirectory() + { + var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath; + + var directoryInfo = new DirectoryInfo(applicationBasePath); + do + { + var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, SolutionName)); + if (solutionFileInfo.Exists) + { + return directoryInfo.FullName; + } + + directoryInfo = directoryInfo.Parent; + } while (directoryInfo.Parent != null); + + throw new InvalidOperationException($"Solution directory could not be found for {applicationBasePath}."); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs new file mode 100644 index 0000000000..0223bc9e5b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public abstract class ApplicationTestFixture : IDisposable + { + public const string NuGetPackagesEnvironmentKey = "NUGET_PACKAGES"; + private readonly string _oldRestoreDirectory; + private bool _isRestored; + + protected ApplicationTestFixture(string applicationName) + { + ApplicationName = applicationName; + _oldRestoreDirectory = Environment.GetEnvironmentVariable(NuGetPackagesEnvironmentKey); + } + + public string ApplicationName { get; } + + public string ApplicationPath => ApplicationPaths.GetTestAppDirectory(ApplicationName); + + public string TempRestoreDirectory { get; } = CreateTempRestoreDirectory(); + + public IApplicationDeployer CreateDeployment(RuntimeFlavor flavor) + { + if (!_isRestored) + { + Restore(); + _isRestored = true; + } + + var tempRestoreDirectoryEnvironment = new KeyValuePair( + NuGetPackagesEnvironmentKey, + TempRestoreDirectory); + + var deploymentParameters = new DeploymentParameters( + ApplicationPath, + ServerType.Kestrel, + flavor, + RuntimeArchitecture.x64) + { + PublishApplicationBeforeDeployment = true, + TargetFramework = flavor == RuntimeFlavor.Clr ? "net451" : "netcoreapp1.0", + Configuration = "Release", + EnvironmentVariables = + { + tempRestoreDirectoryEnvironment + }, + PublishEnvironmentVariables = + { + tempRestoreDirectoryEnvironment + }, + }; + + var logger = new LoggerFactory() + .AddConsole() + .CreateLogger($"{ApplicationName}:{flavor}"); + + return ApplicationDeployerFactory.Create(deploymentParameters, logger); + } + + protected virtual void Restore() + { + RestoreProject(ApplicationPath); + } + + public void Dispose() + { + try + { + Directory.Delete(TempRestoreDirectory, recursive: true); + } + catch (IOException) + { + // Ignore delete failures. + } + } + + protected void RestoreProject(string applicationDirectory) + { + var packagesDirectory = GetNuGetPackagesDirectory(); + var args = new[] + { + Path.Combine(applicationDirectory, "project.json"), + "-s", + packagesDirectory, + "-s", + ApplicationPaths.ArtifactPackagesDirectory, + "--packages", + TempRestoreDirectory, + }; + + var commandResult = Command + .CreateDotNet("restore", args) + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute(); + + Assert.True(commandResult.ExitCode == 0, + string.Join(Environment.NewLine, + $"dotnet {commandResult.StartInfo.Arguments} exited with {commandResult.ExitCode}.", + commandResult.StdOut, + commandResult.StdErr)); + + Console.WriteLine(commandResult.StdOut); + } + + private static string CreateTempRestoreDirectory() + { + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + return Directory.CreateDirectory(path).FullName; + } + + private static string GetNuGetPackagesDirectory() + { + var nugetFeed = Environment.GetEnvironmentVariable(NuGetPackagesEnvironmentKey); + if (!string.IsNullOrEmpty(nugetFeed)) + { + return nugetFeed; + } + + string basePath; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = Environment.GetEnvironmentVariable("HOME"); + } + + return Path.Combine(basePath, ".nuget", "packages"); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/TestEmbeddedResource.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/TestEmbeddedResource.cs new file mode 100644 index 0000000000..d584528d5f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/TestEmbeddedResource.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Reflection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public static class TestEmbeddedResource + { + private static readonly object _writeLock = new object(); + private static readonly string ProjectName = typeof(TestEmbeddedResource).GetTypeInfo().Assembly.GetName().Name; + + public static void AssertContent(string resourceFile, string actual) + { + var expected = GetResourceContent(resourceFile); +#if GENERATE_BASELINES + // Normalize line endings to '\r\n' for comparison. This removes Environment.NewLine from the equation. Not + // worth updating files just because we generate baselines on a different system. + var normalizedContent = actual.Replace("\r", "").Replace("\n", "\r\n"); + + if (!string.Equals(expected, normalizedContent, System.StringComparison.Ordinal)) + { + var solutionRoot = ApplicationPaths.SolutionDirectory; + var projectName = typeof(TestEmbeddedResource).GetTypeInfo().Assembly.GetName().Name; + var fullPath = Path.Combine(solutionRoot, "test", ProjectName, "Resources", resourceFile); + lock (_writeLock) + { + // Write content to the file, creating it if necessary. + File.WriteAllText(fullPath, actual); + } + } +#else + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); +#endif + } + + private static string GetResourceContent(string resourceFile) + { + resourceFile = $"{ProjectName}.Resources.{resourceFile}"; + var assembly = typeof(TestEmbeddedResource).GetTypeInfo().Assembly; + var resourceStream = assembly.GetManifestResourceStream(resourceFile); + if (resourceStream == null) + { + return null; + } + + using (var streamReader = new StreamReader(resourceStream)) + { + // Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and + // .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle + // some line endings like "\r" but otherwise ensures checksums and line mappings are consistent. + return streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n"); + } + } + + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests.xproj b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests.xproj new file mode 100644 index 0000000000..0fc55bc876 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 46c9a4b2-8b1c-451b-b670-c194901d66ac + Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b1fa884228 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.Index.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.Index.txt new file mode 100644 index 0000000000..edb2b9073a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.Index.txt @@ -0,0 +1,2 @@ +AspNetCore.Views_Home_Index_cshtml, ApplicationWithConfigureMvc.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +
Hello world! \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.ViewWithPreprocessor.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.ViewWithPreprocessor.txt new file mode 100644 index 0000000000..8d0df2adff --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithConfigureMvc.Home.ViewWithPreprocessor.txt @@ -0,0 +1 @@ +Hello from Test123 \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.ClassLibraryTagHelper.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.ClassLibraryTagHelper.txt new file mode 100644 index 0000000000..38c1e2ea7c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.ClassLibraryTagHelper.txt @@ -0,0 +1,25 @@ + + + + - ApplicationWithTagHelpers + + + + +
+ +AspNetCore.Views_Home_ClassLibraryTagHelper_cshtml, ApplicationWithTagHelpers.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +
To boldy tag that no one has ever tagged before...
+ + + + +
+
+

© 2016 - ApplicationWithTagHelpers

+
+
+ + AspNetCore.Views_Shared__Layout_cshtml, ApplicationWithTagHelpers.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.LocalTagHelper.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.LocalTagHelper.txt new file mode 100644 index 0000000000..c65026eb02 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationWithTagHelpers.Home.LocalTagHelper.txt @@ -0,0 +1,25 @@ + + + + - ApplicationWithTagHelpers + + + + +
+ +AspNetCore.Views_Home_LocalTagHelper_cshtml, ApplicationWithTagHelpers.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +TestTagHelper content. + + + + +
+
+

© 2016 - ApplicationWithTagHelpers

+
+
+ + AspNetCore.Views_Shared__Layout_cshtml, ApplicationWithTagHelpers.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/SimpleAppTest.Home.Index.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/SimpleAppTest.Home.Index.txt new file mode 100644 index 0000000000..6a085c4be8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/SimpleAppTest.Home.Index.txt @@ -0,0 +1,165 @@ + + + + + + Home Page - SimpleApp + + + + + + + + + + +
+ +AspNetCore.Views_Home_Index_cshtml, SimpleApp.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + + +
+
+

© 2016 - SimpleApp

+
+
+ + + + + + + + + + + + AspNetCore.Views_Shared__Layout_cshtml, SimpleApp.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/SimpleAppTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/SimpleAppTest.cs new file mode 100644 index 0000000000..aaa2944ac4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/SimpleAppTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public class SimpleAppTest : IClassFixture + { + public SimpleAppTest(SimpleAppTestFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + [Theory] + [InlineData(RuntimeFlavor.Clr)] + [InlineData(RuntimeFlavor.CoreClr)] + public async Task Precompilation_WorksForSimpleApps(RuntimeFlavor flavor) + { + // Arrange + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var httpClient = new HttpClient() + { + BaseAddress = new Uri(deploymentResult.ApplicationBaseUri) + }; + + // Act + var response = await httpClient.GetStringAsync(""); + + // Assert + TestEmbeddedResource.AssertContent("SimpleAppTest.Home.Index.txt", response); + } + } + + public class SimpleAppTestFixture : ApplicationTestFixture + { + public SimpleAppTestFixture() + : base("SimpleApp") + { + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/project.json b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/project.json new file mode 100644 index 0000000000..3e08ef5540 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/project.json @@ -0,0 +1,29 @@ +{ + "buildOptions": { + "define": [ + "GENERATE_BASELINES" + ], + "embed": "Resources/*" + }, + "dependencies": { + "dotnet-test-xunit": "2.2.0-*", + "Microsoft.AspNetCore.Server.IntegrationTesting": "0.2.0-*", + "Microsoft.Extensions.Logging": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.1.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-preview2-003121", + "xunit": "2.2.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + "testRunner": "xunit" +} diff --git a/testapps/ApplicationWithConfigureMvc/ApplicationWithConfigureMvc.xproj b/testapps/ApplicationWithConfigureMvc/ApplicationWithConfigureMvc.xproj new file mode 100644 index 0000000000..9cb473104c --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/ApplicationWithConfigureMvc.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + e2eaeb85-91d5-478e-9ce2-964f68de20d0 + ApplicationWithConfigureMvc + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/testapps/ApplicationWithConfigureMvc/Controllers/HomeController.cs b/testapps/ApplicationWithConfigureMvc/Controllers/HomeController.cs new file mode 100644 index 0000000000..0b6e29e940 --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/Controllers/HomeController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace ApplicationWithConfigureStartup.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() => View(); + + public IActionResult ViewWithPreprocessor() => View(); + } +} diff --git a/testapps/ApplicationWithConfigureMvc/Program.cs b/testapps/ApplicationWithConfigureMvc/Program.cs new file mode 100644 index 0000000000..2adf60f59b --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace ApplicationWithConfigureStartup +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/ApplicationWithConfigureMvc/RazorRewriter.cs b/testapps/ApplicationWithConfigureMvc/RazorRewriter.cs new file mode 100644 index 0000000000..71ab2e314e --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/RazorRewriter.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ApplicationWithConfigureStartup +{ + public class RazorRewriter : CSharpSyntaxRewriter + { + public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node) + { + if (node.Token.IsKind(SyntaxKind.StringLiteralToken)) + { + return node.WithToken(SyntaxFactory.Literal(node.Token.ValueText.Replace("\r\n", "\r\n
"))); + } + + return node; + } + } +} diff --git a/testapps/ApplicationWithConfigureMvc/Startup.cs b/testapps/ApplicationWithConfigureMvc/Startup.cs new file mode 100644 index 0000000000..124d81b51c --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/Startup.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ApplicationWithConfigureStartup +{ + public class Startup : IDesignTimeMvcBuilderConfiguration + { + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + var builder = services.AddMvc(); + ConfigureMvc(builder); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + + public void ConfigureMvc(IMvcBuilder builder) + { + builder.AddRazorOptions(options => + { + options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols(new[] { "TEST123" }); + var callback = options.CompilationCallback; + options.CompilationCallback = context => + { + callback(context); + foreach (var tree in context.Compilation.SyntaxTrees) + { + var rewrittenRoot = new RazorRewriter().Visit(tree.GetRoot()); + var rewrittenTree = tree.WithRootAndOptions(rewrittenRoot, tree.Options); + context.Compilation = context.Compilation.ReplaceSyntaxTree(tree, rewrittenTree); + } + }; + }); + } + } +} diff --git a/testapps/ApplicationWithConfigureMvc/Views/Home/Index.cshtml b/testapps/ApplicationWithConfigureMvc/Views/Home/Index.cshtml new file mode 100644 index 0000000000..ff3fabfb67 --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/Views/Home/Index.cshtml @@ -0,0 +1,2 @@ +@GetType().AssemblyQualifiedName +Hello world! \ No newline at end of file diff --git a/testapps/ApplicationWithConfigureMvc/Views/Home/ViewWithPreprocessor.cshtml b/testapps/ApplicationWithConfigureMvc/Views/Home/ViewWithPreprocessor.cshtml new file mode 100644 index 0000000000..6330cc26f5 --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/Views/Home/ViewWithPreprocessor.cshtml @@ -0,0 +1,7 @@ +@{ + var message = "Hello world message"; +#if TEST123 + message = "Hello from Test123"; +#endif +} +@message \ No newline at end of file diff --git a/testapps/ApplicationWithConfigureMvc/project.json b/testapps/ApplicationWithConfigureMvc/project.json new file mode 100644 index 0000000000..3a317fecce --- /dev/null +++ b/testapps/ApplicationWithConfigureMvc/project.json @@ -0,0 +1,45 @@ +{ + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { + "version": "1.0.0-*", + "target": "package", + "type": "build" + }, + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*" + }, + + "tools": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + }, + + "publishOptions": { + "include": [ + "wwwroot", + "appsettings.json", + "web.config" + ] + }, + + "scripts": { + "postpublish": "dotnet razor-precompile --configuration %publish:Configuration% --framework %publish:TargetFramework% --output-path %publish:OutputPath% %publish:ProjectPath%" + } +} diff --git a/testapps/ApplicationWithTagHelpers/ApplicationWithTagHelpers.xproj b/testapps/ApplicationWithTagHelpers/ApplicationWithTagHelpers.xproj new file mode 100644 index 0000000000..5c14063cb2 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/ApplicationWithTagHelpers.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 2079872f-e8f9-4db1-a340-c0d897807b86 + ApplicationWithTagHelpers + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/testapps/ApplicationWithTagHelpers/Controllers/HomeController.cs b/testapps/ApplicationWithTagHelpers/Controllers/HomeController.cs new file mode 100644 index 0000000000..0e94a2ea33 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Controllers/HomeController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace ApplicationWithTagHelpers.Controllers +{ + public class HomeController : Controller + { + public IActionResult ClassLibraryTagHelper() => View(); + + public IActionResult LocalTagHelper() => View(); + + public IActionResult NuGetPackageTagHelper() => View(); + + public IActionResult About() => Content("About content"); + } +} diff --git a/testapps/ApplicationWithTagHelpers/Program.cs b/testapps/ApplicationWithTagHelpers/Program.cs new file mode 100644 index 0000000000..ea75f8ccab --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace ApplicationWithTagHelpers +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/ApplicationWithTagHelpers/Startup.cs b/testapps/ApplicationWithTagHelpers/Startup.cs new file mode 100644 index 0000000000..00a0fb6df9 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Startup.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ApplicationWithTagHelpers +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/testapps/ApplicationWithTagHelpers/TagHelpers/TestTagHelper.cs b/testapps/ApplicationWithTagHelpers/TagHelpers/TestTagHelper.cs new file mode 100644 index 0000000000..c26d848552 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/TagHelpers/TestTagHelper.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace ApplicationWithTagHelpers.TagHelpers +{ + public class TestTagHelper : TagHelper + { + public TestTagHelper(IUrlHelperFactory urlHelperFactory) + { + UrlHelperFactory = urlHelperFactory; + } + + [HtmlAttributeNotBound] + public IUrlHelperFactory UrlHelperFactory { get; } + + [ViewContext] + [HtmlAttributeNotBound] + public ViewContext ViewContext { get; set; } + + public string Controller { get; set; } + + public string Action { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); + output.Attributes.SetAttribute("href", urlHelper.Action(new UrlActionContext + { + Controller = Controller, + Action = Action + })); + + output.PreContent.SetContent($"{nameof(TestTagHelper)} content."); + } + } +} diff --git a/testapps/ApplicationWithTagHelpers/Views/Home/ClassLibraryTagHelper.cshtml b/testapps/ApplicationWithTagHelpers/Views/Home/ClassLibraryTagHelper.cshtml new file mode 100644 index 0000000000..731528bc7e --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/Home/ClassLibraryTagHelper.cshtml @@ -0,0 +1,7 @@ +@addTagHelper *, ClassLibraryTagHelper + +@GetType().AssemblyQualifiedName +
To boldy tag that no one has ever tagged before...
+ + + diff --git a/testapps/ApplicationWithTagHelpers/Views/Home/LocalTagHelper.cshtml b/testapps/ApplicationWithTagHelpers/Views/Home/LocalTagHelper.cshtml new file mode 100644 index 0000000000..11f4e00bc6 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/Home/LocalTagHelper.cshtml @@ -0,0 +1,7 @@ +@addTagHelper *, ApplicationWithTagHelpers + +@GetType().AssemblyQualifiedName + + + + diff --git a/testapps/ApplicationWithTagHelpers/Views/Home/NuGetPackageTagHelper.cshtml b/testapps/ApplicationWithTagHelpers/Views/Home/NuGetPackageTagHelper.cshtml new file mode 100644 index 0000000000..2175afad17 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/Home/NuGetPackageTagHelper.cshtml @@ -0,0 +1,12 @@ +@addTagHelper *, TagHelperSamples.Bootstrap + +@GetType().AssemblyQualifiedName + + +

Something happened

+

Something happened

+
+
+ + diff --git a/testapps/ApplicationWithTagHelpers/Views/Shared/_Layout.cshtml b/testapps/ApplicationWithTagHelpers/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..d62ae2daab --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/Shared/_Layout.cshtml @@ -0,0 +1,20 @@ + + + + @ViewData["Title"] - ApplicationWithTagHelpers + + + +
+ @RenderBody() +
+
+

© 2016 - ApplicationWithTagHelpers

+
+
+ @RenderSection("scripts", required: false) + @GetType().AssemblyQualifiedName + + diff --git a/testapps/ApplicationWithTagHelpers/Views/_ViewImports.cshtml b/testapps/ApplicationWithTagHelpers/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..da4a9da3f1 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using ApplicationWithTagHelpers +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/testapps/ApplicationWithTagHelpers/Views/_ViewStart.cshtml b/testapps/ApplicationWithTagHelpers/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..a5f10045db --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/testapps/ApplicationWithTagHelpers/project.json b/testapps/ApplicationWithTagHelpers/project.json new file mode 100644 index 0000000000..8c88764d38 --- /dev/null +++ b/testapps/ApplicationWithTagHelpers/project.json @@ -0,0 +1,49 @@ +{ + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "ClassLibraryTagHelper": { + "target": "project" + }, + "TagHelperSamples.Bootstrap": "1.1.1", + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { + "version": "1.0.0-*", + "target": "package", + "type": "build" + }, + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*" + }, + + "tools": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + }, + + "publishOptions": { + "include": [ + "wwwroot", + "appsettings.json", + "web.config" + ] + }, + + "scripts": { + "postpublish": "dotnet razor-precompile --configuration %publish:Configuration% --framework %publish:TargetFramework% --output-path %publish:OutputPath% %publish:ProjectPath%" + } +} diff --git a/testapps/ClassLibraryTagHelper/BoldTagHelper.cs b/testapps/ClassLibraryTagHelper/BoldTagHelper.cs new file mode 100644 index 0000000000..1f090f1af1 --- /dev/null +++ b/testapps/ClassLibraryTagHelper/BoldTagHelper.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace ClassLibraryTagHelpers +{ + [HtmlTargetElement(Attributes = "bold")] + public class BoldTagHelper : TagHelper + { + public override void Process(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.RemoveAll("bold"); + output.PreContent.AppendHtml(""); + output.PostContent.AppendHtml(""); + } + } +} \ No newline at end of file diff --git a/testapps/ClassLibraryTagHelper/ClassLibraryTagHelper.xproj b/testapps/ClassLibraryTagHelper/ClassLibraryTagHelper.xproj new file mode 100644 index 0000000000..80d42837fa --- /dev/null +++ b/testapps/ClassLibraryTagHelper/ClassLibraryTagHelper.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 39efa075-3673-49ab-95f3-aa5e88df6c30 + ClassLibraryTagHelpers + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/testapps/ClassLibraryTagHelper/project.json b/testapps/ClassLibraryTagHelper/project.json new file mode 100644 index 0000000000..f18444f3dc --- /dev/null +++ b/testapps/ClassLibraryTagHelper/project.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "Microsoft.AspNetCore.Razor.Runtime": "1.1.0-*" + }, + "frameworks": { + "netstandard1.6": {}, + "net451": {} + } +} diff --git a/testapps/NuGet.config b/testapps/NuGet.config new file mode 100644 index 0000000000..4243f75fa0 --- /dev/null +++ b/testapps/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/testapps/SimpleApp/Controllers/HomeController.cs b/testapps/SimpleApp/Controllers/HomeController.cs new file mode 100644 index 0000000000..9798d3cc1b --- /dev/null +++ b/testapps/SimpleApp/Controllers/HomeController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SimpleApp.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + + public IActionResult About() + { + ViewData["Message"] = "Your application description page."; + + return View(); + } + } +} diff --git a/testapps/SimpleApp/Program.cs b/testapps/SimpleApp/Program.cs new file mode 100644 index 0000000000..6ed39c5e32 --- /dev/null +++ b/testapps/SimpleApp/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace SimpleApp +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/SimpleApp/SimpleApp.xproj b/testapps/SimpleApp/SimpleApp.xproj new file mode 100644 index 0000000000..0ab0e1618e --- /dev/null +++ b/testapps/SimpleApp/SimpleApp.xproj @@ -0,0 +1,23 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 8fa176ed-c29e-48d6-bc7a-1c3a862cb15f + SimpleApp + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + + + + + diff --git a/testapps/SimpleApp/Startup.cs b/testapps/SimpleApp/Startup.cs new file mode 100644 index 0000000000..461873a890 --- /dev/null +++ b/testapps/SimpleApp/Startup.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace SimpleApp +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/testapps/SimpleApp/Views/Home/About.cshtml b/testapps/SimpleApp/Views/Home/About.cshtml new file mode 100644 index 0000000000..50476d1fbd --- /dev/null +++ b/testapps/SimpleApp/Views/Home/About.cshtml @@ -0,0 +1,7 @@ +@{ + ViewData["Title"] = "About"; +} +

@ViewData["Title"].

+

@ViewData["Message"]

+ +

Use this area to provide additional information.

diff --git a/testapps/SimpleApp/Views/Home/Index.cshtml b/testapps/SimpleApp/Views/Home/Index.cshtml new file mode 100644 index 0000000000..de297ae049 --- /dev/null +++ b/testapps/SimpleApp/Views/Home/Index.cshtml @@ -0,0 +1,110 @@ +@{ + ViewData["Title"] = "Home Page"; +} + +@GetType().AssemblyQualifiedName + + + diff --git a/testapps/SimpleApp/Views/Shared/_Layout.cshtml b/testapps/SimpleApp/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..4f65b1d219 --- /dev/null +++ b/testapps/SimpleApp/Views/Shared/_Layout.cshtml @@ -0,0 +1,68 @@ + + + + + + @ViewData["Title"] - SimpleApp + + + + + + + + + + + + +
+ @RenderBody() +
+
+

© 2016 - SimpleApp

+
+
+ + + + + + + + + + + + + @RenderSection("scripts", required: false) + @GetType().AssemblyQualifiedName + + diff --git a/testapps/SimpleApp/Views/_ViewImports.cshtml b/testapps/SimpleApp/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..71413f674c --- /dev/null +++ b/testapps/SimpleApp/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using SimpleApp +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/testapps/SimpleApp/Views/_ViewStart.cshtml b/testapps/SimpleApp/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..a5f10045db --- /dev/null +++ b/testapps/SimpleApp/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/testapps/SimpleApp/project.json b/testapps/SimpleApp/project.json new file mode 100644 index 0000000000..3a317fecce --- /dev/null +++ b/testapps/SimpleApp/project.json @@ -0,0 +1,45 @@ +{ + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { + "version": "1.0.0-*", + "target": "package", + "type": "build" + }, + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*" + }, + + "tools": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + }, + + "publishOptions": { + "include": [ + "wwwroot", + "appsettings.json", + "web.config" + ] + }, + + "scripts": { + "postpublish": "dotnet razor-precompile --configuration %publish:Configuration% --framework %publish:TargetFramework% --output-path %publish:OutputPath% %publish:ProjectPath%" + } +} diff --git a/testapps/global.json b/testapps/global.json new file mode 100644 index 0000000000..c8de56e20f --- /dev/null +++ b/testapps/global.json @@ -0,0 +1,3 @@ +{ + "projects": [] +} diff --git a/tools/Key.snk b/tools/Key.snk new file mode 100644 index 0000000000..e10e4889c1 Binary files /dev/null and b/tools/Key.snk differ