diff --git a/README.md b/README.md index 92d19c5b..93dbce00 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ To change this file edit the source file and then run MarkdownSnippets. Use Razor to build templates from Files / EmbeddedResources / Strings / Database or your custom source outside of ASP.NET MVC. No redundant dependencies and workarounds in pair with excellent performance and **.NET Standard 2.0** and **.NET Core 3.0** support. ![Build Status](https://github.com/toddams/RazorLight/actions/workflows/dotnet.yml/badge.svg) - [![NuGet Pre Release](https://img.shields.io/nuget/vpre/RazorLight.svg?maxAge=2592000?style=flat-square)](https://www.nuget.org/packages/RazorLight/) [![NuGet downloads](https://img.shields.io/nuget/dt/RazorLight.svg)](https://www.nuget.org/packages/RazorLight/) [![Join the chat at https://gitter.im/gitterHQ/gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Razor-Light) +[![NuGet Pre Release](https://img.shields.io/nuget/vpre/RazorLight.svg?maxAge=2592000?style=flat-square)](https://www.nuget.org/packages/RazorLight/) [![NuGet downloads](https://img.shields.io/nuget/dt/RazorLight.svg)](https://www.nuget.org/packages/RazorLight/) [![Join the chat at https://gitter.im/gitterHQ/gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Razor-Light) # Solidarity with Ukraine ![ComeBackAlive](https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Come_Back_Alive_Logo_09.2022.svg/1200px-Come_Back_Alive_Logo_09.2022.svg.png) @@ -18,8 +18,6 @@ Dear friends, my name is Ivan, I am the guy who created this library. I live in *Update:* it's been a long time since I first posted this message. Thank you for your enormous support, I am removing my volunteer donation account and instead providing you with the largest and proven charity organization in Ukraine - [ComeBackAlive](https://savelife.in.ua/en/donate-en/). If you have the possibility and desire to help Ukraine - that is the right place for your valuable donations. Thank you. Be safe - - # Table of contents - [Quickstart](#quickstart) - [Template sources](#template-sources) @@ -41,8 +39,8 @@ Install-Package RazorLight -Version 2.3.0 The simplest scenario is to create a template from string. Each template must have a ````templateKey```` that is associated with it, so you can render the same template next time without recompilation. - - + + ```cs var engine = new RazorLightEngineBuilder() // required to have a default RazorLightProject type, @@ -57,13 +55,13 @@ ViewModel model = new ViewModel {Name = "John Doe"}; string result = await engine.CompileRenderStringAsync("templateKey", template, model); ``` -snippet source | anchor +snippet source | anchor To render a compiled template: - + ```cs var cacheResult = engine.Handler.Cache.RetrieveTemplate("templateKey"); if(cacheResult.Success) @@ -72,7 +70,7 @@ if(cacheResult.Success) string result = await engine.RenderTemplateAsync(templatePage, model); } ``` -snippet source | anchor +snippet source | anchor # Template sources @@ -84,7 +82,7 @@ RazorLight can resolve templates from any source, but there are a built-in provi When resolving a template from filesystem, templateKey - is a relative path to the root folder, that you pass to RazorLightEngineBuilder. - + ```cs var engine = new RazorLightEngineBuilder() .UseFileSystemProject("C:/RootFolder/With/YourTemplates") @@ -94,7 +92,7 @@ var engine = new RazorLightEngineBuilder() var model = new {Name = "John Doe"}; string result = await engine.CompileRenderAsync("Subfolder/View.cshtml", model); ``` -snippet source | anchor +snippet source | anchor ## EmbeddedResource source @@ -115,7 +113,7 @@ Project.Core/ ```` - + ```cs var engine = new RazorLightEngineBuilder() .UseEmbeddedResourcesProject(typeof(SomeService).Assembly) @@ -125,13 +123,13 @@ var engine = new RazorLightEngineBuilder() var model = new Model(); string html = await engine.CompileRenderAsync("EmailTemplates.Body", model); ``` -snippet source | anchor +snippet source | anchor Setting the root namespace allows you to leave that piece off when providing the template name as the key: - + ```cs var engine = new RazorLightEngineBuilder() .UseEmbeddedResourcesProject(typeof(SomeService).Assembly, "Project.Core.EmailTemplates") @@ -141,7 +139,7 @@ var engine = new RazorLightEngineBuilder() var model = new Model(); string html = await engine.CompileRenderAsync("Body", model); ``` -snippet source | anchor +snippet source | anchor ## Custom source diff --git a/README.source.md b/README.source.md index aa0a1642..abb5b843 100644 --- a/README.source.md +++ b/README.source.md @@ -32,7 +32,7 @@ Install-Package RazorLight -Version 2.3.0 The simplest scenario is to create a template from string. Each template must have a ````templateKey```` that is associated with it, so you can render the same template next time without recompilation. -snippet: simple +snippet: Simple To render a compiled template: diff --git a/samples/RazorLight.Samples/Samples.EntityFrameworkProject.csproj b/samples/RazorLight.Samples/Samples.EntityFrameworkProject.csproj index cdea2c30..18656313 100644 --- a/samples/RazorLight.Samples/Samples.EntityFrameworkProject.csproj +++ b/samples/RazorLight.Samples/Samples.EntityFrameworkProject.csproj @@ -2,28 +2,30 @@ Exe - netcoreapp2.1;netcoreapp3.1;net5.0 + net8.0 true false - - - - - - - + + + + + + + + + + - - - + + - + diff --git a/sandbox/RazorLight.Sandbox/RazorLight.Sandbox.csproj b/sandbox/RazorLight.Sandbox/RazorLight.Sandbox.csproj index b8515886..88ebf5ac 100644 --- a/sandbox/RazorLight.Sandbox/RazorLight.Sandbox.csproj +++ b/sandbox/RazorLight.Sandbox/RazorLight.Sandbox.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net8.0 true false @@ -32,6 +32,13 @@ + + + + + + + @@ -39,4 +46,8 @@ + + + + diff --git a/src/RazorLight.Precompile/RazorLight.Precompile.csproj b/src/RazorLight.Precompile/RazorLight.Precompile.csproj index 55d5c9ea..6653d941 100644 --- a/src/RazorLight.Precompile/RazorLight.Precompile.csproj +++ b/src/RazorLight.Precompile/RazorLight.Precompile.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 razorlight-precompile true true @@ -12,8 +12,14 @@ - - + + + + + + + + diff --git a/src/RazorLight/Compilation/RoslynCompilationService.cs b/src/RazorLight/Compilation/RoslynCompilationService.cs index 2d9c1bf2..0858c15f 100644 --- a/src/RazorLight/Compilation/RoslynCompilationService.cs +++ b/src/RazorLight/Compilation/RoslynCompilationService.cs @@ -1,260 +1,260 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.Options; -using RazorLight.Generation; -using RazorLight.Internal; -using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions; - -namespace RazorLight.Compilation -{ - public class RoslynCompilationService : ICompilationService - { - private readonly IMetadataReferenceManager metadataReferenceManager; - private readonly bool isDevelopment; - private readonly List metadataReferences = new List(); - private readonly IPrecompileCallback precompileCallback; - - public RoslynCompilationService(IMetadataReferenceManager referenceManager, Assembly operatingAssembly, IPrecompileCallback precompileCallback = null) - { - this.metadataReferenceManager = referenceManager ?? throw new ArgumentNullException(nameof(referenceManager)); - this.OperatingAssembly = operatingAssembly ?? throw new ArgumentNullException(nameof(operatingAssembly)); - this.precompileCallback = precompileCallback; - - isDevelopment = AssemblyDebugModeUtility.IsAssemblyDebugBuild(OperatingAssembly); - var pdbFormat = SymbolsUtility.SupportsFullPdbGeneration() ? - DebugInformationFormat.Pdb : - DebugInformationFormat.PortablePdb; - - EmitOptions = new EmitOptions(debugInformationFormat: pdbFormat); - } - - public RoslynCompilationService(IMetadataReferenceManager referenceManager, IOptions options, IPrecompileCallback precompileCallback = null) : - this(referenceManager, options.Value.OperatingAssembly, precompileCallback) - { - - } - - #region Options - - public virtual Assembly OperatingAssembly { get; } - - public virtual EmitOptions EmitOptions { get; } - public virtual CSharpCompilationOptions CSharpCompilationOptions - { - get - { - EnsureOptions(); - return _compilationOptions; - } - } - public virtual CSharpParseOptions ParseOptions - { - get - { - EnsureOptions(); - return _parseOptions; - } - } - - #endregion - - private CSharpParseOptions _parseOptions; - private CSharpCompilationOptions _compilationOptions; - - private static readonly object locker = new object(); - - private bool _optionsInitialized; - private void EnsureOptions() - { - lock (locker) - { - if (!_optionsInitialized) - { - var dependencyContextOptions = GetDependencyContextCompilationOptions(); - _parseOptions = GetParseOptions(dependencyContextOptions); - _compilationOptions = GetCompilationOptions(dependencyContextOptions); - - metadataReferences.AddRange(metadataReferenceManager.Resolve(OperatingAssembly)); - - _optionsInitialized = true; - } - } - } - - - public Assembly CompileAndEmit(IGeneratedRazorTemplate razorTemplate) - { - if (razorTemplate == null) - { - throw new ArgumentNullException(nameof(razorTemplate)); - } - - string assemblyName = Path.GetRandomFileName(); - var compilation = CreateCompilation(razorTemplate.GeneratedCode, assemblyName); - - using (var assemblyStream = new MemoryStream()) - using (var pdbStream = new MemoryStream()) - { - var result = compilation.Emit( - assemblyStream, - pdbStream, - options: EmitOptions); - - if (!result.Success) - { - List errorsDiagnostics = result.Diagnostics - .Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error) - .ToList(); - - StringBuilder builder = new StringBuilder(); - builder.AppendLine("Failed to compile generated Razor template:"); - - var compilationDiagnostics = new List(); - - foreach (Diagnostic diagnostic in errorsDiagnostics) - { - FileLinePositionSpan lineSpan = diagnostic.Location.SourceTree.GetMappedLineSpan(diagnostic.Location.SourceSpan); - string errorMessage = diagnostic.GetMessage(); - string formattedMessage = $"- ({lineSpan.StartLinePosition.Line}:{lineSpan.StartLinePosition.Character}) {errorMessage}"; - - var compilationDiagnostic = new TemplateCompilationDiagnostic(errorMessage, formattedMessage, lineSpan); - compilationDiagnostics.Add(compilationDiagnostic); - - builder.AppendLine(formattedMessage); - } - - builder.AppendLine("\nSee CompilationErrors for detailed information"); - - throw new TemplateCompilationException(builder.ToString(),compilationDiagnostics); - } - - assemblyStream.Seek(0, SeekOrigin.Begin); +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.Options; +using RazorLight.Generation; +using RazorLight.Internal; +using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions; + +namespace RazorLight.Compilation +{ + public class RoslynCompilationService : ICompilationService + { + private readonly IMetadataReferenceManager metadataReferenceManager; + private readonly bool isDevelopment; + private readonly List metadataReferences = new List(); + private readonly IPrecompileCallback precompileCallback; + + public RoslynCompilationService(IMetadataReferenceManager referenceManager, Assembly operatingAssembly, IPrecompileCallback precompileCallback = null) + { + this.metadataReferenceManager = referenceManager ?? throw new ArgumentNullException(nameof(referenceManager)); + this.OperatingAssembly = operatingAssembly ?? throw new ArgumentNullException(nameof(operatingAssembly)); + this.precompileCallback = precompileCallback; + + isDevelopment = AssemblyDebugModeUtility.IsAssemblyDebugBuild(OperatingAssembly); + var pdbFormat = SymbolsUtility.SupportsFullPdbGeneration() ? + DebugInformationFormat.Pdb : + DebugInformationFormat.PortablePdb; + + EmitOptions = new EmitOptions(debugInformationFormat: pdbFormat); + } + + public RoslynCompilationService(IMetadataReferenceManager referenceManager, IOptions options, IPrecompileCallback precompileCallback = null) : + this(referenceManager, options.Value.OperatingAssembly, precompileCallback) + { + + } + + #region Options + + public virtual Assembly OperatingAssembly { get; } + + public virtual EmitOptions EmitOptions { get; } + public virtual CSharpCompilationOptions CSharpCompilationOptions + { + get + { + EnsureOptions(); + return _compilationOptions; + } + } + public virtual CSharpParseOptions ParseOptions + { + get + { + EnsureOptions(); + return _parseOptions; + } + } + + #endregion + + private CSharpParseOptions _parseOptions; + private CSharpCompilationOptions _compilationOptions; + + private static readonly object locker = new object(); + + private bool _optionsInitialized; + private void EnsureOptions() + { + lock (locker) + { + if (!_optionsInitialized) + { + var dependencyContextOptions = GetDependencyContextCompilationOptions(); + _parseOptions = GetParseOptions(dependencyContextOptions); + _compilationOptions = GetCompilationOptions(dependencyContextOptions); + + metadataReferences.AddRange(metadataReferenceManager.Resolve(OperatingAssembly)); + + _optionsInitialized = true; + } + } + } + + + public Assembly CompileAndEmit(IGeneratedRazorTemplate razorTemplate) + { + if (razorTemplate == null) + { + throw new ArgumentNullException(nameof(razorTemplate)); + } + + string assemblyName = Path.GetRandomFileName(); + var compilation = CreateCompilation(razorTemplate.GeneratedCode, assemblyName); + + using (var assemblyStream = new MemoryStream()) + using (var pdbStream = new MemoryStream()) + { + var result = compilation.Emit( + assemblyStream, + pdbStream, + options: EmitOptions); + + if (!result.Success) + { + List errorsDiagnostics = result.Diagnostics + .Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error) + .ToList(); + + StringBuilder builder = new StringBuilder(); + builder.AppendLine("Failed to compile generated Razor template:"); + + var compilationDiagnostics = new List(); + + foreach (Diagnostic diagnostic in errorsDiagnostics) + { + FileLinePositionSpan lineSpan = diagnostic.Location.SourceTree.GetMappedLineSpan(diagnostic.Location.SourceSpan); + string errorMessage = diagnostic.GetMessage(); + string formattedMessage = $"- ({lineSpan.StartLinePosition.Line}:{lineSpan.StartLinePosition.Character}) {errorMessage}"; + + var compilationDiagnostic = new TemplateCompilationDiagnostic(errorMessage, formattedMessage, lineSpan); + compilationDiagnostics.Add(compilationDiagnostic); + + builder.AppendLine(formattedMessage); + } + + builder.AppendLine("\nSee CompilationErrors for detailed information"); + + throw new TemplateCompilationException(builder.ToString(),compilationDiagnostics); + } + + assemblyStream.Seek(0, SeekOrigin.Begin); pdbStream.Seek(0, SeekOrigin.Begin); var rawAssembly = assemblyStream.ToArray(); var rawSymbolStore = pdbStream.ToArray(); precompileCallback?.Invoke(razorTemplate, rawAssembly, rawSymbolStore); - var assembly = Assembly.Load(rawAssembly, rawSymbolStore); - - return assembly; - } - } - - protected internal virtual DependencyContextCompilationOptions GetDependencyContextCompilationOptions() - { - var dependencyContext = DependencyContext.Load(OperatingAssembly); - - if (dependencyContext?.CompilationOptions != null) - { - return dependencyContext.CompilationOptions; - } - - return DependencyContextCompilationOptions.Default; - } - - private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName) - { - SourceText sourceText = SourceText.From(compilationContent, Encoding.UTF8); - SyntaxTree syntaxTree = CreateSyntaxTree(sourceText).WithFilePath(assemblyName); - - CSharpCompilation compilation = CreateCompilation(assemblyName).AddSyntaxTrees(syntaxTree); - - compilation = ExpressionRewriter.Rewrite(compilation); - - //var compilationContext = new RoslynCompilationContext(compilation); - //_compilationCallback(compilationContext); - //compilation = compilationContext.Compilation; - return compilation; - } - - public CSharpCompilation CreateCompilation(string assemblyName) - { - return CSharpCompilation.Create( - assemblyName, - options: CSharpCompilationOptions, - references: metadataReferences); - } - - public SyntaxTree CreateSyntaxTree(SourceText sourceText) - { - return CSharpSyntaxTree.ParseText(sourceText, options: ParseOptions); - } - - private CSharpCompilationOptions GetCompilationOptions(DependencyContextCompilationOptions dependencyContextOptions) - { - var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); - - // Disable 1702 until roslyn turns this off by default - csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions( - new Dictionary - { - {"CS1701", ReportDiagnostic.Suppress}, // Binding redirects - {"CS1702", ReportDiagnostic.Suppress}, - {"CS1705", ReportDiagnostic.Suppress} - }); - - if (dependencyContextOptions.AllowUnsafe.HasValue) - { - csharpCompilationOptions = csharpCompilationOptions.WithAllowUnsafe( - dependencyContextOptions.AllowUnsafe.Value); - } - - OptimizationLevel optimizationLevel; - if (dependencyContextOptions.Optimize.HasValue) - { - optimizationLevel = dependencyContextOptions.Optimize.Value ? - OptimizationLevel.Release : - OptimizationLevel.Debug; - } - else - { - optimizationLevel = isDevelopment ? - OptimizationLevel.Debug : - OptimizationLevel.Release; - } - csharpCompilationOptions = csharpCompilationOptions.WithOptimizationLevel(optimizationLevel); - - if (dependencyContextOptions.WarningsAsErrors.HasValue) - { - var reportDiagnostic = dependencyContextOptions.WarningsAsErrors.Value ? - ReportDiagnostic.Error : - ReportDiagnostic.Default; - csharpCompilationOptions = csharpCompilationOptions.WithGeneralDiagnosticOption(reportDiagnostic); - } - - return csharpCompilationOptions; - } - - private CSharpParseOptions GetParseOptions(DependencyContextCompilationOptions dependencyContextOptions) - { - var configurationSymbol = isDevelopment ? "DEBUG" : "RELEASE"; - var defines = dependencyContextOptions.Defines.Concat(new[] { configurationSymbol }); - - var parseOptions = new CSharpParseOptions(preprocessorSymbols: defines); - - if (!string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion)) - { - if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion)) - { - parseOptions = parseOptions.WithLanguageVersion(languageVersion); - } - else - { - Debug.Fail($"LanguageVersion {dependencyContextOptions.LanguageVersion} specified in the deps file could not be parsed."); - } - } - - return parseOptions; - } - } -} + var assembly = Assembly.Load(rawAssembly, rawSymbolStore); + + return assembly; + } + } + + protected internal virtual DependencyContextCompilationOptions GetDependencyContextCompilationOptions() + { + var dependencyContext = DependencyContext.Load(OperatingAssembly); + + if (dependencyContext?.CompilationOptions != null) + { + return dependencyContext.CompilationOptions; + } + + return DependencyContextCompilationOptions.Default; + } + + private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName) + { + SourceText sourceText = SourceText.From(compilationContent, Encoding.UTF8); + SyntaxTree syntaxTree = CreateSyntaxTree(sourceText).WithFilePath(assemblyName); + + CSharpCompilation compilation = CreateCompilation(assemblyName).AddSyntaxTrees(syntaxTree); + + compilation = ExpressionRewriter.Rewrite(compilation); + + //var compilationContext = new RoslynCompilationContext(compilation); + //_compilationCallback(compilationContext); + //compilation = compilationContext.Compilation; + return compilation; + } + + public CSharpCompilation CreateCompilation(string assemblyName) + { + return CSharpCompilation.Create( + assemblyName, + options: CSharpCompilationOptions, + references: metadataReferences); + } + + public SyntaxTree CreateSyntaxTree(SourceText sourceText) + { + return CSharpSyntaxTree.ParseText(sourceText, options: ParseOptions); + } + + private CSharpCompilationOptions GetCompilationOptions(DependencyContextCompilationOptions dependencyContextOptions) + { + var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + + // Disable 1702 until roslyn turns this off by default + csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions( + new Dictionary + { + {"CS1701", ReportDiagnostic.Suppress}, // Binding redirects + {"CS1702", ReportDiagnostic.Suppress}, + {"CS1705", ReportDiagnostic.Suppress} + }); + + if (dependencyContextOptions.AllowUnsafe.HasValue) + { + csharpCompilationOptions = csharpCompilationOptions.WithAllowUnsafe( + dependencyContextOptions.AllowUnsafe.Value); + } + + OptimizationLevel optimizationLevel; + if (dependencyContextOptions.Optimize.HasValue) + { + optimizationLevel = dependencyContextOptions.Optimize.Value ? + OptimizationLevel.Release : + OptimizationLevel.Debug; + } + else + { + optimizationLevel = isDevelopment ? + OptimizationLevel.Debug : + OptimizationLevel.Release; + } + csharpCompilationOptions = csharpCompilationOptions.WithOptimizationLevel(optimizationLevel); + + if (dependencyContextOptions.WarningsAsErrors.HasValue) + { + var reportDiagnostic = dependencyContextOptions.WarningsAsErrors.Value ? + ReportDiagnostic.Error : + ReportDiagnostic.Default; + csharpCompilationOptions = csharpCompilationOptions.WithGeneralDiagnosticOption(reportDiagnostic); + } + + return csharpCompilationOptions; + } + + private CSharpParseOptions GetParseOptions(DependencyContextCompilationOptions dependencyContextOptions) + { + var configurationSymbol = isDevelopment ? "DEBUG" : "RELEASE"; + var defines = dependencyContextOptions.Defines.Concat(new[] { configurationSymbol }); + + var parseOptions = new CSharpParseOptions(preprocessorSymbols: defines); + + if (!string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion)) + { + if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion)) + { + parseOptions = parseOptions.WithLanguageVersion(languageVersion); + } + else + { + Debug.Fail($"LanguageVersion {dependencyContextOptions.LanguageVersion} specified in the deps file could not be parsed."); + } + } + + return parseOptions; + } + } +} diff --git a/src/RazorLight/Razor/FileSystemRazorProject.cs b/src/RazorLight/Razor/FileSystemRazorProject.cs index 46e4a248..1b156000 100644 --- a/src/RazorLight/Razor/FileSystemRazorProject.cs +++ b/src/RazorLight/Razor/FileSystemRazorProject.cs @@ -1,106 +1,106 @@ -using Microsoft.Extensions.FileProviders; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using Microsoft.Extensions.FileProviders; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; -using System.Threading.Tasks; - -namespace RazorLight.Razor +using System.Threading.Tasks; + +namespace RazorLight.Razor { - /// - /// Specifies RazorProject where templates are located in files - /// - public class FileSystemRazorProject : RazorLightProject - { - public const string DefaultExtension = ".cshtml"; - private readonly IFileProvider _fileProvider; - - public FileSystemRazorProject(string root) - : this(root, DefaultExtension) - { - } - - public FileSystemRazorProject(string root, string extension) - { - Extension = extension ?? throw new ArgumentNullException(nameof(extension)); - - if (!Directory.Exists(root)) - { - throw new DirectoryNotFoundException($"Root directory {root} not found"); - } - - Root = root; - _fileProvider = new PhysicalFileProvider(Root); - } - - public string Extension { get; set; } - - /// - /// Looks up for the template source with a given - /// - /// Unique template key - /// - public override Task GetItemAsync(string templateKey) - { - if (!templateKey.EndsWith(Extension)) - { - templateKey = templateKey + Extension; - } - - string absolutePath = GetAbsoluteFilePathFromKey(templateKey); - var item = new FileSystemRazorProjectItem(templateKey, new FileInfo(absolutePath)); - - if (item.Exists) - { - item.ExpirationToken = _fileProvider.Watch(templateKey); - } - - return Task.FromResult((RazorLightProjectItem)item); - } - - /// - /// Root folder - /// - public string Root { get; } - - protected string GetAbsoluteFilePathFromKey(string templateKey) - { - if (string.IsNullOrEmpty(templateKey)) - { - throw new ArgumentNullException(nameof(templateKey)); - } - - var absolutePath = templateKey; - if (!absolutePath.StartsWith(Root, StringComparison.OrdinalIgnoreCase)) - { - if (templateKey[0] == '/' || templateKey[0] == '\\') - { - templateKey = templateKey.Substring(1); - } - - absolutePath = Path.Combine(Root, templateKey); - } - - absolutePath = absolutePath.Replace('\\', '/'); - - return absolutePath; - } - - public override Task> GetImportsAsync(string templateKey) - { - return Task.FromResult(Enumerable.Empty()); - } - public override Task> GetKnownKeysAsync() - { - var files = Directory.EnumerateFiles(Root, $"*{Extension}", SearchOption.AllDirectories) - .Where(x => x.StartsWith(Root)) - .Select(x => x.Substring(Root.Length, x.Length - Root.Length)) - .Select(x => x.StartsWith("\\") || x.StartsWith("/") ? x.Substring(1) : x) - .Select(x => x.Replace('\\', '/')); - - return Task.FromResult(files); - } - public override string NormalizeKey(string templateKey) => FileSystemRazorProjectHelper.NormalizeKey(templateKey); - } -} + /// + /// Specifies RazorProject where templates are located in files + /// + public class FileSystemRazorProject : RazorLightProject + { + public const string DefaultExtension = ".cshtml"; + private readonly IFileProvider _fileProvider; + + public FileSystemRazorProject(string root) + : this(root, DefaultExtension) + { + } + + public FileSystemRazorProject(string root, string extension) + { + Extension = extension ?? throw new ArgumentNullException(nameof(extension)); + + if (!Directory.Exists(root)) + { + throw new DirectoryNotFoundException($"Root directory {root} not found"); + } + + Root = root; + _fileProvider = new PhysicalFileProvider(Root); + } + + public string Extension { get; set; } + + /// + /// Looks up for the template source with a given + /// + /// Unique template key + /// + public override Task GetItemAsync(string templateKey) + { + if (!templateKey.EndsWith(Extension)) + { + templateKey = templateKey + Extension; + } + + string absolutePath = GetAbsoluteFilePathFromKey(templateKey); + var item = new FileSystemRazorProjectItem(templateKey, new FileInfo(absolutePath)); + + if (item.Exists) + { + item.ExpirationToken = _fileProvider.Watch(templateKey); + } + + return Task.FromResult((RazorLightProjectItem)item); + } + + /// + /// Root folder + /// + public string Root { get; } + + protected string GetAbsoluteFilePathFromKey(string templateKey) + { + if (string.IsNullOrEmpty(templateKey)) + { + throw new ArgumentNullException(nameof(templateKey)); + } + + var absolutePath = templateKey; + if (!absolutePath.StartsWith(Root, StringComparison.OrdinalIgnoreCase)) + { + if (templateKey[0] == '/' || templateKey[0] == '\\') + { + templateKey = templateKey.Substring(1); + } + + absolutePath = Path.Combine(Root, templateKey); + } + + absolutePath = absolutePath.Replace('\\', '/'); + + return absolutePath; + } + + public override Task> GetImportsAsync(string templateKey) + { + return Task.FromResult(Enumerable.Empty()); + } + public override Task> GetKnownKeysAsync() + { + var files = Directory.EnumerateFiles(Root, $"*{Extension}", SearchOption.AllDirectories) + .Where(x => x.StartsWith(Root)) + .Select(x => x.Substring(Root.Length, x.Length - Root.Length)) + .Select(x => x.StartsWith("\\") || x.StartsWith("/") ? x.Substring(1) : x) + .Select(x => x.Replace('\\', '/')); + + return Task.FromResult(files); + } + public override string NormalizeKey(string templateKey) => FileSystemRazorProjectHelper.NormalizeKey(templateKey); + } +} diff --git a/src/RazorLight/RazorLight.csproj b/src/RazorLight/RazorLight.csproj index b3d95fd2..bd9fa97d 100644 --- a/src/RazorLight/RazorLight.csproj +++ b/src/RazorLight/RazorLight.csproj @@ -1,6 +1,6 @@ - netstandard2.0;netcoreapp3.1;net5.0;net6.0 + net8.0 @@ -14,72 +14,18 @@ https://github.com/toddams/RazorLight - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - + + + + + + + + + + diff --git a/tests/RazorLight.Precompile.Tests/FileSystemCachingStrategyTests.cs b/tests/RazorLight.Precompile.Tests/FileSystemCachingStrategyTests.cs index 8ed30eee..f3d91d0f 100644 --- a/tests/RazorLight.Precompile.Tests/FileSystemCachingStrategyTests.cs +++ b/tests/RazorLight.Precompile.Tests/FileSystemCachingStrategyTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NUnit.Framework.Legacy; using RazorLight.Caching; using System.Runtime.InteropServices; @@ -57,14 +58,14 @@ public void DifferentKey(IFileSystemCachingStrategy s) var templateFilePath = "Samples/folder/MessageItem.cshtml"; var o1 = s.GetCachedFileInfo("folder/MessageItem.cshtml", templateFilePath, "X:/"); var o2 = s.GetCachedFileInfo("MessageItem.cshtml", templateFilePath, "X:/"); - Assert.AreNotEqual(o1.AssemblyFilePath, o2.AssemblyFilePath); + ClassicAssert.AreNotEqual(o1.AssemblyFilePath, o2.AssemblyFilePath); } [TestCaseSource(nameof(s_sepCombinations))] public void EquivalentKeyFileHashCachingStrategy(string[] sepCombination) { var (asmFilePath1, asmFilePath2) = GetAsmFilePaths(FileHashCachingStrategy.Instance, sepCombination); - Assert.AreEqual(asmFilePath1, asmFilePath2); + ClassicAssert.AreEqual(asmFilePath1, asmFilePath2); } [TestCaseSource(nameof(s_sepCombinations))] @@ -76,7 +77,7 @@ public void EquivalentKeySimpleFileCachingStrategy(string[] sepCombination) asmFilePath1 = Path.GetFullPath(asmFilePath1); asmFilePath2 = Path.GetFullPath(asmFilePath2); } - Assert.AreEqual(asmFilePath1, asmFilePath2); + ClassicAssert.AreEqual(asmFilePath1, asmFilePath2); } private static (string, string) GetAsmFilePaths(IFileSystemCachingStrategy s, string[] sepCombination) @@ -84,7 +85,7 @@ private static (string, string) GetAsmFilePaths(IFileSystemCachingStrategy s, st var templateFilePath = "Samples/folder/MessageItem.cshtml"; string key1 = $"{sepCombination[0]}folder{sepCombination[1]}MessageItem.cshtml"; string key2 = $"{sepCombination[2]}folder{sepCombination[3]}MessageItem.cshtml"; - Assert.AreNotEqual(key1, key2); + ClassicAssert.AreNotEqual(key1, key2); var asmFilePath1 = s.GetCachedFileInfo(key1, templateFilePath, "X:/").AssemblyFilePath; var asmFilePath2 = s.GetCachedFileInfo(key2, templateFilePath, "X:/").AssemblyFilePath; return (asmFilePath1, asmFilePath2); diff --git a/tests/RazorLight.Precompile.Tests/Helper.cs b/tests/RazorLight.Precompile.Tests/Helper.cs index 3a0d9db6..d21aa8f0 100644 --- a/tests/RazorLight.Precompile.Tests/Helper.cs +++ b/tests/RazorLight.Precompile.Tests/Helper.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NUnit.Framework.Legacy; using System.Text; namespace RazorLight.Precompile.Tests @@ -17,7 +18,7 @@ public static StringBuilder RunCommand(params string[] args) var sw = new StringWriter(); Program.ConsoleOut = sw; var exitCode = Program.DoRun(args); - Assert.Zero(exitCode); + ClassicAssert.Zero(exitCode); sw.Close(); return sw.GetStringBuilder(); diff --git a/tests/RazorLight.Precompile.Tests/PrecompileTests.cs b/tests/RazorLight.Precompile.Tests/PrecompileTests.cs index 1841c3ce..0e48e993 100644 --- a/tests/RazorLight.Precompile.Tests/PrecompileTests.cs +++ b/tests/RazorLight.Precompile.Tests/PrecompileTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NUnit.Framework.Legacy; using System.Diagnostics; namespace RazorLight.Precompile.Tests @@ -60,7 +61,7 @@ public static void Precompile(string templateFilePath, TestScenario scenario, st commandLineArgs.AddRange(scenario.ExtraCommandLineArgs); var precompiledFilePath = Helper.RunCommandTrimNewline(commandLineArgs.ToArray()); - Assert.AreEqual(expectedPrecompiledFilePath, precompiledFilePath); + ClassicAssert.AreEqual(expectedPrecompiledFilePath, precompiledFilePath); FileAssert.Exists(precompiledFilePath); } } diff --git a/tests/RazorLight.Precompile.Tests/RazorLight.Precompile.Tests.csproj b/tests/RazorLight.Precompile.Tests/RazorLight.Precompile.Tests.csproj index 73be2459..d537df08 100644 --- a/tests/RazorLight.Precompile.Tests/RazorLight.Precompile.Tests.csproj +++ b/tests/RazorLight.Precompile.Tests/RazorLight.Precompile.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 true enable enable @@ -9,9 +9,15 @@ - - - + + + + + + + + + @@ -31,4 +37,8 @@ + + + + diff --git a/tests/RazorLight.Precompile.Tests/Render1Tests.cs b/tests/RazorLight.Precompile.Tests/Render1Tests.cs index fdd07545..c6ca1710 100644 --- a/tests/RazorLight.Precompile.Tests/Render1Tests.cs +++ b/tests/RazorLight.Precompile.Tests/Render1Tests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NUnit.Framework.Legacy; namespace RazorLight.Precompile.Tests { @@ -90,7 +91,7 @@ public void Render(string templateFilePath, string jsonQuery, string expected) } var actual = Helper.RunCommand(commandLineArgs.ToArray()).ToString(); - Assert.AreEqual(expected, actual); + ClassicAssert.AreEqual(expected, actual); } [TestCaseSource(nameof(s_testCases))] @@ -112,7 +113,7 @@ public void PrecompileAndRender(string templateFilePath, string jsonQuery, strin } var actual = Helper.RunCommand(commandLineArgs.ToArray()).ToString(); - Assert.AreEqual(expected, actual); + ClassicAssert.AreEqual(expected, actual); } } } \ No newline at end of file diff --git a/tests/RazorLight.Precompile.Tests/Render2Tests.cs b/tests/RazorLight.Precompile.Tests/Render2Tests.cs index f6858946..c562d442 100644 --- a/tests/RazorLight.Precompile.Tests/Render2Tests.cs +++ b/tests/RazorLight.Precompile.Tests/Render2Tests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using NUnit.Framework.Legacy; using RazorLight.Caching; namespace RazorLight.Precompile.Tests @@ -102,7 +103,7 @@ public void RenderFolderNonRecursive(string key, string key2, IFileSystemCaching Precompile(key, key2, s); var exc = Assert.Throws(() => Run(key, expected, "Samples")); - Assert.AreEqual("No precompiled template found for the key /folder/MessageItem.cshtml", exc.Message); + ClassicAssert.AreEqual("No precompiled template found for the key /folder/MessageItem.cshtml", exc.Message); } [TestCaseSource(nameof(s_testCases))] @@ -111,7 +112,7 @@ public void RenderGlobNonRecursive(string key, string key2, IFileSystemCachingSt Precompile(key, key2, s); var exc = Assert.Throws(() => Run(key, expected, "Samples/*.dll")); - Assert.AreEqual("No precompiled template found for the key /folder/MessageItem.cshtml", exc.Message); + ClassicAssert.AreEqual("No precompiled template found for the key /folder/MessageItem.cshtml", exc.Message); } private static (string, string) Precompile(string key, string key2, IFileSystemCachingStrategy s) => ( @@ -134,7 +135,7 @@ private static void Run(string key, string expected, string precompiledFilePath, commandLineArgs.AddRange(args); var actual = Helper.RunCommand(commandLineArgs.ToArray()).ToString(); - Assert.AreEqual(expected, actual); + ClassicAssert.AreEqual(expected, actual); } [TestCaseSource(nameof(s_testCases))] @@ -154,7 +155,7 @@ public void PrecompileAndRender(string templateFilePath, string _, IFileSystemCa }; var actual = Helper.RunCommand(commandLineArgs.ToArray()).ToString(); - Assert.AreEqual(expected, actual); + ClassicAssert.AreEqual(expected, actual); } } } \ No newline at end of file diff --git a/tests/RazorLight.Tests/Compilation/RazorTemplateCompilerTest.cs b/tests/RazorLight.Tests/Compilation/RazorTemplateCompilerTest.cs index 280cebae..94ed6f87 100644 --- a/tests/RazorLight.Tests/Compilation/RazorTemplateCompilerTest.cs +++ b/tests/RazorLight.Tests/Compilation/RazorTemplateCompilerTest.cs @@ -117,14 +117,14 @@ public async Task Compiler_Searches_WithNormalizedKey_IfNotFound() } [Fact] - public void Throws_TemplateNotFoundException_If_ProjectItem_NotExist() + public async void Throws_TemplateNotFoundException_If_ProjectItem_NotExist() { var project = new EmbeddedRazorProject(typeof(Root).Assembly); var compiler = TestRazorTemplateCompiler.Create(project: project); Func task = new Func(() => compiler.CompileAsync("Not.Existing.Key")); - Assert.ThrowsAsync(task); + await Assert.ThrowsAsync(task); } [Fact] diff --git a/tests/RazorLight.Tests/Integration/RendererCommonCasesTests.cs b/tests/RazorLight.Tests/Integration/RendererCommonCasesTests.cs index 7c787eb9..21b37d4e 100644 --- a/tests/RazorLight.Tests/Integration/RendererCommonCasesTests.cs +++ b/tests/RazorLight.Tests/Integration/RendererCommonCasesTests.cs @@ -14,7 +14,7 @@ public class TestViewModel public int NumberOfItems { get; set; } } - [UsesVerify] + //[UsesVerify] public class RendererCommonCasesTests { diff --git a/tests/RazorLight.Tests/Razor/EmbeddedRazorProjectTest.cs b/tests/RazorLight.Tests/Razor/EmbeddedRazorProjectTest.cs index 0d6521ed..4bf0816f 100644 --- a/tests/RazorLight.Tests/Razor/EmbeddedRazorProjectTest.cs +++ b/tests/RazorLight.Tests/Razor/EmbeddedRazorProjectTest.cs @@ -17,11 +17,11 @@ public void Ensure_Throws_OnNullRootType() } [Fact] - public void Ensure_Throws_OnNullTemplateKey() + public async void Ensure_Throws_OnNullTemplateKey() { var project = new EmbeddedRazorProject(typeof(EmbeddedRazorProject)); - Assert.ThrowsAsync(async () => { await project.GetItemAsync(null); }); + await Assert.ThrowsAsync(async () => { await project.GetItemAsync(null); }); } [Fact] diff --git a/tests/RazorLight.Tests/Razor/FileSystemRazorProjectTest.cs b/tests/RazorLight.Tests/Razor/FileSystemRazorProjectTest.cs index f01b19cd..ccdc2add 100644 --- a/tests/RazorLight.Tests/Razor/FileSystemRazorProjectTest.cs +++ b/tests/RazorLight.Tests/Razor/FileSystemRazorProjectTest.cs @@ -50,11 +50,12 @@ public void Ensure_ExtensionProperty_AssignedOnConstructor() } [Fact] - public void Null_TemplateKey_ThrowsOn_GetItem() + public async void Null_TemplateKey_ThrowsOn_GetItem() { var project = new FileSystemRazorProject(DirectoryUtils.RootDirectory); - Assert.ThrowsAsync(async () => await project.GetItemAsync("not-existing-key")); + //Maybe you want to check if the file exists and then throw an exception if it doesn't? + await Assert.ThrowsAsync(async () => await project.GetItemAsync("not-existing-key")); } [Fact] diff --git a/tests/RazorLight.Tests/RazorLight.Tests.csproj b/tests/RazorLight.Tests/RazorLight.Tests.csproj index 524b10c5..56561604 100644 --- a/tests/RazorLight.Tests/RazorLight.Tests.csproj +++ b/tests/RazorLight.Tests/RazorLight.Tests.csproj @@ -1,80 +1,98 @@ - - - - netcoreapp2.0;netcoreapp3.1;net5.0;net6.0 - false - true - $(DefineConstants);SOME_TEST_DEFINE + + + + net8.0 + false + true + $(DefineConstants);SOME_TEST_DEFINE true - NU1701;CS0618 - - - - $(SolutionDir) - - - - true - - - - false - true - - - - - - - - - - - Always - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + NU1701;CS0618 + + + + $(SolutionDir) + + + + true + + + + false + true + + + + + + + + + + + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + +