From 7de6466588b09b3158dbadc329a97f0b666419bd Mon Sep 17 00:00:00 2001 From: bounav Date: Tue, 23 Jan 2024 11:53:13 +0000 Subject: [PATCH 01/14] Using the Microsoft.Extensions.DependencyInjection apis to resolved dependencies - Now using constructor injection instead of auto-initializing properties - Removed ISparkServiceInitialize and ISparkServiceContainer (and it's implementation) - New IBatchCompiler interface so that we can use different compilers - ~2x performance improvement when compiling views with Roslyn - CodeDom compilation can still be used at the moment (class marked as obsolete) - CastleMonoRail still using codedom (rosylin doesn't like the assembly name when compiling in that project) --- .../SparkBatchCompilerTester.cs | 11 +- .../SparkViewDataTests.cs | 4 +- ...parkViewFactoryStrictNullBehaviourTests.cs | 9 +- .../SparkViewFactoryTests.cs | 50 ++- .../ViewComponents/BaseViewComponentTests.cs | 11 +- .../ViewComponentRenderViewTests.cs | 3 +- .../Install/PrecompileInstaller.cs | 5 +- src/Castle.MonoRail.Views.Spark/SparkView.cs | 8 +- .../SparkViewFactory.cs | 55 ++- .../ViewComponentContext.cs | 7 +- src/Spark.JsTests/Generate.ashx.cs | 36 +- .../PythonViewCompilerTests.cs | 3 +- .../ScriptingLanguageFactoryTests.cs | 17 +- .../Compiler/PythonViewCompiler.cs | 5 +- src/Spark.Python/PythonLanguageFactory.cs | 4 + src/Spark.Ruby.Tests/RubyViewCompilerTests.cs | 10 +- src/Spark.Ruby/Compiler/RubyViewCompiler.cs | 8 +- src/Spark.Ruby/RubyLanguageFactory.cs | 3 + src/Spark.Tests/CompiledViewHolderTester.cs | 56 ++- src/Spark.Tests/Parser/ViewLoaderTester.cs | 133 +++++- .../PdfViewResultTests.cs | 27 +- .../PythonLanguageFactoryWithExtensions.cs | 5 + .../ServiceCollectionExtensions.cs | 25 ++ .../SparkPythonEngineStarter.cs | 134 ------ .../HtmlHelperExtensionsTester.cs | 28 +- .../RubyLanguageFactoryWithExtensions.cs | 6 + .../ServiceCollectionExtensions.cs | 25 ++ .../SparkRubyEngineStarter.cs | 133 ------ .../DescriptorBuildingTester.cs | 83 +++- .../SparkBatchCompilerTester.cs | 59 ++- .../SparkViewFactoryTester.cs | 147 +++++-- src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs | 36 +- .../Descriptors/DescriptorFilterExtensions.cs | 7 +- .../Extensions/ServiceCollectionExtensions.cs | 73 ++++ .../Install/PrecompileInstaller.cs | 17 +- src/Spark.Web.Mvc/LanguageKit.cs | 10 +- src/Spark.Web.Mvc/Spark.Web.Mvc.csproj | 3 +- src/Spark.Web.Mvc/SparkEngineStarter.cs | 137 ------ src/Spark.Web.Mvc/SparkViewFactory.cs | 173 +++----- src/Spark.Web.Tests/BatchCompilationTester.cs | 41 +- .../Bindings/BindingExecutionTester.cs | 20 +- .../Caching/CacheElementTester.cs | 22 +- .../ClientsideCompilerTester.cs | 29 +- .../Compiler/CSharpViewCompilerTester.cs | 79 ++-- .../Compiler/SourceMappingTester.cs | 24 +- .../Compiler/VisualBasicViewCompilerTester.cs | 46 +- .../SparkSectionHandlerTester.cs | 11 +- .../Extensions/ServiceCollectionExtensions.cs | 69 +++ .../FileSystem/InMemoryViewFolderTester.cs | 45 +- .../FileSystem/ViewFolderSettingsTester.cs | 37 +- src/Spark.Web.Tests/ImportAndIncludeTester.cs | 12 +- .../Parser/AutomaticEncodingTester.cs | 10 +- .../Parser/CSharpSyntaxProviderTester.cs | 34 +- src/Spark.Web.Tests/PartialProviderTester.cs | 39 +- src/Spark.Web.Tests/PrefixSupportTester.cs | 37 +- src/Spark.Web.Tests/SparkExtensionTester.cs | 14 +- .../SparkServiceContainerTester.cs | 162 ------- src/Spark.Web.Tests/SparkViewFactoryTester.cs | 31 +- src/Spark.Web.Tests/ViewActivatorTester.cs | 17 +- .../Visitors/DetectCodeExpressionTester.cs | 94 ++++- src/Spark.Web.Tests/VisualBasicViewTester.cs | 21 +- src/Spark.sln.DotSettings | 3 + src/Spark/Compiler/AssemblyExtensions.cs | 44 ++ src/Spark/Compiler/BatchCompilerException.cs | 14 - .../Compiler/CSharp/CSharpViewCompiler.cs | 83 ++-- .../CodeDomBatchCompiler.cs} | 395 ++++++++---------- .../CodeDom/CodeDomCompilerException.cs | 9 + src/Spark/Compiler/CompilerException.cs | 5 + src/Spark/Compiler/IBatchCompiler.cs | 18 + src/Spark/Compiler/Roslyn/CSharpLink.cs | 57 +++ .../Compiler/Roslyn/IRoslynCompilationLink.cs | 12 + .../Compiler/Roslyn/RoslynBatchCompiler.cs | 89 ++++ .../Roslyn/RoslynCompilerException.cs | 9 + src/Spark/Compiler/Roslyn/VisualBasicLink.cs | 57 +++ .../VisualBasic/VisualBasicViewCompiler.cs | 14 +- src/Spark/DefaultLanguageFactory.cs | 8 +- src/Spark/ISparkServiceContainer.cs | 25 -- src/Spark/ISparkServiceInitialize.cs | 26 -- src/Spark/ISparkViewEngine.cs | 11 +- src/Spark/Parser/ViewLoader.cs | 122 ++---- src/Spark/Spark.csproj | 2 + src/Spark/SparkServiceContainer.cs | 128 ------ src/Spark/SparkViewEngine.cs | 245 ++++------- src/Xpark/Program.cs | 48 ++- 84 files changed, 2005 insertions(+), 1879 deletions(-) create mode 100644 src/Spark.Web.Mvc.Python/ServiceCollectionExtensions.cs delete mode 100644 src/Spark.Web.Mvc.Python/SparkPythonEngineStarter.cs create mode 100644 src/Spark.Web.Mvc.Ruby/ServiceCollectionExtensions.cs delete mode 100644 src/Spark.Web.Mvc.Ruby/SparkRubyEngineStarter.cs create mode 100644 src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs delete mode 100644 src/Spark.Web.Mvc/SparkEngineStarter.cs create mode 100644 src/Spark.Web.Tests/Extensions/ServiceCollectionExtensions.cs delete mode 100644 src/Spark.Web.Tests/SparkServiceContainerTester.cs create mode 100644 src/Spark.sln.DotSettings create mode 100644 src/Spark/Compiler/AssemblyExtensions.cs delete mode 100644 src/Spark/Compiler/BatchCompilerException.cs rename src/Spark/Compiler/{BatchCompiler.cs => CodeDom/CodeDomBatchCompiler.cs} (70%) create mode 100644 src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs create mode 100644 src/Spark/Compiler/IBatchCompiler.cs create mode 100644 src/Spark/Compiler/Roslyn/CSharpLink.cs create mode 100644 src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs create mode 100644 src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs create mode 100644 src/Spark/Compiler/Roslyn/RoslynCompilerException.cs create mode 100644 src/Spark/Compiler/Roslyn/VisualBasicLink.cs delete mode 100644 src/Spark/ISparkServiceContainer.cs delete mode 100644 src/Spark/ISparkServiceInitialize.cs delete mode 100644 src/Spark/SparkServiceContainer.cs diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs index 90936835..b0932118 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs @@ -35,11 +35,12 @@ public class SparkBatchCompilerTester [SetUp] public void Init() { - var settings = new SparkSettings(); + var settings = new SparkSettings() + .SetPageBaseType(typeof(SparkView)); var services = new StubMonoRailServices(); + services.AddService(typeof(ISparkSettings), settings); services.AddService(typeof(IViewSourceLoader), new FileAssemblyViewSourceLoader("MonoRail.Tests.Views")); - services.AddService(typeof(ISparkViewEngine), new SparkViewEngine(settings)); services.AddService(typeof(IControllerDescriptorProvider), services.ControllerDescriptorProvider); _factory = new SparkViewFactory(); _factory.Service(services); @@ -59,7 +60,7 @@ public void CompileBatchDescriptor() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(3, assembly.GetTypes().Length); + Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] @@ -98,7 +99,7 @@ public void MultipleLayoutFiles() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(4, assembly.GetTypes().Length); + Assert.AreEqual(4, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] @@ -131,7 +132,7 @@ public void WildcardIncludeRules() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(3, assembly.GetTypes().Length); + Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewDataTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewDataTests.cs index 77b8c423..9a06af17 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewDataTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewDataTests.cs @@ -46,7 +46,7 @@ public void PropertyBagAvailable() controllerContext.PropertyBag.Add("foo", "bar"); mocks.ReplayAll(); - view.Contextualize(engineContext, controllerContext, null, null); + view.Contextualize(engineContext, controllerContext, null, null, null); Assert.AreEqual("bar", view.ViewData["foo"]); } @@ -71,7 +71,7 @@ public void MergingCollectionsLikeVelocity() engineContext.Request.Params.Add("contextParamsKey", "contextParamsValue"); controllerContext.Resources.Add("controllerResourcesKey", resource); - view.Contextualize(engineContext, controllerContext, null, null); + view.Contextualize(engineContext, controllerContext, null, null, null); Assert.AreEqual("controllerPropertyBagValue", view.ViewData["controllerPropertyBagKey"]); Assert.AreEqual("contextFlashValue", view.ViewData["contextFlashKey"]); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs index 13f4c57f..1f75a9c4 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs @@ -27,10 +27,13 @@ public class SparkViewFactoryStrictNullBehaviourTests : SparkViewFactoryTestsBas { protected override void Configure() { - var settings = new SparkSettings(); + var settings = + new SparkSettings() + .SetPageBaseType(typeof(SparkView)); + settings.SetNullBehaviour(NullBehaviour.Strict); - var sparkViewEngine = new SparkViewEngine(settings); - serviceProvider.AddService(typeof(ISparkViewEngine), sparkViewEngine); + + serviceProvider.AddService(typeof(ISparkSettings), settings); factory = new SparkViewFactory(); factory.Service(serviceProvider); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs index e6bdc997..b969bcb3 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs @@ -27,21 +27,25 @@ namespace Castle.MonoRail.Views.Spark.Tests [TestFixture] public class SparkViewFactoryTests : SparkViewFactoryTestsBase - { - protected override void Configure() - { - factory = new SparkViewFactory(); - factory.Service(serviceProvider); - - manager = new DefaultViewEngineManager(); - manager.Service(serviceProvider); - serviceProvider.ViewEngineManager = manager; - serviceProvider.AddService(typeof(IViewEngineManager), manager); - serviceProvider.AddService(typeof(ISparkSettings), new SparkSettings()); + { + protected override void Configure() + { + var settings = new SparkSettings() + .SetPageBaseType(typeof(SparkView)); + + serviceProvider.AddService(typeof(ISparkSettings), settings); + + factory = new SparkViewFactory(); + factory.Service(serviceProvider); - manager.RegisterEngineForExtesionLookup(factory); - manager.RegisterEngineForView(factory); - } + manager = new DefaultViewEngineManager(); + manager.Service(serviceProvider); + serviceProvider.ViewEngineManager = manager; + serviceProvider.AddService(typeof(IViewEngineManager), manager); + + manager.RegisterEngineForExtesionLookup(factory); + manager.RegisterEngineForView(factory); + } [Test] public void ExtensionIsSpark() @@ -69,7 +73,7 @@ public void ContextAndControllerContextAvailable() descriptor.Templates.Add(string.Format("Shared{0}default.spark", Path.DirectorySeparatorChar)); var entry = factory.Engine.GetEntry(descriptor); var view = (SparkView)entry.CreateInstance(); - view.Contextualize(engineContext, controllerContext, factory, null); + view.Contextualize(engineContext, controllerContext, serviceProvider.GetService(), factory, null); var result = new StringWriter(); view.RenderView(result); @@ -114,14 +118,14 @@ public void NullBehaviourConfiguredToLenient() { mocks.ReplayAll(); manager.Process(string.Format("Home{0}NullBehaviourConfiguredToLenient", Path.DirectorySeparatorChar), output, engineContext, controller, controllerContext); - var content = output.ToString(); - Assert.IsFalse(content.Contains("default")); - - ContainsInOrder(content, - "

name kaboom *${user.Name}*

", - "

name silently **

", - "

name fixed *fred*

"); - } + var content = output.ToString(); + Assert.IsFalse(content.Contains("default")); + + ContainsInOrder(content, + "

name kaboom *${user.Name}*

", + "

name silently **

", + "

name fixed *fred*

"); + } [Test] public void TerseHtmlEncode() diff --git a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs index 226e183a..e813af53 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs @@ -29,8 +29,7 @@ public class BaseViewComponentTests protected StubEngineContext engineContext; protected SparkViewFactory factory; protected IController controller; - protected SparkViewEngine engine; - + [SetUp] public virtual void Init() { @@ -45,9 +44,11 @@ public virtual void Init() services.AddService(typeof(IViewComponentFactory), viewComponentFactory); services.AddService(typeof(IViewComponentRegistry), viewComponentFactory.Registry); - var settings = new SparkSettings(); - engine = new SparkViewEngine(settings); - services.AddService(typeof(ISparkViewEngine), engine); + var settings = new SparkSettings() + .SetPageBaseType(typeof(SparkView)); + services.AddService(typeof(ISparkSettings), settings); + + services.AddService(typeof(IResourcePathManager), new DefaultResourcePathManager(settings)); factory = new SparkViewFactory(); factory.Service(services); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/ViewComponentRenderViewTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/ViewComponentRenderViewTests.cs index 3b163180..c4dbf1dd 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/ViewComponentRenderViewTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/ViewComponentRenderViewTests.cs @@ -16,6 +16,7 @@ using System.Reflection; using Castle.MonoRail.Framework; using NUnit.Framework; +using Spark; using Spark.FileSystem; namespace Castle.MonoRail.Views.Spark.Tests.ViewComponents @@ -81,7 +82,7 @@ public void ComponentRenderViewFromEmbeddedResource() Assembly.Load("Castle.MonoRail.Views.Spark.Tests"), "Castle.MonoRail.Views.Spark.Tests.EmbeddedViews"); - engine.ViewFolder = engine.ViewFolder.Append(embeddedViewFolder); + this.factory.Engine.ViewFolder = this.factory.Engine.ViewFolder.Append(embeddedViewFolder); mocks.ReplayAll(); diff --git a/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs b/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs index 138f6233..e604106b 100644 --- a/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs +++ b/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs @@ -77,11 +77,12 @@ public override void Install(IDictionary stateSaver) // Attempt to get the configuration from settings, otherwise use default settings var settings = (ISparkSettings)config.GetSection("spark") ?? - new SparkSettings(); + new SparkSettings() + .SetPageBaseType(typeof(SparkView)); var services = new StubMonoRailServices(); + services.AddService(typeof(ISparkSettings), settings); services.AddService(typeof(IViewSourceLoader), new FileAssemblyViewSourceLoader(viewsLocation)); - services.AddService(typeof(ISparkViewEngine), new SparkViewEngine(settings)); services.AddService(typeof(IControllerDescriptorProvider), services.ControllerDescriptorProvider); var factory = new SparkViewFactory(); diff --git a/src/Castle.MonoRail.Views.Spark/SparkView.cs b/src/Castle.MonoRail.Views.Spark/SparkView.cs index 8c5a1f16..6dc6c525 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkView.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkView.cs @@ -35,6 +35,7 @@ protected SparkView() private IEngineContext _context; private IControllerContext _controllerContext; + private IResourcePathManager _resourcePathManager; private SparkViewFactory _viewEngine; private IDictionary _contextVars; @@ -54,7 +55,7 @@ protected SparkView() public string SiteRoot { get { return _context.ApplicationPath; } } public string SiteResource(string path) { - return _viewEngine.Engine.ResourcePathManager.GetResourcePath(SiteRoot, path); + return this._resourcePathManager.GetResourcePath(SiteRoot, path); } public IDictionary PropertyBag { get { return _contextVars ?? _controllerContext.PropertyBag; } } @@ -91,10 +92,11 @@ public string Eval(string expression, string format) public T Helper() where T : class { return ControllerContext.Helpers[typeof(T).Name] as T; } public T Helper(string name) where T : class { return ControllerContext.Helpers[name] as T; } - public virtual void Contextualize(IEngineContext context, IControllerContext controllerContext, SparkViewFactory viewEngine, SparkView outerView) + public virtual void Contextualize(IEngineContext context, IControllerContext controllerContext, IResourcePathManager resourcePathManager, SparkViewFactory viewEngine, SparkView outerView) { _context = context; _controllerContext = controllerContext; + _resourcePathManager = resourcePathManager; _viewEngine = viewEngine; if (_viewEngine != null && _viewEngine.CacheServiceProvider != null) @@ -132,7 +134,7 @@ public void RenderComponent( var service = (IViewComponentFactory)_context.GetService(typeof(IViewComponentFactory)); var component = service.Create(name); - IViewComponentContext viewComponentContext = new ViewComponentContext(this, _viewEngine, name, parameters, body, sections); + IViewComponentContext viewComponentContext = new ViewComponentContext(this, _resourcePathManager, _viewEngine, name, parameters, body, sections); var oldContextVars = _contextVars; try diff --git a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs index 53cf70ac..6ffb7099 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs @@ -26,7 +26,12 @@ using Castle.MonoRail.Framework.Resources; using Castle.MonoRail.Framework.Routing; using Castle.MonoRail.Views.Spark.Wrappers; +using Spark.Bindings; using Spark.Compiler; +using Spark.Compiler.CodeDom; +using Spark.Compiler.Roslyn; +using Spark.Parser; +using Spark.Parser.Syntax; namespace Castle.MonoRail.Views.Spark { @@ -36,6 +41,7 @@ public class SparkViewFactory : ViewEngineBase, IViewSourceLoaderContainer { private IControllerDescriptorProvider _controllerDescriptorProvider; private IViewActivatorFactory _viewActivatorFactory; + private IResourcePathManager _resourcePathManager; public override void Service(IServiceProvider provider) { @@ -43,9 +49,10 @@ public override void Service(IServiceProvider provider) _controllerDescriptorProvider = (IControllerDescriptorProvider)provider.GetService(typeof(IControllerDescriptorProvider)); _viewActivatorFactory = (IViewActivatorFactory)provider.GetService(typeof(IViewActivatorFactory)); + _resourcePathManager = (IResourcePathManager)provider.GetService(typeof(IResourcePathManager)); _cacheServiceProvider = (ICacheServiceProvider)provider.GetService(typeof(ICacheServiceProvider)); - SetEngine((ISparkViewEngine)provider.GetService(typeof(ISparkViewEngine))); + Engine = (ISparkViewEngine)provider.GetService(typeof(ISparkViewEngine)); } private ISparkViewEngine _engine; @@ -55,29 +62,35 @@ public ISparkViewEngine Engine { if (_engine == null) { - SetEngine(new SparkViewEngine(new SparkSettings())); + var settings = + (ISparkSettings)this.serviceProvider.GetService(typeof(ISparkSettings)) + ?? new SparkSettings() + .SetPageBaseType(typeof(SparkView)); + + var partialProvider = new DefaultPartialProvider(); + + var viewFolder = new ViewSourceLoaderWrapper(this); + + var batchCompiler = new CodeDomBatchCompiler(); + + this._engine = + new SparkViewEngine( + settings, + new DefaultSyntaxProvider(settings), + this._viewActivatorFactory ?? new DefaultViewActivator(), + new DefaultLanguageFactory(batchCompiler), + new CompiledViewHolder(), + viewFolder, + batchCompiler, + partialProvider, + new DefaultPartialReferenceProvider(partialProvider), + new DefaultBindingProvider(), + new ViewComponentExtensionFactory(this.serviceProvider)); } return _engine; } - set - { - SetEngine(value); - } - } - - private void SetEngine(ISparkViewEngine engine) - { - _engine = engine; - if (_engine == null) - return; - - _engine.ViewFolder = new ViewSourceLoaderWrapper(this); - _engine.ExtensionFactory = new ViewComponentExtensionFactory(serviceProvider); - _engine.DefaultPageBaseType = typeof(SparkView).FullName; - - if (_viewActivatorFactory != null) - _engine.ViewActivatorFactory = _viewActivatorFactory; + set => this._engine = value; } private ICacheServiceProvider _cacheServiceProvider; @@ -156,7 +169,7 @@ public override void Process(string templateName, TextWriter output, IEngineCont var entry = Engine.CreateEntry(descriptor); var view = (SparkView)entry.CreateInstance(); - view.Contextualize(context, controllerContext, this, null); + view.Contextualize(context, controllerContext, _resourcePathManager, this, null); if (view.Logger == null || view.Logger == NullLogger.Instance) view.Logger = Logger; view.RenderView(output); diff --git a/src/Castle.MonoRail.Views.Spark/ViewComponentContext.cs b/src/Castle.MonoRail.Views.Spark/ViewComponentContext.cs index df8430a0..1b0e7d20 100644 --- a/src/Castle.MonoRail.Views.Spark/ViewComponentContext.cs +++ b/src/Castle.MonoRail.Views.Spark/ViewComponentContext.cs @@ -26,6 +26,7 @@ namespace Castle.MonoRail.Views.Spark public class ViewComponentContext : IViewComponentContext { private readonly SparkView _view; + private readonly IResourcePathManager _resourcePathManager; private readonly SparkViewFactory _viewEngine; private readonly string _name; private readonly IDictionary _componentParameters; @@ -33,10 +34,10 @@ public class ViewComponentContext : IViewComponentContext private readonly IDictionary _sections; private readonly IDictionary _contextVarsAdapter; - - public ViewComponentContext(SparkView view, SparkViewFactory viewEngine, string name, IDictionary componentParameters, Action body, IDictionary sections) + public ViewComponentContext(SparkView view, IResourcePathManager resourcePathManager, SparkViewFactory viewEngine, string name, IDictionary componentParameters, Action body, IDictionary sections) { _view = view; + _resourcePathManager = resourcePathManager; _viewEngine = viewEngine; _name = name; _componentParameters = componentParameters; @@ -68,7 +69,7 @@ public void RenderView(string name, TextWriter writer) try { - componentView.Contextualize(_view.Context, _view.ControllerContext, _viewEngine, _view); + componentView.Contextualize(_view.Context, _view.ControllerContext, _resourcePathManager, _viewEngine, _view); componentView.RenderView(writer); } finally diff --git a/src/Spark.JsTests/Generate.ashx.cs b/src/Spark.JsTests/Generate.ashx.cs index f4cc82f2..299b1bd4 100644 --- a/src/Spark.JsTests/Generate.ashx.cs +++ b/src/Spark.JsTests/Generate.ashx.cs @@ -16,7 +16,11 @@ using System.IO; using System.Web; using System.Web.Services; +using Spark.Bindings; +using Spark.Compiler.Roslyn; using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; namespace Spark.JsTests { @@ -29,13 +33,31 @@ public class Generate : IHttpHandler { public void ProcessRequest(HttpContext context) { - var engine = new SparkViewEngine - { - ViewFolder = new VirtualPathProviderViewFolder("~/Views") - }; - var entry = engine.CreateEntry(new SparkViewDescriptor() - .SetLanguage(LanguageType.Javascript) - .AddTemplate(context.Request.PathInfo.TrimStart('/', Path.DirectorySeparatorChar) + ".spark")); + var settings = new SparkSettings().SetDefaultLanguage(LanguageType.Javascript); + + var viewFolder = new VirtualPathProviderViewFolder("~/Views"); + + var partialProvider = new DefaultPartialProvider(); + + var batchCompiler = new RoslynBatchCompiler(); + + var engine = new SparkViewEngine( + settings, + new DefaultSyntaxProvider(settings), + new DefaultViewActivator(), + new DefaultLanguageFactory(batchCompiler), + new CompiledViewHolder(), + viewFolder, + batchCompiler, + partialProvider, + new DefaultPartialReferenceProvider(partialProvider), + new DefaultBindingProvider(), + null); + + var entry = engine.CreateEntry( + new SparkViewDescriptor() + .SetLanguage(LanguageType.Javascript) + .AddTemplate(context.Request.PathInfo.TrimStart('/', Path.DirectorySeparatorChar) + ".spark")); //Spark.Simple._LiteralHtml({foo:'asoi'}) context.Response.ContentType = "text/javascript"; diff --git a/src/Spark.Python.Tests/PythonViewCompilerTests.cs b/src/Spark.Python.Tests/PythonViewCompilerTests.cs index ceec7215..c3720f99 100644 --- a/src/Spark.Python.Tests/PythonViewCompilerTests.cs +++ b/src/Spark.Python.Tests/PythonViewCompilerTests.cs @@ -17,6 +17,7 @@ using System.IO; using NUnit.Framework; using Spark.Compiler; +using Spark.Compiler.Roslyn; using Spark.Parser; using Spark.Python.Compiler; using Spark.Tests.Models; @@ -37,7 +38,7 @@ public void Init() { BaseClass = typeof(StubSparkView).FullName }; - _languageFactory = new PythonLanguageFactory(); + _languageFactory = new PythonLanguageFactory(new RoslynBatchCompiler()); // Load up assemblies IronPython.Hosting.Python.CreateEngine(); diff --git a/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs b/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs index 37b5e7d3..e4f90f4d 100644 --- a/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs +++ b/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // + +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.Python.Compiler; using Spark.Tests.Stubs; @@ -26,11 +29,15 @@ public class ScriptingLanguageFactoryTests [SetUp] public void Init() { - _engine = new SparkViewEngine(new SparkSettings()) - { - LanguageFactory = new PythonLanguageFactory(), - DefaultPageBaseType = typeof(StubSparkView).FullName - }; + var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton() + .BuildServiceProvider();; + + _engine = (SparkViewEngine)sp.GetService(); + IronPython.Hosting.Python.CreateEngine(); } diff --git a/src/Spark.Python/Compiler/PythonViewCompiler.cs b/src/Spark.Python/Compiler/PythonViewCompiler.cs index 04ed8448..b60418f8 100644 --- a/src/Spark.Python/Compiler/PythonViewCompiler.cs +++ b/src/Spark.Python/Compiler/PythonViewCompiler.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Text; using Spark.Compiler; +using Spark.Compiler.CodeDom; using Spark.Compiler.CSharp.ChunkVisitors; using GeneratedCodeVisitor=Spark.Python.Compiler.ChunkVisitors.GeneratedCodeVisitor; using GlobalFunctionsVisitor=Spark.Python.Compiler.ChunkVisitors.GlobalFunctionsVisitor; @@ -29,8 +30,8 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new BatchCompiler(); - var assembly = compiler.Compile(Debug, "csharp", SourceCode); + var compiler = new CodeDomBatchCompiler(); + var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Python/PythonLanguageFactory.cs b/src/Spark.Python/PythonLanguageFactory.cs index 318a277e..21aa6705 100644 --- a/src/Spark.Python/PythonLanguageFactory.cs +++ b/src/Spark.Python/PythonLanguageFactory.cs @@ -20,6 +20,10 @@ namespace Spark.Python { public class PythonLanguageFactory : DefaultLanguageFactory { + public PythonLanguageFactory(IBatchCompiler batchCompiler) : base(batchCompiler) + { + } + private PythonEngineManager _PythonEngineManager; public PythonEngineManager PythonEngineManager diff --git a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs index 7c9df532..7099520d 100644 --- a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs +++ b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs @@ -17,6 +17,7 @@ using System.IO; using NUnit.Framework; using Spark.Compiler; +using Spark.Compiler.Roslyn; using Spark.Parser; using Spark.Ruby.Compiler; using Spark.Tests.Models; @@ -34,10 +35,11 @@ public class RubyViewCompilerTests public void Init() { _compiler = new RubyViewCompiler - { - BaseClass = typeof(StubSparkView).FullName,Debug = true - }; - _languageFactory = new RubyLanguageFactory(); + { + BaseClass = typeof(StubSparkView).FullName, + Debug = true + }; + _languageFactory = new RubyLanguageFactory(new RoslynBatchCompiler()); //load assemblies global::IronRuby.Ruby.CreateEngine(); diff --git a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs index 03123925..72e210a7 100644 --- a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs +++ b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; + using System.Collections.Generic; using System.Linq; using System.Text; using Spark.Compiler; -using Spark.Ruby.Compiler; +using Spark.Compiler.CodeDom; using Spark.Ruby.Compiler.ChunkVisitors; using BaseClassVisitor = Spark.Compiler.CSharp.ChunkVisitors.BaseClassVisitor; @@ -31,8 +31,8 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new BatchCompiler(); - var assembly = compiler.Compile(Debug, "csharp", SourceCode); + var compiler = new CodeDomBatchCompiler(); + var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Ruby/RubyLanguageFactory.cs b/src/Spark.Ruby/RubyLanguageFactory.cs index c8b339b1..5917fd62 100644 --- a/src/Spark.Ruby/RubyLanguageFactory.cs +++ b/src/Spark.Ruby/RubyLanguageFactory.cs @@ -20,6 +20,9 @@ namespace Spark.Ruby { public class RubyLanguageFactory : DefaultLanguageFactory { + public RubyLanguageFactory(IBatchCompiler batchCompiler) : base(batchCompiler) + { + } private RubyEngineManager _RubyEngineManager; diff --git a/src/Spark.Tests/CompiledViewHolderTester.cs b/src/Spark.Tests/CompiledViewHolderTester.cs index 39b271dc..4361dac6 100644 --- a/src/Spark.Tests/CompiledViewHolderTester.cs +++ b/src/Spark.Tests/CompiledViewHolderTester.cs @@ -17,6 +17,9 @@ using Spark.Parser; using NUnit.Framework; using Rhino.Mocks; +using Spark.Bindings; +using Spark.FileSystem; +using Spark.Parser.Syntax; namespace Spark.Tests { @@ -51,7 +54,21 @@ public void LookupNonExistentReturnsNull() public void LookupReturnsStoredInstance() { var key = BuildKey(Path.Combine("c", "v"), Path.Combine("shared", "m")); - var entry = new CompiledViewEntry { Descriptor = key, Loader = new ViewLoader() }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + MockRepository.GenerateMock(), + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); + + var entry = new CompiledViewEntry { Descriptor = key, Loader = viewLoader }; Assert.IsNull(holder.Lookup(key)); holder.Store(entry); Assert.AreSame(entry, holder.Lookup(key)); @@ -77,24 +94,43 @@ public void VariousKeyEqualities() Assert.That(!Equals(null, key1)); } - bool isCurrent; + public class FakeViewLoader : ViewLoader + { + public FakeViewLoader() : base(null, null, null, null, null, null, null) + { + } + + public bool IsCurrentValue { get; set; } + + public override bool IsCurrent() + { + return IsCurrentValue; + } + } [Test] public void ExpiredEntryReturnsNull() { - var loader = MockRepository.GenerateMock(); - - isCurrent = true; - Func foo = () => isCurrent; - loader.Stub(x => x.IsCurrent()).Do(foo); + var loader = new FakeViewLoader + { + IsCurrentValue = true + }; var key = BuildKey(Path.Combine("c", "v"), Path.Combine("shared", "m")); - var entry = new CompiledViewEntry { Descriptor = key, Loader = loader }; + + var entry = new CompiledViewEntry + { + Descriptor = key, + Loader = loader + }; + holder.Store(entry); + Assert.AreSame(entry, holder.Lookup(key)); - isCurrent = false; - Assert.IsNull(holder.Lookup(key)); + loader.IsCurrentValue = false; + + Assert.IsNull(holder.Lookup(key)); } [Test] diff --git a/src/Spark.Tests/Parser/ViewLoaderTester.cs b/src/Spark.Tests/Parser/ViewLoaderTester.cs index 1becdc8d..f0445afd 100644 --- a/src/Spark.Tests/Parser/ViewLoaderTester.cs +++ b/src/Spark.Tests/Parser/ViewLoaderTester.cs @@ -47,17 +47,36 @@ public void Init() viewFolder.Stub(x => x.ListViews(Arg.Is.Anything)).IgnoreArguments().Return(Array.Empty()); syntaxProvider = MockRepository.GenerateMock(); + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); - loader = new ViewLoader { ViewFolder = viewFolder, SyntaxProvider = syntaxProvider }; + loader = new ViewLoader(settings, viewFolder, partialProvider, new DefaultPartialReferenceProvider(partialProvider), null, syntaxProvider, null); } + private static int syntaxCallbacks = 0; + IViewFile ExpectGetChunks(string path, params Chunk[] chunks) { var source = MockRepository.GenerateMock(); - viewFolder.Expect(x => x.GetViewSource(path)).Return(source); - source.Expect(x => x.LastModified).Return(0); - syntaxProvider.Expect(x => x.GetChunks(Arg.Is.Anything, Arg.Is.Anything)).Return(chunks); + viewFolder + .Expect(x => x.GetViewSource(path)) + .Return(source); + + source + .Expect(x => x.LastModified) + .Return(0); + + syntaxProvider + .Expect(x => x.GetChunks(Arg.Is.Anything, Arg.Is.Anything)) + .WhenCalled( + call => + { + syntaxCallbacks++; + }) + .Return(chunks); return source; } @@ -145,14 +164,21 @@ public void FileNotFoundException() [Test] public void ExpiresWhenFilesChange() { - var viewFolder = new StubViewFolder { Path = Path.Combine("home", "changing.spark"), LastModified = 4 }; + var settings = new SparkSettings(); - var viewLoader = new ViewLoader - { - ViewFolder = viewFolder, - SyntaxProvider = MockRepository.GenerateStub() - }; - viewLoader.SyntaxProvider + var viewFolder = new StubViewFolder + { + Path = Path.Combine("home", "changing.spark"), + LastModified = 4 + }; + + this.syntaxProvider = MockRepository.GenerateStub(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader(settings, viewFolder, partialProvider, new DefaultPartialReferenceProvider(partialProvider), null, this.syntaxProvider, null); + + this.syntaxProvider .Expect(x => x.GetChunks(null, null)) .IgnoreArguments() .Return(Array.Empty()); @@ -181,12 +207,13 @@ public void BindingProviderIsCalledUsingTheCorrectBindingRequest() syntaxProvider.Stub(x => x.GetChunks(null, null)) .IgnoreArguments() .Return(new List()); - var viewLoader = new ViewLoader - { - ViewFolder = folder, - SyntaxProvider = syntaxProvider, - BindingProvider = bindingProvider - }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader(settings, folder, partialProvider, new DefaultPartialReferenceProvider(partialProvider), null, syntaxProvider, bindingProvider); + viewLoader.Load(viewPath); bindingProvider.VerifyAllExpectations(); @@ -234,7 +261,20 @@ public void LoadingPartialInsideNamedSection() {Path.Combine("home", "_Guts.spark"), "
"}, {Path.Combine("home", "_Another.spark"), "

hello world

"} }; - var viewLoader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); + var chunks = viewLoader.Load(Path.Combine("home", "index.spark")); var everything = viewLoader.GetEverythingLoaded(); @@ -254,7 +294,19 @@ public void PartialsInSameFolderAreDiscovered() {Path.Combine("invoice", "five.spark"), ""}, }; - var viewLoader = new ViewLoader { ViewFolder = viewFolder }; + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); + var zero = viewLoader.FindPartialFiles(Path.Combine("home", "zero.spark")); var product = viewLoader.FindPartialFiles(Path.Combine("product", "two.spark")); var invoice = viewLoader.FindPartialFiles(Path.Combine("invoice", "five.spark")); @@ -299,7 +351,18 @@ public void PartialsInCascadingBaseFoldersAndSharedFoldersAreDiscovered() {Path.Combine("area2","Shared","_dontfind3.spark"), ""}, }; - var viewLoader = new ViewLoader { ViewFolder = viewFolder }; + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); var partials = viewLoader.FindPartialFiles(Path.Combine("area1","controller2","view3.spark")); @@ -321,7 +384,20 @@ public void LoadingEmptyFile() { { Path.Combine("home", "empty.spark"), "" }, }; - var viewLoader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); + var chunks = viewLoader.Load(Path.Combine("home", "empty.spark")); var everything = viewLoader.GetEverythingLoaded(); @@ -335,7 +411,20 @@ public void LoadingEmptyShadeFile() { { Path.Combine("home", "empty.shade"), "" }, }; - var viewLoader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var viewLoader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); + var chunks = viewLoader.Load(Path.Combine("home", "empty.shade")); var everything = viewLoader.GetEverythingLoaded(); diff --git a/src/Spark.Web.Mvc.Pdf.Tests/PdfViewResultTests.cs b/src/Spark.Web.Mvc.Pdf.Tests/PdfViewResultTests.cs index 674f8b0a..1bd653f2 100644 --- a/src/Spark.Web.Mvc.Pdf.Tests/PdfViewResultTests.cs +++ b/src/Spark.Web.Mvc.Pdf.Tests/PdfViewResultTests.cs @@ -2,9 +2,11 @@ using System.IO; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; using Spark.FileSystem; +using Spark.Web.Mvc.Extensions; using Spark.Web.Mvc.Tests; namespace Spark.Web.Mvc.Pdf.Tests @@ -62,17 +64,20 @@ private static IViewEngine MockViewEngine(ControllerContext controllerContext, o public void PdfResultShouldWriteToOutputStream() { var settings = new SparkSettings(); - var viewFolder = new InMemoryViewFolder - { - { - "foo/bar.spark", - HelloWorldXml - } - }; - var factory = new SparkViewFactory(settings) - { - ViewFolder = viewFolder - }; + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton( + new InMemoryViewFolder + { + { + "foo/bar.spark", + HelloWorldXml + } + }) + .BuildServiceProvider(); + + var factory = sp.GetService(); var stream = new MemoryStream(); var controllerContext = GetControllerContext(stream); diff --git a/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs b/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs index 6013fa35..71adfd2e 100644 --- a/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs +++ b/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs @@ -15,6 +15,7 @@ using System.Web.Mvc; using Microsoft.Scripting.Runtime; using Spark.Compiler; +using Spark.Compiler.Roslyn; using Spark.Python; [assembly: ExtensionType(typeof(HtmlHelper), typeof(System.Web.Mvc.Html.FormExtensions))] @@ -31,6 +32,10 @@ namespace Spark.Web.Mvc.Python { public class PythonLanguageFactoryWithExtensions : PythonLanguageFactory { + public PythonLanguageFactoryWithExtensions(IBatchCompiler batchCompiler) : base(batchCompiler) + { + } + private bool _initialized; public override ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkViewDescriptor descriptor) diff --git a/src/Spark.Web.Mvc.Python/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc.Python/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..a8bbba34 --- /dev/null +++ b/src/Spark.Web.Mvc.Python/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Spark.Web.Mvc.Extensions; + +namespace Spark.Web.Mvc.Python +{ + public static class ServiceCollectionExtensions + { + /// + /// Registers spark dependencies in the service collection. + /// + /// + /// + /// + public static IServiceCollection AddSparkPython(this IServiceCollection services, ISparkSettings settings = null) + { + services.AddSpark(settings); + + // Override ISparkLanguageFactory + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Spark.Web.Mvc.Python/SparkPythonEngineStarter.cs b/src/Spark.Web.Mvc.Python/SparkPythonEngineStarter.cs deleted file mode 100644 index 8f01db2a..00000000 --- a/src/Spark.Web.Mvc.Python/SparkPythonEngineStarter.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System.Collections.Generic; -using System.Web.Mvc; -using Spark.Web.Mvc.Python; - -namespace Spark.Web.Mvc.Python -{ - public static class SparkPythonEngineStarter - { - /// - /// Adds Asp.Net Mvc specific IViewEngine implementation and the IronPython - /// view language factory to a spark service container. - /// - /// An instance of the spark service container to modify - public static void ConfigureContainer(ISparkServiceContainer container) - { - container.SetServiceBuilder(c => new SparkViewFactory(c.GetService())); - container.SetServiceBuilder(c => new PythonLanguageFactoryWithExtensions()); - } - - /// - /// Create an IronPython enabled Spark service container - /// - /// A configured service container. Additional service builders may - /// me added. - public static ISparkServiceContainer CreateContainer() - { - var container = new SparkServiceContainer(); - ConfigureContainer(container); - return container; - } - - /// - /// Create an IronPython enabled service container with explicit spark settings. - /// - /// Typically an instance of SparkSettings object - /// A configured service container. Additional service builders may - /// me added. May be passed to RegisterViewEngine. - public static ISparkServiceContainer CreateContainer(ISparkSettings settings) - { - var container = new SparkServiceContainer(settings); - ConfigureContainer(container); - return container; - } - - /// - /// Creates a spark IViewEngine with IronPython as the default language. - /// Settings come from config or are defaulted. - /// - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine() - { - return CreateContainer().GetService(); - } - - /// - /// Creates a spark IViewEngine with IronPython as the default language. - /// - /// Typically an instance of SparkSettings object - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine(ISparkSettings settings) - { - return CreateContainer(settings).GetService(); - } - - /// - /// Installs the Spark+IronPython view engine. Settings come from config or are defaulted. - /// - public static void RegisterViewEngine() - { - ViewEngines.Engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings passed in. - /// - public static void RegisterViewEngine(ISparkSettings settings) - { - ViewEngines.Engines.Add(CreateViewEngine(settings)); - } - - /// - /// Installs the Spark view engine. Service container passed in. - /// - public static void RegisterViewEngine(ISparkServiceContainer container) - { - ViewEngines.Engines.Add(container.GetService()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings come from config or are defaulted. - /// - /// Typically in the ViewEngines.Engines collection - public static void RegisterViewEngine(ICollection engines) - { - engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings passed in. - /// - /// Typically in the ViewEngines.Engines collection - /// Typically an instance of SparkSettings object - public static void RegisterViewEngine(ICollection engines, ISparkSettings settings) - { - engines.Add(CreateViewEngine(settings)); - } - - /// - /// Install the view engine from the container. Typical usage is to call CreateContainer, - /// provide additinal service builder functors to override certain classes, then call this - /// method. - /// - /// Typically the ViewEngines.Engines collection - /// A service container, often created with CreateContainer - public static void RegisterViewEngine(ICollection engines, ISparkServiceContainer container) - { - engines.Add(container.GetService()); - } - } -} \ No newline at end of file diff --git a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs index a0d7b6b0..a4d3c1ea 100644 --- a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs +++ b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs @@ -12,17 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; -using System.Collections.Generic; + using System.IO; -using System.Linq; -using System.Text; using System.Web; using System.Web.Caching; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Compiler.Roslyn; using Spark.FileSystem; +using Spark.Web.Mvc.Extensions; namespace Spark.Web.Mvc.Ruby.Tests { @@ -41,7 +41,10 @@ public class StubController : Controller [Test] public void BuildingScriptHeader() { - var languageFactory = new RubyLanguageFactoryWithExtensions(); + var batchCompiler = new RoslynBatchCompiler(); + + var languageFactory = new RubyLanguageFactoryWithExtensions(batchCompiler); + var header = languageFactory.BuildScriptHeader(languageFactory.GetType().Assembly); Assert.That(header.Contains("ActionLink")); @@ -51,15 +54,18 @@ public void BuildingScriptHeader() private static ViewContext CompileView(string viewContents) { - var settings = new SparkSettings(); - var container = SparkRubyEngineStarter.CreateContainer(settings); + var services = new ServiceCollection() + .AddSpark(new SparkSettings()); + + var viewFolder = new InMemoryViewFolder { { $"stub{Path.DirectorySeparatorChar}index.spark", viewContents } }; - var viewFolder = new InMemoryViewFolder { { string.Format("stub{0}index.spark", Path.DirectorySeparatorChar), viewContents } }; - container.SetServiceBuilder(c => viewFolder); - var viewEngine = container.GetService(); + services.AddSingleton(viewFolder); - var httpContext = new StubHttpContext(); + var serviceProvider = services.BuildServiceProvider(); + + var viewEngine = serviceProvider.GetService(); + var httpContext = new StubHttpContext(); var routeData = new RouteData(); routeData.Values.Add("controller", "stub"); diff --git a/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs b/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs index 56d5a388..b427e760 100644 --- a/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs +++ b/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs @@ -22,6 +22,7 @@ using IronRuby.Runtime; using Microsoft.Scripting.Runtime; using Spark.Compiler; +using Spark.Compiler.Roslyn; using Spark.Ruby; using Spark.Ruby.Compiler; @@ -39,7 +40,12 @@ namespace Spark.Web.Mvc.Ruby { public class RubyLanguageFactoryWithExtensions : RubyLanguageFactory { + public RubyLanguageFactoryWithExtensions(IBatchCompiler batchCompiler) : base(batchCompiler) + { + } + private bool _initialized; + private string _scriptHeader; public override ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkViewDescriptor descriptor) diff --git a/src/Spark.Web.Mvc.Ruby/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc.Ruby/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..f746e85e --- /dev/null +++ b/src/Spark.Web.Mvc.Ruby/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Spark.Web.Mvc.Extensions; + +namespace Spark.Web.Mvc.Ruby +{ + public static class ServiceCollectionExtensions + { + /// + /// Registers spark dependencies in the service collection. + /// + /// + /// + /// + public static IServiceCollection AddSparkRuby(this IServiceCollection services, ISparkSettings settings = null) + { + services.AddSpark(settings); + + // Override ISparkLanguageFactory + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Spark.Web.Mvc.Ruby/SparkRubyEngineStarter.cs b/src/Spark.Web.Mvc.Ruby/SparkRubyEngineStarter.cs deleted file mode 100644 index 2b9579b8..00000000 --- a/src/Spark.Web.Mvc.Ruby/SparkRubyEngineStarter.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System.Collections.Generic; -using System.Web.Mvc; - -namespace Spark.Web.Mvc.Ruby -{ - public static class SparkRubyEngineStarter - { - /// - /// Adds Asp.Net Mvc specific IViewEngine implementation and the IronPython - /// view language factory to a spark service container. - /// - /// An instance of the spark service container to modify - public static void ConfigureContainer(ISparkServiceContainer container) - { - container.SetServiceBuilder(c => new SparkViewFactory(c.GetService())); - container.SetServiceBuilder(c => new RubyLanguageFactoryWithExtensions()); - } - - /// - /// Create an IronPython enabled Spark service container - /// - /// A configured service container. Additional service builders may - /// me added. - public static ISparkServiceContainer CreateContainer() - { - var container = new SparkServiceContainer(); - ConfigureContainer(container); - return container; - } - - /// - /// Create an IronPython enabled service container with explicit spark settings. - /// - /// Typically an instance of SparkSettings object - /// A configured service container. Additional service builders may - /// me added. May be passed to RegisterViewEngine. - public static ISparkServiceContainer CreateContainer(ISparkSettings settings) - { - var container = new SparkServiceContainer(settings); - ConfigureContainer(container); - return container; - } - - /// - /// Creates a spark IViewEngine with IronPython as the default language. - /// Settings come from config or are defaulted. - /// - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine() - { - return CreateContainer().GetService(); - } - - /// - /// Creates a spark IViewEngine with IronPython as the default language. - /// - /// Typically an instance of SparkSettings object - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine(ISparkSettings settings) - { - return CreateContainer(settings).GetService(); - } - - /// - /// Installs the Spark+IronPython view engine. Settings come from config or are defaulted. - /// - public static void RegisterViewEngine() - { - ViewEngines.Engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings passed in. - /// - public static void RegisterViewEngine(ISparkSettings settings) - { - ViewEngines.Engines.Add(CreateViewEngine(settings)); - } - - /// - /// Installs the Spark view engine. Service container passed in. - /// - public static void RegisterViewEngine(ISparkServiceContainer container) - { - ViewEngines.Engines.Add(container.GetService()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings come from config or are defaulted. - /// - /// Typically in the ViewEngines.Engines collection - public static void RegisterViewEngine(ICollection engines) - { - engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark+IronPython view engine. Settings passed in. - /// - /// Typically in the ViewEngines.Engines collection - /// Typically an instance of SparkSettings object - public static void RegisterViewEngine(ICollection engines, ISparkSettings settings) - { - engines.Add(CreateViewEngine(settings)); - } - - /// - /// Install the view engine from the container. Typical usage is to call CreateContainer, - /// provide additinal service builder functors to override certain classes, then call this - /// method. - /// - /// Typically the ViewEngines.Engines collection - /// A service container, often created with CreateContainer - public static void RegisterViewEngine(ICollection engines, ISparkServiceContainer container) - { - engines.Add(container.GetService()); - } - } -} \ No newline at end of file diff --git a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs index 088da660..f160edcd 100644 --- a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs +++ b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs @@ -16,16 +16,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; using Spark.FileSystem; using Spark.Parser; using Spark.Web.Mvc.Descriptors; +using Spark.Web.Mvc.Extensions; namespace Spark.Web.Mvc.Tests { @@ -37,13 +38,35 @@ public class DescriptorBuildingTester private RouteData _routeData; private ControllerContext _controllerContext; + public static IServiceProvider SetupServiceProvider(ISparkSettings settings, Action serviceOverrides = null) + { + IServiceCollection services = new ServiceCollection() + .AddSpark(settings); + + if (serviceOverrides != null) + { + serviceOverrides.Invoke(services); + } + + return services.BuildServiceProvider(); + } + [SetUp] public void Init() { - _factory = new SparkViewFactory(); + var settings = new SparkSettings(); + _viewFolder = new InMemoryViewFolder(); - _factory.ViewFolder = _viewFolder; + var sp = SetupServiceProvider( + settings, + services => + { + services.AddSingleton(this._viewFolder); + }); + + _factory = sp.GetService(); + var httpContext = MockRepository.GenerateStub(); _routeData = new RouteData(); var controller = MockRepository.GenerateStub(); @@ -573,7 +596,17 @@ private static IDictionary Dict(IEnumerable values) [Test] public void CustomParameterAddedToViewSearchPath() { - _factory.DescriptorBuilder = new ExtendingDescriptorBuilderWithInheritance(_factory.Engine); + var settings = new SparkSettings(); + + var sp = SetupServiceProvider(settings, + s => + { + s.AddSingleton(this._viewFolder); + s.AddSingleton(); + }); + + var factory = sp.GetService(); + _routeData.Values.Add("controller", "Home"); _routeData.Values.Add("language", "en-us"); _viewFolder.Add(@"Home\Index.en-us.spark", ""); @@ -584,7 +617,8 @@ public void CustomParameterAddedToViewSearchPath() _viewFolder.Add(@"Layouts\Application.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + AssertDescriptorTemplates( result, searchedLocations, @@ -594,12 +628,8 @@ public void CustomParameterAddedToViewSearchPath() public class ExtendingDescriptorBuilderWithInheritance : DefaultDescriptorBuilder { - public ExtendingDescriptorBuilderWithInheritance() - { - } - - public ExtendingDescriptorBuilderWithInheritance(ISparkViewEngine engine) - : base(engine) + public ExtendingDescriptorBuilderWithInheritance(ISparkSettings settings, IViewFolder viewFolder) + : base(settings, viewFolder) { } @@ -649,16 +679,30 @@ static IEnumerable Merge(IEnumerable locations, string region) [Test] public void CustomDescriptorBuildersCantUseDescriptorFilters() { - _factory.DescriptorBuilder = MockRepository.GenerateStub(); - Assert.That(() => - _factory.AddFilter(MockRepository.GenerateStub()), - Throws.TypeOf()); + var settings = new SparkSettings(); + + var factory = new SparkViewFactory( + settings, + null, + MockRepository.GenerateStub(), + null, + null); + + Assert.That( + () => + factory.AddFilter(MockRepository.GenerateStub()), + Throws.TypeOf()); } [Test] public void SimplifiedUseMasterGrammarDetectsElementCorrectly() { - var builder = new DefaultDescriptorBuilder(); + var settings = new SparkSettings + { + Prefix = null + }; + + var builder = new DefaultDescriptorBuilder(settings, null); var a = builder.ParseUseMaster(new Position(new SourceContext(""))); var b = builder.ParseUseMaster(new Position(new SourceContext(""))); @@ -678,7 +722,12 @@ public void SimplifiedUseMasterGrammarDetectsElementCorrectly() [Test] public void SimplifiedUseMasterGrammarWithPrefixDetectsElementCorrectly() { - var builder = new DefaultDescriptorBuilder("s"); + var settings = new SparkSettings + { + Prefix = "s" + }; + + var builder = new DefaultDescriptorBuilder(settings, null); var a = builder.ParseUseMaster(new Position(new SourceContext(""))); var b = builder.ParseUseMaster(new Position(new SourceContext(""))); diff --git a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs index 6052f04e..0933dadc 100644 --- a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs @@ -17,8 +17,12 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Compiler; +using Spark.Compiler.CodeDom; using Spark.FileSystem; +using Spark.Web.Mvc.Extensions; using Spark.Web.Mvc.Tests.Controllers; namespace Spark.Web.Mvc.Tests @@ -28,12 +32,30 @@ public class SparkBatchCompilerTester { #region Setup/Teardown + public static IServiceProvider SetupServiceProvider(Action serviceOverrides = null) + { + var services = new ServiceCollection(); + + services.AddSpark(new SparkSettings()); + + if (serviceOverrides != null) + { + serviceOverrides.Invoke(services); + } + + return services.BuildServiceProvider(); + } + [SetUp] public void Init() { - var settings = new SparkSettings(); + var serviceProvider = SetupServiceProvider( + s => + { + s.AddSingleton(new FileSystemViewFolder("AspNetMvc.Tests.Views")); + }); - _factory = new SparkViewFactory(settings) { ViewFolder = new FileSystemViewFolder("AspNetMvc.Tests.Views") }; + _factory = serviceProvider.GetService(); } #endregion @@ -52,7 +74,7 @@ public void CompileBatchDescriptor() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(3, assembly.GetTypes().Length); + Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] @@ -67,8 +89,11 @@ public void CanHandleCSharpV3SyntaxWhenLoadedInAppDomainWithoutConfig() try { sandbox = AppDomain.CreateDomain("sandbox", null, appDomainSetup); - var remoteRunner = (PrecompileRunner) sandbox.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, - typeof(PrecompileRunner).FullName); + + var remoteRunner = (PrecompileRunner)sandbox.CreateInstanceAndUnwrap( + Assembly.GetExecutingAssembly().FullName, + typeof(PrecompileRunner).FullName); + remoteRunner.Precompile(); } finally @@ -84,9 +109,16 @@ public class PrecompileRunner : MarshalByRefObject { public void Precompile() { - var settings = new SparkSettings(); + var serviceProvider = SetupServiceProvider( + s => + { + // Don't know why but the roslin compiler complains + // in CanHandleCSharpV3SyntaxWhenLoadedInAppDomainWithoutConfig test + s.AddSingleton(); + s.AddSingleton(new FileSystemViewFolder("AspNetMvc.Tests.Views")); + }); - var factory = new SparkViewFactory(settings) { ViewFolder = new FileSystemViewFolder("AspNetMvc.Tests.Views") }; + var factory = serviceProvider.GetService(); var batch = new SparkBatchDescriptor(); @@ -143,7 +175,7 @@ public void MultipleLayoutFiles() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(4, assembly.GetTypes().Length); + Assert.AreEqual(4, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] @@ -170,7 +202,7 @@ public void WildcardIncludeRules() var assembly = _factory.Precompile(batch); Assert.IsNotNull(assembly); - Assert.AreEqual(3, assembly.GetTypes().Length); + Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); } [Test] @@ -182,6 +214,15 @@ public void FileWithoutSparkExtensionAreIgnored() { string.Format("Stub{0}Helper.cs", Path.DirectorySeparatorChar), "// this is a code file" }, { string.Format("Layouts{0}Stub.spark", Path.DirectorySeparatorChar), "

layout

" }, }; + + var sp = SetupServiceProvider( + s => + { + s.AddSingleton(viewFolder); + }); + + _factory = sp.GetService(); + var batch = new SparkBatchDescriptor(); batch.For(); diff --git a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs index 80e2e0af..e541eef8 100644 --- a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs @@ -21,9 +21,11 @@ using System.Web.Mvc.Html; using System.Web.Routing; using System.Web.SessionState; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; using Spark.FileSystem; +using Spark.Web.Mvc.Extensions; using Spark.Web.Mvc.Tests.Controllers; using Spark.Web.Mvc.Tests.Models; @@ -34,6 +36,20 @@ public class SparkViewFactoryTester { #region Setup/Teardown + public static IServiceProvider SetupServiceProvider(ISparkSettings settings, Action serviceOverrides = null) + { + var services = new ServiceCollection(); + + services.AddSpark(settings); + + if (serviceOverrides != null) + { + serviceOverrides.Invoke(services); + } + + return services.BuildServiceProvider(); + } + [SetUp] public void Init() { @@ -78,8 +94,17 @@ public void Init() controllerContext = new ControllerContext(httpContext, routeData, controller); - var settings = new SparkSettings().AddNamespace("System.Web.Mvc.Html").SetAutomaticEncoding(true); - factory = new SparkViewFactory(settings) { ViewFolder = new FileSystemViewFolder("AspNetMvc.Tests.Views") }; + var settings = new SparkSettings().AddNamespace("System.Web.Mvc.Html") + .SetAutomaticEncoding(true); + + var serviceProvider = SetupServiceProvider( + settings, + s => + { + s.AddSingleton(new FileSystemViewFolder("AspNetMvc.Tests.Views")); + }); + + factory = serviceProvider.GetService(); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); @@ -150,7 +175,8 @@ private static void ContainsInOrder(string content, params string[] values) foreach (var value in values) { var nextIndex = content.IndexOf(value, index); - Assert.GreaterOrEqual(nextIndex, 0, string.Format("Looking for {0}", value)); + + Assert.GreaterOrEqual(nextIndex, 0, $"Looking for {value}"); index = nextIndex + value.Length; } } @@ -241,14 +267,20 @@ public void HtmlHelperWorksOnItsOwn() [Test] public void MasterApplicationIfPresent() { - factory.ViewFolder = new InMemoryViewFolder - { - {string.Format("Foo{0}Baaz.spark", Path.DirectorySeparatorChar), ""}, - {string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), ""} - }; - + var viewFolder = new InMemoryViewFolder + { + { $"Foo{Path.DirectorySeparatorChar}Baaz.spark", "" }, + { $"Shared{Path.DirectorySeparatorChar}Application.spark", "" } + }; + var sp = SetupServiceProvider( + new SparkSettings(), + s => + { + s.AddSingleton(viewFolder); + }); + factory = sp.GetService(); routeData.Values["controller"] = "Foo"; routeData.Values["action"] = "NotBaaz"; @@ -258,17 +290,26 @@ public void MasterApplicationIfPresent() //mocks.VerifyAll(); Assert.AreEqual(2, descriptor.Templates.Count); - Assert.AreEqual(string.Format("Foo{0}Baaz.spark", Path.DirectorySeparatorChar), descriptor.Templates[0]); - Assert.AreEqual(string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), descriptor.Templates[1]); + Assert.AreEqual($"Foo{Path.DirectorySeparatorChar}Baaz.spark", descriptor.Templates[0]); + Assert.AreEqual($"Shared{Path.DirectorySeparatorChar}Application.spark", descriptor.Templates[1]); } [Test] public void MasterEmptyByDefault() { - factory.ViewFolder = new InMemoryViewFolder - { - {string.Format("Foo{0}Baaz.spark", Path.DirectorySeparatorChar), ""} - }; + var viewFolder = new InMemoryViewFolder + { + { $"Foo{Path.DirectorySeparatorChar}Baaz.spark", "" } + }; + + var sp = SetupServiceProvider( + new SparkSettings(), + s => + { + s.AddSingleton(viewFolder); + }); + + factory = sp.GetService(); routeData.Values["controller"] = "Foo"; routeData.Values["action"] = "NotBaaz"; @@ -276,21 +317,27 @@ public void MasterEmptyByDefault() var descriptor = factory.CreateDescriptor(controllerContext, "Baaz", null, true, null); Assert.AreEqual(1, descriptor.Templates.Count); - Assert.AreEqual(string.Format("Foo{0}Baaz.spark", Path.DirectorySeparatorChar), descriptor.Templates[0]); + Assert.AreEqual($"Foo{Path.DirectorySeparatorChar}Baaz.spark", descriptor.Templates[0]); } [Test] public void MasterForControllerIfPresent() { - factory.ViewFolder = new InMemoryViewFolder - { - {string.Format("Foo{0}Baaz.spark", Path.DirectorySeparatorChar), ""}, - {string.Format("Shared{0}Foo.spark", Path.DirectorySeparatorChar),""} - }; - - - - + var viewFolder = new InMemoryViewFolder + { + { $"Foo{Path.DirectorySeparatorChar}Baaz.spark", "" }, + { $"Shared{Path.DirectorySeparatorChar}Foo.spark", "" } + }; + + var sp = SetupServiceProvider( + new SparkSettings(), + s => + { + s.AddSingleton(viewFolder); + }); + + factory = sp.GetService(); + routeData.Values["controller"] = "Foo"; routeData.Values["action"] = "NotBaaz"; @@ -355,16 +402,23 @@ public void RenderPlainView() [Test] public void TargetNamespaceFromController() { - factory.ViewFolder = new InMemoryViewFolder - { - {string.Format("Home{0}Baaz.spark", Path.DirectorySeparatorChar), ""}, - {string.Format("Layouts{0}Home.spark", Path.DirectorySeparatorChar),""} - }; + var viewFolder = new InMemoryViewFolder + { + { $"Home{Path.DirectorySeparatorChar}Baaz.spark", "" }, + { $"Layouts{Path.DirectorySeparatorChar}Home.spark", "" } + }; - controller = new StubController(); - controllerContext = new ControllerContext(httpContext, routeData, controller); + var sp = SetupServiceProvider( + new SparkSettings(), + s => + { + s.AddSingleton(viewFolder); + }); + factory = sp.GetService(); + controller = new StubController(); + controllerContext = new ControllerContext(httpContext, routeData, controller); var descriptor = factory.CreateDescriptor(controllerContext, "Baaz", null, true, null); //mocks.VerifyAll(); @@ -440,35 +494,36 @@ public void ViewSourceLoaderCanBeChanged() { var replacement = MockRepository.GenerateStub(); + var existing = factory.Engine.ViewFolder; - - var existing = factory.ViewFolder; Assert.AreNotSame(existing, replacement); - Assert.AreSame(existing, factory.ViewFolder); + Assert.AreSame(existing, factory.Engine.ViewFolder); - factory.ViewFolder = replacement; - Assert.AreSame(replacement, factory.ViewFolder); - Assert.AreNotSame(existing, factory.ViewFolder); + factory.Engine.ViewFolder = replacement; + + Assert.AreSame(replacement, factory.Engine.ViewFolder); + Assert.AreNotSame(existing, factory.Engine.ViewFolder); } [Test] public void CreatingViewEngineWithSimpleContainer() { var settings = new SparkSettings().AddNamespace("System.Web.Mvc.Html"); - var container = SparkEngineStarter.CreateContainer(settings); - var viewFactory = (SparkViewFactory)container.GetService(); - var viewEngine = container.GetService(); - var viewFolder = container.GetService(); - var descriptorBuilder = container.GetService(); - var cacheServiceProvider = container.GetService(); - var viewActivatorFactory = container.GetService(); + var sp = SetupServiceProvider(settings); + + var viewFactory = sp.GetService(); + var viewEngine = sp.GetService(); + var viewFolder = sp.GetService(); + var descriptorBuilder = sp.GetService(); + var cacheServiceProvider = sp.GetService(); + var viewActivatorFactory = sp.GetService(); Assert.AreSame(settings, viewFactory.Settings); Assert.AreSame(settings, viewEngine.Settings); Assert.AreSame(viewEngine, viewFactory.Engine); Assert.AreSame(viewFolder, viewEngine.ViewFolder); - Assert.AreSame(viewFolder, viewFactory.ViewFolder); + Assert.AreSame(viewFolder, viewFactory.Engine.ViewFolder); Assert.AreSame(descriptorBuilder, viewFactory.DescriptorBuilder); Assert.AreSame(cacheServiceProvider, viewFactory.CacheServiceProvider); Assert.AreSame(viewActivatorFactory, viewFactory.ViewActivatorFactory); diff --git a/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs b/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs index cb6c6749..8ab0e37e 100644 --- a/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs +++ b/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs @@ -2,43 +2,27 @@ using System.IO; using System.Linq; using System.Web.Mvc; -using Spark.Compiler; -using Spark.Compiler.NodeVisitors; +using Spark.FileSystem; using Spark.Parser; using Spark.Parser.Syntax; using Spark.Web.Mvc.Descriptors; namespace Spark.Web.Mvc { - public class DefaultDescriptorBuilder : IDescriptorBuilder, ISparkServiceInitialize + public class DefaultDescriptorBuilder : IDescriptorBuilder { - private ISparkViewEngine _engine; + private IViewFolder _viewFolder; - public DefaultDescriptorBuilder() - : this((string)null) + public DefaultDescriptorBuilder(ISparkSettings settings, IViewFolder viewFolder) { - } - - public DefaultDescriptorBuilder(string _prefix) - { - Filters = new List + this.Filters = new List { new AreaDescriptorFilter() }; - _grammar = new UseMasterGrammar(_prefix); - } - public DefaultDescriptorBuilder(ISparkViewEngine engine) - : this() - { - _engine = engine; - _grammar = new UseMasterGrammar(_engine.Settings.Prefix); - } + this._grammar = new UseMasterGrammar(settings.Prefix); - public virtual void Initialize(ISparkServiceContainer container) - { - _engine = container.GetService(); - _grammar = new UseMasterGrammar(_engine.Settings.Prefix); + this._viewFolder = viewFolder; } public IList Filters { get; set; } @@ -153,12 +137,12 @@ public UseMasterGrammar(string _prefix) } private UseMasterGrammar _grammar; - public ParseAction ParseUseMaster { get { return _grammar.ParseUseMaster; } } + public ParseAction ParseUseMaster => _grammar.ParseUseMaster; public string TrailingUseMasterName(SparkViewDescriptor descriptor) { var lastTemplate = descriptor.Templates.Last(); - var sourceContext = AbstractSyntaxProvider.CreateSourceContext(lastTemplate, _engine.ViewFolder); + var sourceContext = AbstractSyntaxProvider.CreateSourceContext(lastTemplate, _viewFolder); if (sourceContext == null) { return null; @@ -173,7 +157,7 @@ private bool LocatePotentialTemplate( ICollection descriptorTemplates, ICollection searchedLocations) { - var template = potentialTemplates.FirstOrDefault(t => _engine.ViewFolder.HasView(t)); + var template = potentialTemplates.FirstOrDefault(t => _viewFolder.HasView(t)); if (template != null) { descriptorTemplates.Add(template); diff --git a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs b/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs index d4e08f3c..d8a10919 100644 --- a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs +++ b/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs @@ -4,11 +4,6 @@ namespace Spark.Web.Mvc.Descriptors { public static class DescriptorFilterExtensions { - public static void AddFilter(this ISparkServiceContainer target, IDescriptorFilter filter) - { - target.GetService().AddFilter(filter); - } - public static void AddFilter(this SparkViewFactory target, IDescriptorFilter filter) { target.DescriptorBuilder.AddFilter(filter); @@ -17,7 +12,7 @@ public static void AddFilter(this SparkViewFactory target, IDescriptorFilter fil public static void AddFilter(this IDescriptorBuilder target, IDescriptorFilter filter) { if (!(target is DefaultDescriptorBuilder)) - throw new InvalidCastException("IDescriptorFilters may only be added to DefaultDescriptorBuilder"); + throw new InvalidCastException($"IDescriptorFilters may only be added to {nameof(DefaultDescriptorBuilder)}"); ((DefaultDescriptorBuilder) target).AddFilter(filter); } diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..fcbd8052 --- /dev/null +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,73 @@ +using System; +using System.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Spark.Bindings; +using Spark.Compiler; +using Spark.Compiler.CodeDom; +using Spark.Compiler.Roslyn; +using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; + +namespace Spark.Web.Mvc.Extensions +{ + public static class ServiceCollectionExtensions + { + internal static string MissingSparkSettingsConfigurationErrorExceptionMessage + = "Spark setting not configured. Missing spark section app configuration or no ISparkSetting instance registered in IoC container."; + + /// + /// Registers spark dependencies in the service collection. + /// + /// + /// + /// + public static IServiceCollection AddSpark(this IServiceCollection services, ISparkSettings settings = null) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (settings == null) + { + settings = (ISparkSettings)ConfigurationManager.GetSection("spark"); + + if (settings == null) + { + throw new ConfigurationErrorsException(MissingSparkSettingsConfigurationErrorExceptionMessage); + } + } + + // Roslyn + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services + .AddSingleton(settings) + .AddSingleton(settings) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(settings.CreateDefaultViewFolder()) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services.AddSingleton(c => null); + + services + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + return services; + } + } +} diff --git a/src/Spark.Web.Mvc/Install/PrecompileInstaller.cs b/src/Spark.Web.Mvc/Install/PrecompileInstaller.cs index 69d80903..819e6f6a 100644 --- a/src/Spark.Web.Mvc/Install/PrecompileInstaller.cs +++ b/src/Spark.Web.Mvc/Install/PrecompileInstaller.cs @@ -19,11 +19,14 @@ using System.Configuration.Install; using System.IO; using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Spark.FileSystem; +using Spark.Web.Mvc.Extensions; namespace Spark.Web.Mvc.Install { [RunInstaller(true)] + [Obsolete("Is Spark MVC ever 'installed'?")] public partial class PrecompileInstaller : Installer { public PrecompileInstaller() @@ -68,13 +71,15 @@ public override void Install(IDictionary stateSaver) settings = (ISparkSettings) config.GetSection("spark"); } - // Finally create an engine with the settings from the web.config - var factory = new SparkViewFactory(settings) - { - ViewFolder = new FileSystemViewFolder(viewsLocation) - }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder(viewsLocation)) + .BuildServiceProvider(); - // And generate all of the known view/master templates into the target assembly + // Create an engine with the settings from the web.config + var factory = sp.GetService(); + + // And generate all the known view/master templates into the target assembly var batch = new SparkBatchDescriptor(targetPath); // create entries for controller attributes in the parent installer's assembly diff --git a/src/Spark.Web.Mvc/LanguageKit.cs b/src/Spark.Web.Mvc/LanguageKit.cs index caa2eee6..5f7f2086 100644 --- a/src/Spark.Web.Mvc/LanguageKit.cs +++ b/src/Spark.Web.Mvc/LanguageKit.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Web.Mvc; using Spark.FileSystem; using Spark.Web.Mvc.Descriptors; @@ -11,16 +10,11 @@ namespace Spark.Web.Mvc { public static class LanguageKit { - public static void Install(ISparkServiceContainer services, Func selector) - { - services.AddFilter(new Filter(selector)); - services.GetService().ViewFolder = new Folder(services.GetService()); - } - public static void Install(SparkViewFactory factory, Func selector) { factory.AddFilter(new Filter(selector)); - factory.ViewFolder = new Folder(factory.ViewFolder); + + factory.Engine.ViewFolder = new Folder(factory.Engine.ViewFolder); } public static void Install(IEnumerable engines, Func selector) diff --git a/src/Spark.Web.Mvc/Spark.Web.Mvc.csproj b/src/Spark.Web.Mvc/Spark.Web.Mvc.csproj index b5e59c73..c83d9dee 100644 --- a/src/Spark.Web.Mvc/Spark.Web.Mvc.csproj +++ b/src/Spark.Web.Mvc/Spark.Web.Mvc.csproj @@ -1,4 +1,4 @@ - + Library net48 @@ -55,5 +55,6 @@ + \ No newline at end of file diff --git a/src/Spark.Web.Mvc/SparkEngineStarter.cs b/src/Spark.Web.Mvc/SparkEngineStarter.cs deleted file mode 100644 index a5ac1171..00000000 --- a/src/Spark.Web.Mvc/SparkEngineStarter.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; - -namespace Spark.Web.Mvc -{ - public static class SparkEngineStarter - { - /// - /// Adds Asp.Net Mvc specific IViewEngine implementation. - /// - /// An instance of the spark service container to modify - public static void ConfigureContainer(ISparkServiceContainer container) - { - container.SetServiceBuilder(c => new SparkViewFactory(c.GetService())); - container.SetServiceBuilder(c => new DefaultDescriptorBuilder()); - container.SetServiceBuilder(c => new DefaultCacheServiceProvider()); - } - - /// - /// Create a CSharp enabled Spark service container - /// - /// A configured service container. Additional service builders may - /// me added. - public static ISparkServiceContainer CreateContainer() - { - var container = new SparkServiceContainer(); - ConfigureContainer(container); - return container; - } - - /// - /// Create a CSharp enabled service container with explicit spark settings. - /// - /// Typically an instance of SparkSettings object - /// A configured service container. Additional service builders may - /// me added. May be passed to RegisterViewEngine. - public static ISparkServiceContainer CreateContainer(ISparkSettings settings) - { - var container = new SparkServiceContainer(settings); - ConfigureContainer(container); - return container; - } - - /// - /// Creates a spark IViewEngine with CSharp as the default language. - /// Settings come from config or are defaulted. - /// - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine() - { - return CreateContainer().GetService(); - } - - /// - /// Creates a spark IViewEngine with CSharp as the default language. - /// - /// Typically an instance of SparkSettings object - /// An IViewEngine interface of the SparkViewFactory - public static IViewEngine CreateViewEngine(ISparkSettings settings) - { - return CreateContainer(settings).GetService(); - } - - - /// - /// Installs the Spark view engine. Settings come from config or are defaulted. - /// - public static void RegisterViewEngine() - { - ViewEngines.Engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark view engine. Settings passed in. - /// - public static void RegisterViewEngine(ISparkSettings settings) - { - ViewEngines.Engines.Add(CreateViewEngine(settings)); - } - - /// - /// Installs the Spark view engine. Container passed in. - /// - public static void RegisterViewEngine(ISparkServiceContainer container) - { - ViewEngines.Engines.Add(container.GetService()); - } - - /// - /// Installs the Spark view engine. Settings come from config or are defaulted. - /// - /// Typically in the ViewEngines.Engines collection - public static void RegisterViewEngine(ICollection engines) - { - engines.Add(CreateViewEngine()); - } - - /// - /// Installs the Spark view engine. Settings passed in. - /// - /// Typically in the ViewEngines.Engines collection - /// Typically an instance of SparkSettings object - public static void RegisterViewEngine(ICollection engines, ISparkSettings settings) - { - engines.Add(CreateViewEngine(settings)); - } - - /// - /// Install the view engine from the container. Typical usage is to call CreateContainer, - /// provide additinal service builder functors to override certain classes, then call this - /// method. - /// - /// Typically the ViewEngines.Engines collection - /// A service container, often created with CreateContainer - public static void RegisterViewEngine(ICollection engines, ISparkServiceContainer container) - { - engines.Add(container.GetService()); - } - } -} diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 1ac07f1d..700d5911 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -14,11 +14,9 @@ // using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using System.Web.Mvc; using System.Web.Routing; using Spark.Compiler; @@ -27,92 +25,40 @@ namespace Spark.Web.Mvc { - public class SparkViewFactory : IViewEngine, IViewFolderContainer, ISparkServiceInitialize + public class SparkViewFactory : IViewEngine, IViewFolderContainer { - private ISparkViewEngine _engine; - private IDescriptorBuilder _descriptorBuilder; - private ICacheServiceProvider _cacheServiceProvider; - - - public SparkViewFactory() - : this(null) - { - } - - public SparkViewFactory(ISparkSettings settings) - { - Settings = settings ?? (ISparkSettings)ConfigurationManager.GetSection("spark") ?? new SparkSettings(); - } - - - public virtual void Initialize(ISparkServiceContainer container) - { - Settings = container.GetService(); - Engine = container.GetService(); - DescriptorBuilder = container.GetService(); - CacheServiceProvider = container.GetService(); - } - - public ISparkSettings Settings { get; set; } - - public ISparkViewEngine Engine + public ISparkSettings Settings { get; protected set; } + public ISparkViewEngine Engine { get; protected set; } + public IDescriptorBuilder DescriptorBuilder { get; protected set; } + public IResourcePathManager ResourcePathManager { get; protected set; } + public ICacheServiceProvider CacheServiceProvider { get; protected set; } + + private readonly Dictionary _cache; + private readonly ViewEngineResult _cacheMissResult; + + public SparkViewFactory(ISparkSettings settings, + ISparkViewEngine viewEngine, + IDescriptorBuilder descriptorBuilder, + IResourcePathManager resourcePathManager, + ICacheServiceProvider cacheServiceProvider) { - get - { - if (_engine == null) - SetEngine(new SparkViewEngine(Settings)); + Settings = settings; - return _engine; - } - set + if (string.IsNullOrEmpty(settings.PageBaseType)) { - SetEngine(value); + settings.PageBaseType = typeof(SparkView).FullName; } - } - public void SetEngine(ISparkViewEngine engine) - { - _descriptorBuilder = null; - _engine = engine; - if (_engine != null) - { - _engine.DefaultPageBaseType = typeof(SparkView).FullName; - } - } + Engine = viewEngine; + DescriptorBuilder = descriptorBuilder; + ResourcePathManager = resourcePathManager; + CacheServiceProvider = cacheServiceProvider; - public IViewActivatorFactory ViewActivatorFactory - { - get { return Engine.ViewActivatorFactory; } - set { Engine.ViewActivatorFactory = value; } + _cache = new Dictionary(); + _cacheMissResult = new ViewEngineResult(Array.Empty()); } - public IViewFolder ViewFolder - { - get { return Engine.ViewFolder; } - set { Engine.ViewFolder = value; } - } - - public IDescriptorBuilder DescriptorBuilder - { - get - { - return _descriptorBuilder ?? - Interlocked.CompareExchange(ref _descriptorBuilder, new DefaultDescriptorBuilder(Engine), null) ?? - _descriptorBuilder; - } - set { _descriptorBuilder = value; } - } - - public ICacheServiceProvider CacheServiceProvider - { - get - { - return _cacheServiceProvider ?? - Interlocked.CompareExchange(ref _cacheServiceProvider, new DefaultCacheServiceProvider(), null) ?? - _cacheServiceProvider; - } - set { _cacheServiceProvider = value; } - } + public IViewActivatorFactory ViewActivatorFactory => Engine.ViewActivatorFactory; public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName) { @@ -136,16 +82,12 @@ public virtual ViewEngineResult FindPartialView(ControllerContext controllerCont public virtual void ReleaseView(ControllerContext controllerContext, IView view) { - var sparkView = view as ISparkView; - if (sparkView != null) + if (view is ISparkView sparkView) + { Engine.ReleaseInstance(sparkView); + } } - private readonly Dictionary _cache = - new Dictionary(); - - private readonly ViewEngineResult _cacheMissResult = new ViewEngineResult(new string[0]); - private ViewEngineResult FindViewInternal(ControllerContext controllerContext, string viewName, string masterName, bool findDefaultMaster, bool useCache) { var searchedLocations = new List(); @@ -198,16 +140,16 @@ private void SetCacheValue(BuildDescriptorParams descriptorParams, ISparkViewEnt lock (_cache) _cache[descriptorParams] = entry; } - private ViewEngineResult BuildResult(RequestContext requestContext, ISparkViewEntry entry) { var view = (IView)entry.CreateInstance(); - if (view is SparkView) + + if (view is SparkView sparkView) { - var sparkView = (SparkView)view; - sparkView.ResourcePathManager = Engine.ResourcePathManager; + sparkView.ResourcePathManager = ResourcePathManager; sparkView.CacheService = CacheServiceProvider.GetCacheService(requestContext); } + return new ViewEngineResult(view, this); } @@ -233,17 +175,23 @@ public SparkViewDescriptor CreateDescriptor( searchedLocations); } - public SparkViewDescriptor CreateDescriptor(string targetNamespace, string controllerName, string viewName, - string masterName, bool findDefaultMaster) + public SparkViewDescriptor CreateDescriptor( + string targetNamespace, + string controllerName, + string viewName, + string masterName, + bool findDefaultMaster) { var searchedLocations = new List(); + var descriptor = DescriptorBuilder.BuildDescriptor( new BuildDescriptorParams( targetNamespace /*areaName*/, controllerName, viewName, masterName, - findDefaultMaster, null), + findDefaultMaster, + null), searchedLocations); if (descriptor == null) @@ -291,7 +239,7 @@ public IList CreateDescriptors(SparkBatchEntry entry) { if (include.EndsWith("*")) { - foreach (var fileName in ViewFolder.ListViews(controllerName)) + foreach (var fileName in Engine.ViewFolder.ListViews(controllerName)) { if (!string.Equals(Path.GetExtension(fileName), ".spark", StringComparison.InvariantCultureIgnoreCase)) { @@ -332,23 +280,25 @@ public IList CreateDescriptors(SparkBatchEntry entry) { if (entry.LayoutNames.Count == 0) { - descriptors.Add(CreateDescriptor( - entry.ControllerType.Namespace, - controllerName, - viewName, - null /*masterName*/, - true)); + descriptors.Add( + CreateDescriptor( + entry.ControllerType.Namespace, + controllerName, + viewName, + null /*masterName*/, + true)); } else { foreach (var masterName in entry.LayoutNames) { - descriptors.Add(CreateDescriptor( - entry.ControllerType.Namespace, - controllerName, - viewName, - string.Join(" ", masterName.ToArray()), - false)); + descriptors.Add( + CreateDescriptor( + entry.ControllerType.Namespace, + controllerName, + viewName, + string.Join(" ", masterName.ToArray()), + false)); } } } @@ -402,17 +352,6 @@ void IViewEngine.ReleaseView(ControllerContext controllerContext, IView view) #endregion - - #region ISparkServiceInitialize Members - - void ISparkServiceInitialize.Initialize(ISparkServiceContainer container) - { - Initialize(container); - } - - #endregion - - #region IViewFolderContainer Members IViewFolder IViewFolderContainer.ViewFolder diff --git a/src/Spark.Web.Tests/BatchCompilationTester.cs b/src/Spark.Web.Tests/BatchCompilationTester.cs index 4ed80c2e..ad18ffba 100644 --- a/src/Spark.Web.Tests/BatchCompilationTester.cs +++ b/src/Spark.Web.Tests/BatchCompilationTester.cs @@ -21,6 +21,8 @@ using Spark.FileSystem; using Spark.Tests.Precompiled; using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Spark.Extensions; using Spark.Tests; namespace Spark @@ -36,14 +38,17 @@ public void Init() var settings = new SparkSettings() .SetPageBaseType(typeof(Tests.Stubs.StubSparkView)); - engine = new SparkViewEngine(settings) - { - ViewFolder = new InMemoryViewFolder - { - {Path.Combine("Home","Index.spark"), "

Hello world

"}, - {Path.Combine("Home","List.spark"), "
  1. one
  2. two
"} - } - }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton( + new InMemoryViewFolder + { + { Path.Combine("Home", "Index.spark"), "

Hello world

" }, + { Path.Combine("Home", "List.spark"), "
  1. one
  2. two
" } + }) + .BuildServiceProvider(); + + engine = (SparkViewEngine)sp.GetService(); } [Test] @@ -57,7 +62,11 @@ public void CompileMultipleDescriptors() var assembly = engine.BatchCompilation(descriptors); - var types = assembly.GetTypes(); + var types = + assembly + .GetTypes() + .Where(x => x.BaseType == typeof(Tests.Stubs.StubSparkView)); + Assert.AreEqual(2, types.Count()); var entry0 = engine.GetEntry(descriptors[0]); @@ -84,7 +93,12 @@ public void DescriptorsAreEqual() var assembly = engine.BatchCompilation(new[] { descriptor }); - var types = assembly.GetTypes(); + var types = + assembly + .GetTypes() + .Where(x => x.BaseType == typeof(Tests.Stubs.StubSparkView)) + .ToArray(); + Assert.AreEqual(1, types.Count()); var attribs = types[0].GetCustomAttributes(typeof(SparkViewAttribute), false); @@ -103,7 +117,12 @@ public void DescriptorsWithNoTargetNamespace() var assembly = engine.BatchCompilation(new[] { descriptor }); - var types = assembly.GetTypes(); + var types = + assembly + .GetTypes() + .Where(x => x.BaseType == typeof(Tests.Stubs.StubSparkView)) + .ToArray(); + Assert.AreEqual(1, types.Count()); var attribs = types[0].GetCustomAttributes(typeof(SparkViewAttribute), false); diff --git a/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs b/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs index 688ea456..be896215 100644 --- a/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs +++ b/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs @@ -1,7 +1,9 @@ using System; using System.IO; using System.Text; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests.Stubs; @@ -16,16 +18,20 @@ public class BindingExecutionTester [SetUp] public void Init() { - this._viewFolder = new InMemoryViewFolder(); + var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton() + .BuildServiceProvider(); + + this._viewFolder = (InMemoryViewFolder)sp.GetService(); + + var viewEngine = sp.GetService(); this._factory = new StubViewFactory { - Engine = new SparkViewEngine( - new SparkSettings() - .SetPageBaseType(typeof(StubSparkView))) - { - ViewFolder = this._viewFolder - } + Engine = viewEngine }; } diff --git a/src/Spark.Web.Tests/Caching/CacheElementTester.cs b/src/Spark.Web.Tests/Caching/CacheElementTester.cs index 93e29480..269f49c9 100644 --- a/src/Spark.Web.Tests/Caching/CacheElementTester.cs +++ b/src/Spark.Web.Tests/Caching/CacheElementTester.cs @@ -23,7 +23,9 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests.Stubs; @@ -39,16 +41,20 @@ public class CacheElementTester [SetUp] public void Init() { - this._viewFolder = new InMemoryViewFolder(); - this._cacheService = new StubCacheService(); + var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + + this._viewFolder = (InMemoryViewFolder) sp.GetService(); + this._cacheService = (StubCacheService) sp.GetService(); + this._factory = new StubViewFactory { - Engine = new SparkViewEngine( - new SparkSettings() - .SetPageBaseType(typeof(StubSparkView))) - { - ViewFolder = this._viewFolder - }, + Engine = (SparkViewEngine)sp.GetService(), CacheService = this._cacheService }; } diff --git a/src/Spark.Web.Tests/ClientsideCompilerTester.cs b/src/Spark.Web.Tests/ClientsideCompilerTester.cs index e85c0bb2..c5ad7641 100644 --- a/src/Spark.Web.Tests/ClientsideCompilerTester.cs +++ b/src/Spark.Web.Tests/ClientsideCompilerTester.cs @@ -14,7 +14,9 @@ // using System.IO; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; namespace Spark @@ -22,14 +24,30 @@ namespace Spark [TestFixture] public class ClientsideCompilerTester { + private static ServiceProvider CreateServiceProvider(ISparkSettings settings, IViewFolder viewFolder) + { + return new ServiceCollection() + .AddSpark(settings) + .AddSingleton(viewFolder) + .BuildServiceProvider(); + } + [Test] public void GenerateSimpleTemplate() { + var settings = new SparkSettings(); + + var viewFolder = new FileSystemViewFolder("Spark.Tests.Views"); + + var sp = CreateServiceProvider(settings, viewFolder); + + var engine = sp.GetService(); + var descriptor = new SparkViewDescriptor() .SetLanguage(LanguageType.Javascript) .AddTemplate(Path.Combine("Clientside","simple.spark")); - var engine = new SparkViewEngine { ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") }; + var entry = engine.CreateEntry(descriptor); Assert.IsNotNull(entry.SourceCode); @@ -39,11 +57,18 @@ public void GenerateSimpleTemplate() [Test] public void AnonymousTypeBecomesHashLikeObject() { + var settings = new SparkSettings(); + + var viewFolder = new FileSystemViewFolder("Spark.Tests.Views"); + + var sp = CreateServiceProvider(settings, viewFolder); + + var engine = sp.GetService(); + var descriptor = new SparkViewDescriptor() .SetLanguage(LanguageType.Javascript) .AddTemplate(Path.Combine("Clientside","AnonymousTypeBecomesHashLikeObject.spark")); - var engine = new SparkViewEngine { ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") }; var entry = engine.CreateEntry(descriptor); Assert.IsNotNull(entry.SourceCode); diff --git a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs index 2a8afc86..61b5dbbe 100644 --- a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs @@ -15,7 +15,9 @@ using System.Collections.Generic; using NUnit.Framework; +using Spark.Compiler.CodeDom; using Spark.Compiler.CSharp; +using Spark.Compiler.Roslyn; using Spark.Tests; using Spark.Tests.Models; using Spark.Tests.Stubs; @@ -25,10 +27,12 @@ namespace Spark.Compiler [TestFixture] public class CSharpViewCompilerTester { + private IBatchCompiler batchCompiler; [SetUp] public void Init() { + this.batchCompiler = new RoslynBatchCompiler(); } private static void DoCompileView(ViewCompiler compiler, IList chunks) @@ -39,7 +43,7 @@ private static void DoCompileView(ViewCompiler compiler, IList chunks) [Test] public void MakeAndCompile() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new[] { new SendLiteralChunk { Text = "hello world" } }); @@ -54,7 +58,7 @@ public void MakeAndCompile() public void UnsafeLiteralCharacters() { var text = "hello\t\r\n\"world"; - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new[] { new SendLiteralChunk { Text = text } }); Assert.That(compiler.SourceCode.Contains("Write(\"hello\\t\\r\\n\\\"world\")")); @@ -68,7 +72,7 @@ public void UnsafeLiteralCharacters() [Test] public void SimpleOutput() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); string contents = instance.RenderView(); @@ -79,7 +83,7 @@ public void SimpleOutput() [Test] public void LocalVariableDecl() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new Chunk[] { new LocalVariableChunk { Name = "i", Value = "5" }, @@ -94,7 +98,7 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new[]{3,4,5}"}, @@ -120,7 +124,7 @@ public void ForEachLoop() [Test] public void GlobalVariables() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; DoCompileView(compiler, new Chunk[] { new SendExpressionChunk{Code="title"}, @@ -139,11 +143,12 @@ public void GlobalVariables() [Test] public void TargetNamespace() { - var compiler = new CSharpViewCompiler - { - BaseClass = "Spark.SparkViewBase", - Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } - }; + var compiler = new CSharpViewCompiler(this.batchCompiler) + { + BaseClass = "Spark.SparkViewBase", + Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } + }; + DoCompileView(compiler, new Chunk[] { new SendLiteralChunk { Text = "Hello" } }); var instance = compiler.CreateInstance(); @@ -154,7 +159,7 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; Assert.That( () => @@ -170,7 +175,7 @@ public void ProvideFullException() [Test] public void IfTrueCondition() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -193,7 +198,7 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -216,7 +221,7 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; var falseChunks = new Chunk[] { new SendLiteralChunk { Text = "wasfalse" } }; @@ -241,7 +246,7 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -264,7 +269,7 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -287,7 +292,7 @@ public void UnlessFalseCondition() [Test] public void LenientSilentNullDoesNotCauseWarningCS0168() { - var compiler = new CSharpViewCompiler() + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Lenient @@ -307,7 +312,7 @@ public void LenientSilentNullDoesNotCauseWarningCS0168() [Test] public void LenientOutputNullDoesNotCauseWarningCS0168() { - var compiler = new CSharpViewCompiler() + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Lenient @@ -326,7 +331,7 @@ public void LenientOutputNullDoesNotCauseWarningCS0168() [Test] public void StrictNullUsesException() { - var compiler = new CSharpViewCompiler() + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict @@ -348,11 +353,12 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var compiler = new CSharpViewCompiler() - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var compiler = new CSharpViewCompiler(this.batchCompiler) + { + BaseClass = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + DoCompileView(compiler, new Chunk[] { new PageBaseTypeChunk { BaseClass="Spark.Tests.Stubs.StubSparkView2"}, @@ -368,11 +374,11 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var compiler = new CSharpViewCompiler() - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var compiler = new CSharpViewCompiler(this.batchCompiler) + { + BaseClass = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; DoCompileView( compiler, @@ -392,11 +398,12 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var compiler = new CSharpViewCompiler() - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var compiler = new CSharpViewCompiler(this.batchCompiler) + { + BaseClass = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + DoCompileView(compiler, new Chunk[] { new PageBaseTypeChunk {BaseClass = "Spark.Tests.Stubs.StubSparkView3"}, @@ -413,7 +420,7 @@ public void PageBaseTypeWorksWithGenericParametersIncluded() [Test] public void Markdown() { - var compiler = new CSharpViewCompiler { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; var innerChunks = new Chunk[] { new SendLiteralChunk { Text = "*test*" } }; diff --git a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs index f3b39d41..59b31871 100644 --- a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs +++ b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs @@ -17,6 +17,10 @@ using Spark.FileSystem; using Spark.Tests.Stubs; using System.IO; +using Spark.Bindings; +using Spark.Compiler.Roslyn; +using Spark.Parser; +using Spark.Parser.Syntax; using Spark.Tests; namespace Spark.Compiler @@ -33,13 +37,25 @@ public void Init() { var settings = new SparkSettings() .SetPageBaseType(typeof(StubSparkView)); - var container = new SparkServiceContainer(settings); - _viewFolder = new InMemoryViewFolder(); + var partialProvider = new DefaultPartialProvider(); - container.SetServiceBuilder(c => _viewFolder); + _viewFolder = new InMemoryViewFolder(); - _engine = container.GetService(); + var batchCompiler = new RoslynBatchCompiler(); + + _engine = new SparkViewEngine( + settings, + new DefaultSyntaxProvider(settings), + new DefaultViewActivator(), + new DefaultLanguageFactory(batchCompiler), + new CompiledViewHolder(), + _viewFolder, + batchCompiler, + partialProvider, + new DefaultPartialReferenceProvider(partialProvider), + new DefaultBindingProvider(), + null); } private string RenderView(SparkViewDescriptor descriptor) diff --git a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs index 7b567a00..3d87f227 100644 --- a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs @@ -16,6 +16,8 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using Spark.Compiler.CodeDom; +using Spark.Compiler.Roslyn; using Spark.Compiler.VisualBasic; using Spark.Tests; using Spark.Tests.Models; @@ -26,10 +28,13 @@ namespace Spark.Compiler [TestFixture] public class VisualBasicViewCompilerTester { + private IBatchCompiler batchCompiler; [SetUp] public void Init() { + this.batchCompiler = + new RoslynBatchCompiler(); } private static void DoCompileView(ViewCompiler compiler, IList chunks) @@ -53,7 +58,7 @@ public void MakeAndCompile() [Test] public void StronglyTypedBase() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.Tests.Stubs.StubSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView" }; DoCompileView(compiler, new Chunk[] { @@ -82,9 +87,9 @@ public void UnsafeLiteralCharacters() Assert.That(contents, Is.EqualTo(text)); } - private static VisualBasicViewCompiler CreateCompiler() + private VisualBasicViewCompiler CreateCompiler() { - return new VisualBasicViewCompiler + return new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView", UseAssemblies = new[] { "Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" }, @@ -95,7 +100,7 @@ private static VisualBasicViewCompiler CreateCompiler() [Test] public void SimpleOutput() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); string contents = instance.RenderView(); @@ -142,7 +147,7 @@ public void RethrowNullBehavior() [Test] public void LocalVariableDecl() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; DoCompileView(compiler, new Chunk[] { new LocalVariableChunk { Name = "i", Value = "5" }, @@ -157,7 +162,7 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new Integer(){3,4,5}"}, @@ -183,7 +188,7 @@ public void ForEachLoop() [Test] public void ForEachAutoVariables() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new Integer(){3,4,5}"}, @@ -213,7 +218,7 @@ public void ForEachAutoVariables() [Test] public void GlobalVariables() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; DoCompileView(compiler, new Chunk[] { new SendExpressionChunk{Code="title"}, @@ -233,7 +238,7 @@ public void GlobalVariables() [Platform(Exclude = "Mono", Reason = "Problems with Mono-2.10+/Linux and the VB compiler prevent this from running.")] public void TargetNamespace() { - var compiler = new VisualBasicViewCompiler + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView", Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } @@ -248,19 +253,20 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + Assert.That(() => DoCompileView(compiler, new Chunk[] { new SendExpressionChunk {Code = "NoSuchVariable"} }), - Throws.TypeOf()); + Throws.TypeOf().Or.TypeOf()); } [Test] public void IfTrueCondition() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -280,7 +286,7 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -300,7 +306,7 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; var falseChunks = new Chunk[] { new SendLiteralChunk { Text = "wasfalse" } }; @@ -322,7 +328,7 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -342,7 +348,7 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var compiler = new VisualBasicViewCompiler { BaseClass = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -362,7 +368,7 @@ public void UnlessFalseCondition() [Test] public void StrictNullUsesException() { - var compiler = new VisualBasicViewCompiler() + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict @@ -382,7 +388,7 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var compiler = new VisualBasicViewCompiler() + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict @@ -401,7 +407,7 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var compiler = new VisualBasicViewCompiler() + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict @@ -421,7 +427,7 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var compiler = new VisualBasicViewCompiler() + var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict diff --git a/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs b/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs index a5b1eb03..0ae88148 100644 --- a/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs +++ b/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs @@ -16,7 +16,9 @@ using System.Configuration; using System.IO; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests; using Spark.Tests.Stubs; @@ -79,12 +81,17 @@ public void UseAssemblyAndNamespaceFromSettings() .AddAssembly("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") .SetPageBaseType(typeof(StubSparkView)); - var views = new InMemoryViewFolder + var viewFolder = new InMemoryViewFolder { { Path.Combine("home", "index.spark"), "
${ProcessStatus.Alive}
" } }; - var engine = new SparkViewEngine(settings) { ViewFolder = views }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(viewFolder) + .BuildServiceProvider(); + + var engine = (SparkViewEngine)sp.GetService(); var descriptor = new SparkViewDescriptor(); descriptor.Templates.Add(Path.Combine("home", "index.spark")); diff --git a/src/Spark.Web.Tests/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Tests/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..b5f996e1 --- /dev/null +++ b/src/Spark.Web.Tests/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,69 @@ +using System; +using System.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Spark.Bindings; +using Spark.Compiler; +using Spark.Compiler.Roslyn; +using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; + +namespace Spark.Extensions +{ + // TODO: File duplicated in System.Web.Mvc + public static class ServiceCollectionExtensions + { + internal static string MissingSparkSettingsConfigurationErrorExceptionMessage + = "Spark setting not configured. Missing spark section app configuration or no ISparkSetting instance registered in IoC container."; + + /// + /// Registers spark dependencies in the service collection. + /// + /// + /// + /// + public static IServiceCollection AddSpark(this IServiceCollection services, ISparkSettings settings = null) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (settings == null) + { + settings = (ISparkSettings)ConfigurationManager.GetSection("spark"); + + if (settings == null) + { + throw new ConfigurationErrorsException(MissingSparkSettingsConfigurationErrorExceptionMessage); + } + } + + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services + .AddSingleton(settings) + .AddSingleton(settings) + .AddSingleton() + + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(settings.CreateDefaultViewFolder()) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services + .AddSingleton(c => null); + + return services; + } + } +} diff --git a/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs b/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs index 4a9647ef..d93144dd 100644 --- a/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs +++ b/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs @@ -15,7 +15,9 @@ using System.IO; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.Tests; using Spark.Tests.Stubs; @@ -119,9 +121,19 @@ public void LastModifiedChanges() [Test] public void InMemoryViewFolderUsedByEngine() { - var folder = new InMemoryViewFolder(); - folder.Add(Path.Combine("home", "index.spark"), "

Hello world

"); - var engine = new SparkViewEngine(new SparkSettings().SetPageBaseType(typeof (StubSparkView))){ViewFolder = folder}; + var viewFolder = new InMemoryViewFolder + { + { Path.Combine("home", "index.spark"), "

Hello world

" } + }; + + var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(viewFolder) + .BuildServiceProvider(); + + var engine = sp.GetService(); var descriptor = new SparkViewDescriptor(); descriptor.Templates.Add(Path.Combine("home", "index.spark")); @@ -154,17 +166,26 @@ private static string RenderView(ISparkViewEngine engine, string path) [Test] public void UnicodeCharactersSurviveConversionToByteArrayAndBack() { - var folder = new InMemoryViewFolder(); - folder.Add(Path.Combine("Home", "fr.spark"), "Fran\u00E7ais"); - folder.Add(Path.Combine("Home", "ru.spark"), "\u0420\u0443\u0441\u0441\u043A\u0438\u0439"); - folder.Add(Path.Combine("Home", "ja.spark"), "\u65E5\u672C\u8A9E"); - - Assert.That(ReadToEnd(folder, Path.Combine("Home", "fr.spark")), Is.EqualTo("Français")); - Assert.That(ReadToEnd(folder, Path.Combine("Home", "ru.spark")), Is.EqualTo("Русский")); - Assert.That(ReadToEnd(folder, Path.Combine("Home", "ja.spark")), Is.EqualTo("日本語")); + var viewFolder = new InMemoryViewFolder + { + { Path.Combine("Home", "fr.spark"), "Fran\u00E7ais" }, + { Path.Combine("Home", "ru.spark"), "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" }, + { Path.Combine("Home", "ja.spark"), "\u65E5\u672C\u8A9E" } + }; + + Assert.That(ReadToEnd(viewFolder, Path.Combine("Home", "fr.spark")), Is.EqualTo("Français")); + Assert.That(ReadToEnd(viewFolder, Path.Combine("Home", "ru.spark")), Is.EqualTo("Русский")); + Assert.That(ReadToEnd(viewFolder, Path.Combine("Home", "ja.spark")), Is.EqualTo("日本語")); var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); - var engine = new SparkViewEngine(settings) { ViewFolder = folder }; + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(viewFolder) + .BuildServiceProvider(); + + var engine = sp.GetService(); + Assert.That(RenderView(engine, Path.Combine("Home", "fr.spark")), Is.EqualTo("Français")); Assert.That(RenderView(engine, Path.Combine("Home", "ru.spark")), Is.EqualTo("Русский")); Assert.That(RenderView(engine, Path.Combine("Home", "ja.spark")), Is.EqualTo("日本語")); diff --git a/src/Spark.Web.Tests/FileSystem/ViewFolderSettingsTester.cs b/src/Spark.Web.Tests/FileSystem/ViewFolderSettingsTester.cs index ad536625..ad661c86 100644 --- a/src/Spark.Web.Tests/FileSystem/ViewFolderSettingsTester.cs +++ b/src/Spark.Web.Tests/FileSystem/ViewFolderSettingsTester.cs @@ -25,9 +25,13 @@ public class ViewFolderSettingsTester [Test] public void ApplySettings() { - var settings = new SparkSettings() - .AddViewFolder(typeof(VirtualPathProviderViewFolder), new Dictionary { { "virtualBaseDir", "~/MoreViews/" } }); - var engine = new SparkViewEngine(settings); + var settings = + new SparkSettings() + .AddViewFolder( + typeof(VirtualPathProviderViewFolder), + new Dictionary { { "virtualBaseDir", "~/MoreViews/" } }); + + var engine = new SparkViewEngine(settings, null, null, null, null, null, null, null, null, null, null); var folder = engine.ViewFolder; @@ -44,8 +48,11 @@ public void ApplySettings() public void CustomViewFolder() { var settings = new SparkSettings() - .AddViewFolder(typeof(MyViewFolder), new Dictionary { { "foo", "quux" }, { "bar", "42" } }); - var engine = new SparkViewEngine(settings); + .AddViewFolder( + typeof(MyViewFolder), + new Dictionary { { "foo", "quux" }, { "bar", "42" } }); + + var engine = new SparkViewEngine(settings, null, null, null, null, null, null, null, null, null, null); var folder = engine.ViewFolder; Assert.IsAssignableFrom(typeof(CombinedViewFolder), folder); @@ -60,9 +67,12 @@ public void CustomViewFolder() public void AssemblyParameter() { var settings = new SparkSettings() - .AddViewFolder(typeof(EmbeddedViewFolder), new Dictionary { { "assembly", "Spark.Tests" }, { "resourcePath", "Spark.Tests.Views" } }); + .AddViewFolder( + typeof(EmbeddedViewFolder), + new Dictionary + { { "assembly", "Spark.Tests" }, { "resourcePath", "Spark.Tests.Views" } }); - var engine = new SparkViewEngine(settings); + var engine = new SparkViewEngine(settings, null, null, null, null, null, null, null, null, null, null); var folder = engine.ViewFolder; Assert.IsAssignableFrom(typeof(CombinedViewFolder), folder); @@ -76,11 +86,14 @@ public void AssemblyParameter() public void TypeFileSystemCreatesFileSystemViewFolder() { var settings = new SparkSettings() - .AddViewFolder(typeof(FileSystemViewFolder), new Dictionary - { - { "basePath", @"e:\no\such\path" } - }); - var engine = new SparkViewEngine(settings); + .AddViewFolder( + typeof(FileSystemViewFolder), + new Dictionary + { + { "basePath", @"e:\no\such\path" } + }); + + var engine = new SparkViewEngine(settings, null, null, null, null, null, null, null, null, null, null); var folder = engine.ViewFolder; Assert.IsAssignableFrom(typeof(CombinedViewFolder), folder); var combined = (CombinedViewFolder)folder; diff --git a/src/Spark.Web.Tests/ImportAndIncludeTester.cs b/src/Spark.Web.Tests/ImportAndIncludeTester.cs index 04875575..b4b6d0b6 100644 --- a/src/Spark.Web.Tests/ImportAndIncludeTester.cs +++ b/src/Spark.Web.Tests/ImportAndIncludeTester.cs @@ -14,8 +14,10 @@ // using System.IO; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Spark.Compiler; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests; using Spark.Tests.Stubs; @@ -29,10 +31,12 @@ private ISparkView CreateView(IViewFolder viewFolder, string template) { var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); - var engine = new SparkViewEngine(settings) - { - ViewFolder = viewFolder - }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(viewFolder) + .BuildServiceProvider(); + + var engine = (SparkViewEngine)sp.GetService(); return engine.CreateInstance(new SparkViewDescriptor().AddTemplate(template)); } diff --git a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs index 73f6f99e..2674bddb 100644 --- a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs +++ b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs @@ -15,7 +15,9 @@ using System.IO; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Parser.Markup; using Spark.Tests; @@ -42,13 +44,15 @@ public void Init(bool automaticEncoding) this._settings = new SparkSettings() .SetPageBaseType(typeof(StubSparkView)) .SetAutomaticEncoding(automaticEncoding); - var container = new SparkServiceContainer(this._settings); this._viewFolder = new InMemoryViewFolder(); - container.SetServiceBuilder(c => this._viewFolder); + var sp = new ServiceCollection() + .AddSpark(_settings) + .AddSingleton(_viewFolder) + .BuildServiceProvider(); - this._engine = container.GetService(); + _engine = sp.GetService(); } private string RenderView(SparkViewDescriptor descriptor) diff --git a/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs b/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs index 00bc0b4b..5adeec30 100644 --- a/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs +++ b/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs @@ -14,8 +14,10 @@ // using System.IO; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Spark.Compiler.NodeVisitors; +using Spark.Extensions; using Spark.FileSystem; using Spark.Parser.Syntax; using Spark.Tests; @@ -31,7 +33,11 @@ public class CSharpSyntaxProviderTester [Test] public void CanParseSimpleFile() { - var context = new VisitorContext { ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") }; + var context = new VisitorContext + { + ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") + }; + var result = this._syntax.GetChunks(context, Path.Combine("Home", "childview.spark")); Assert.IsNotNull(result); @@ -40,12 +46,14 @@ public void CanParseSimpleFile() [Test] public void UsingCSharpSyntaxInsideEngine() { - // engine takes base class and IViewFolder - var engine = new SparkViewEngine(new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView")) - { - SyntaxProvider = this._syntax, - ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") - }; + var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .BuildServiceProvider(); + + var engine = sp.GetService(); // describe and instantiate view var descriptor = new SparkViewDescriptor(); @@ -62,9 +70,15 @@ public void UsingCSharpSyntaxInsideEngine() [Test] public void StatementAndExpressionInCode() { - // engine takes base class and IViewFolder - var engine = new SparkViewEngine( - new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView")) { SyntaxProvider = this._syntax, ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") }; + var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .AddSingleton(this._syntax) + .BuildServiceProvider(); + + var engine = sp.GetService(); // describe and instantiate view var descriptor = new SparkViewDescriptor(); diff --git a/src/Spark.Web.Tests/PartialProviderTester.cs b/src/Spark.Web.Tests/PartialProviderTester.cs index 0374715c..6ac97664 100644 --- a/src/Spark.Web.Tests/PartialProviderTester.cs +++ b/src/Spark.Web.Tests/PartialProviderTester.cs @@ -1,13 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; +using Spark.Extensions; namespace Spark { [TestFixture] public class PartialProviderTester { - private SparkViewEngine _engine; private IPartialProvider _partialProvider; + private IPartialReferenceProvider _partialReferenceProvider; private string _viewPath; private string[] _result; @@ -17,10 +19,14 @@ public void Init() this._viewPath = "fake/path"; this._partialProvider = MockRepository.GenerateMock(); - this._engine = new SparkViewEngine(new SparkSettings()) - { - PartialProvider = this._partialProvider - }; + + var sp = new ServiceCollection() + .AddSpark(new SparkSettings()) + .AddSingleton(this._partialProvider) + .BuildServiceProvider(); + + _partialReferenceProvider = sp.GetService(); + this._result = new[] {"output"}; } @@ -28,26 +34,11 @@ public void Init() public void DefaultPartialReferenceProviderWrapsPartialProvider() { this._partialProvider.Expect(x => x.GetPaths(this._viewPath)).Return(this._result); - var output = this._engine.PartialReferenceProvider.GetPaths(this._viewPath, true); - this._partialProvider.VerifyAllExpectations(); - Assert.AreEqual(this._result, output); - } - - [Test] - public void SettingNewPartialProviderPropogatesToDefaultPartialProvider() - { - var differentPartialProvider = MockRepository.GenerateMock(); - this._engine.PartialProvider = differentPartialProvider; - - //should not call the original - this._partialProvider.Expect(x => x.GetPaths(this._viewPath)).Repeat.Never(); - - //should call the newly provided instance - differentPartialProvider.Expect(x => x.GetPaths(this._viewPath)).Return(this._result); - - var output = this._engine.PartialReferenceProvider.GetPaths(this._viewPath, true); + + var output = _partialReferenceProvider.GetPaths(this._viewPath, true); + this._partialProvider.VerifyAllExpectations(); - differentPartialProvider.VerifyAllExpectations(); + Assert.AreEqual(this._result, output); } } diff --git a/src/Spark.Web.Tests/PrefixSupportTester.cs b/src/Spark.Web.Tests/PrefixSupportTester.cs index 24bd9379..12c6cadc 100644 --- a/src/Spark.Web.Tests/PrefixSupportTester.cs +++ b/src/Spark.Web.Tests/PrefixSupportTester.cs @@ -14,7 +14,9 @@ // using System.IO; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests.Stubs; @@ -32,10 +34,12 @@ public void Init() _settings = new SparkSettings() .SetPageBaseType(typeof(StubSparkView)); - _engine = new SparkViewEngine(_settings) - { - ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") - }; + var sp = new ServiceCollection() + .AddSpark(_settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .BuildServiceProvider(); + + _engine = (SparkViewEngine)sp.GetService(); } static void ContainsInOrder(string content, params string[] values) @@ -56,10 +60,12 @@ public void PrefixFromSettings() .SetPageBaseType(typeof(StubSparkView)) .SetPrefix("s"); - var engine = new SparkViewEngine(settings) - { - ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") - }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .BuildServiceProvider(); + + var engine = (SparkViewEngine)sp.GetService(); var view = (StubSparkView)engine.CreateInstance(new SparkViewDescriptor().AddTemplate(Path.Combine("Prefix", "prefix-from-settings.spark"))); view.ViewData["Names"] = new[] { "alpha", "beta", "gamma" }; @@ -177,13 +183,15 @@ public void SegmentAndRenderPrefixes() public void SectionAsSegmentAndRenderPrefixes() { var settings = new SparkSettings() - .SetPageBaseType(typeof (StubSparkView)) + .SetPageBaseType(typeof(StubSparkView)) .SetParseSectionTagAsSegment(true); - var engine = new SparkViewEngine(settings) - { - ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") - }; + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .BuildServiceProvider(); + + var engine = (SparkViewEngine)sp.GetService(); var view = (StubSparkView) @@ -206,8 +214,7 @@ public void SectionAsSegmentAndRenderPrefixes() [Test] public void MacroAndContentPrefixesFromSettings() { - _engine.Settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)) + this._settings.SetPageBaseType(typeof(StubSparkView)) .SetPrefix("s"); var view = diff --git a/src/Spark.Web.Tests/SparkExtensionTester.cs b/src/Spark.Web.Tests/SparkExtensionTester.cs index 91e4f437..c490c59b 100644 --- a/src/Spark.Web.Tests/SparkExtensionTester.cs +++ b/src/Spark.Web.Tests/SparkExtensionTester.cs @@ -21,6 +21,8 @@ using Spark.FileSystem; using Spark.Parser.Markup; using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Spark.Extensions; namespace Spark { @@ -32,9 +34,15 @@ public class SparkExtensionTester [SetUp] public void Init() { - engine = new SparkViewEngine(new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView")) - {ViewFolder = new FileSystemViewFolder("Spark.Tests.Views")}; - engine.ExtensionFactory = new StubExtensionFactory(); + var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .AddSingleton(new StubExtensionFactory()) + .BuildServiceProvider(); + + engine = (SparkViewEngine)sp.GetService(); } [Test] diff --git a/src/Spark.Web.Tests/SparkServiceContainerTester.cs b/src/Spark.Web.Tests/SparkServiceContainerTester.cs deleted file mode 100644 index 6cb74cbc..00000000 --- a/src/Spark.Web.Tests/SparkServiceContainerTester.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.Collections.Generic; -using System.Configuration; -using NUnit.Framework; -using Spark.Bindings; -using Spark.FileSystem; -using System.IO; - -namespace Spark -{ - [TestFixture] - public class SparkServiceContainerTester - { - [Test] - public void ContainerCreatesDefaultServices() - { - var container = new SparkServiceContainer(); - - var langauageFactory = container.GetService(); - Assert.IsInstanceOf(typeof(DefaultLanguageFactory), langauageFactory); - - var resourcePathManager = container.GetService(); - Assert.IsInstanceOf(typeof(DefaultResourcePathManager), resourcePathManager); - - var bindingProvider = container.GetService(); - Assert.IsInstanceOf(typeof(DefaultBindingProvider), bindingProvider); - - var partialProvider = container.GetService(); - Assert.IsInstanceOf(typeof(DefaultPartialProvider), partialProvider); - - var partialReferenceProvider = container.GetService(); - Assert.IsInstanceOf(typeof(DefaultPartialReferenceProvider), partialReferenceProvider); - } - - [Test] - public void ConfigSettingsUsedByDefault() - { - var container = new SparkServiceContainer(); - - var settings = container.GetService().Settings; - Assert.AreSame(ConfigurationManager.GetSection("spark"), settings); - } - - [Test] - public void CreatedSettingsUsedWhenProvided() - { - var settings = new SparkSettings().SetPrefix("foo"); - var container = new SparkServiceContainer(settings); - - var settings2 = container.GetService().Settings; - Assert.AreSame(settings, settings2); - } - - [Test] - public void SettingsServiceReplacesType() - { - var container = new SparkServiceContainer(); - container.SetService(new StubExtensionFactory()); - Assert.IsInstanceOf(typeof(StubExtensionFactory), container.GetService()); - } - - [Test] - public void AddingServiceInstanceCallsInitialize() - { - var container = new SparkServiceContainer(); - var service = new TestService(); - Assert.IsFalse(service.Initialized); - container.SetService(service); - Assert.IsTrue(service.Initialized); - Assert.AreSame(service, container.GetService()); - } - - [Test] - public void AddingServiceBuilderCallsInitialize() - { - var container = new SparkServiceContainer(); - container.SetServiceBuilder(typeof(ITestService), c => new TestService()); - var service = container.GetService(); - Assert.IsInstanceOf(typeof(TestService), service); - Assert.IsTrue(((TestService)service).Initialized); - } - - [Test] - public void EngineGetsCustomServiceAndViewFolderSettings() - { - var settings = new SparkSettings(); - settings.AddViewFolder(typeof(TestViewFolder), - new Dictionary { { "testpath", Path.Combine("hello", "world.spark") } }); - - var container = new SparkServiceContainer(settings); - container.SetServiceBuilder(c=>new TestActivatorFactory()); - - var engine = container.GetService(); - Assert.IsInstanceOf(typeof(TestActivatorFactory), engine.ViewActivatorFactory); - - Assert.IsTrue(engine.ViewFolder.HasView(Path.Combine("hello", "world.spark"))); - } - } - - public interface ITestService - { - - } - public class TestService : ITestService, ISparkServiceInitialize - { - public bool Initialized { get; set; } - public void Initialize(ISparkServiceContainer container) - { - Initialized = true; - } - } - public class TestViewFolder : IViewFolder - { - private readonly string _testpath; - - public TestViewFolder(string testpath) - { - _testpath = testpath; - } - - public IViewFile GetViewSource(string path) - { - throw new System.NotImplementedException(); - } - - public IList ListViews(string path) - { - throw new System.NotImplementedException(); - } - - public bool HasView(string path) - { - return path == _testpath; - } - } - public class TestActivatorFactory : IViewActivatorFactory - { - public IViewActivator Register(Type type) - { - throw new System.NotImplementedException(); - } - - public void Unregister(Type type, IViewActivator activator) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Spark.Web.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Tests/SparkViewFactoryTester.cs index a7373d62..fe734fb5 100644 --- a/src/Spark.Web.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Tests/SparkViewFactoryTester.cs @@ -20,18 +20,20 @@ // John Gietzen //------------------------------------------------------------------------- +using Microsoft.Extensions.DependencyInjection; +using Spark.Extensions; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +using Rhino.Mocks; +using Spark.Compiler; +using Spark.FileSystem; +using Spark.Tests.Models; +using Spark.Tests.Stubs; + namespace Spark.Tests { - using System.Collections.Generic; - using System.Text; - using NUnit.Framework; - - using Rhino.Mocks; - using Spark.Compiler; - using Spark.FileSystem; - using Spark.Tests.Models; - using Spark.Tests.Stubs; - [TestFixture, Category("SparkViewEngine")] public class SparkViewFactoryTester { @@ -46,7 +48,14 @@ public class SparkViewFactoryTester public void Init() { settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); - engine = new SparkViewEngine(settings) { ViewFolder = new FileSystemViewFolder("Spark.Tests.Views") }; + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton(new FileSystemViewFolder("Spark.Tests.Views")) + .BuildServiceProvider(); + + engine = (SparkViewEngine)sp.GetService(); + factory = new StubViewFactory { Engine = engine }; sb = new StringBuilder(); diff --git a/src/Spark.Web.Tests/ViewActivatorTester.cs b/src/Spark.Web.Tests/ViewActivatorTester.cs index ccf4ac22..d1ced9e8 100644 --- a/src/Spark.Web.Tests/ViewActivatorTester.cs +++ b/src/Spark.Web.Tests/ViewActivatorTester.cs @@ -15,7 +15,9 @@ using System; using System.Diagnostics; using System.IO; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Spark.Extensions; using Spark.FileSystem; using Spark.Tests.Stubs; @@ -88,12 +90,15 @@ public void FastCreateViewInstance() [Test] public void CustomViewActivator() { - var engine = new SparkViewEngine( - new SparkSettings().SetPageBaseType(typeof(StubSparkView))) - { - ViewActivatorFactory = new CustomFactory(), - ViewFolder = new InMemoryViewFolder { { "hello/world.spark", "

hello world

" } } - }; + var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton() + .AddSingleton(new InMemoryViewFolder { { "hello/world.spark", "

hello world

" } }) + .BuildServiceProvider(); + + var engine = (SparkViewEngine) sp.GetService(); var descriptor = new SparkViewDescriptor().AddTemplate("hello/world.spark"); var view = engine.CreateInstance(descriptor); diff --git a/src/Spark.Web.Tests/Visitors/DetectCodeExpressionTester.cs b/src/Spark.Web.Tests/Visitors/DetectCodeExpressionTester.cs index e1a4a460..3b3dba30 100644 --- a/src/Spark.Web.Tests/Visitors/DetectCodeExpressionTester.cs +++ b/src/Spark.Web.Tests/Visitors/DetectCodeExpressionTester.cs @@ -33,8 +33,10 @@ public void FindLoopParameters() { SyntaxProvider = new DefaultSyntaxProvider(new SparkSettings()) }; - var nodes = ParseNodes("${xIndex}${xIsLast}", - new SpecialNodeVisitor(context)); + + var nodes = ParseNodes( + "${xIndex}${xIsLast}", + new SpecialNodeVisitor(context)); var visitor = new ChunkBuilderVisitor(context); visitor.Accept(nodes); @@ -56,11 +58,23 @@ public void FindLoopParameters() public void ParametersInPartial() { var viewFolder = new InMemoryViewFolder - { - {Path.Combine("home", "index.spark"), ""}, - {Path.Combine("home", "_Guts.spark"), "

${xIndex}

"} - }; - var loader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + { + { Path.Combine("home", "index.spark"), "" }, + { Path.Combine("home", "_Guts.spark"), "

${xIndex}

" } + }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var loader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); var chunks = loader.Load(Path.Combine("home", "index.spark")); @@ -78,11 +92,23 @@ public void ParametersInPartial() public void ParametersInCallerBody() { var viewFolder = new InMemoryViewFolder - { - {Path.Combine("home", "index.spark"), "${xIndex}"}, - {Path.Combine("home", "_Guts.spark"), "

"} - }; - var loader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + { + { Path.Combine("home", "index.spark"), "${xIndex}" }, + { Path.Combine("home", "_Guts.spark"), "

" } + }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var loader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); var chunks = loader.Load(Path.Combine("home", "index.spark")); @@ -99,11 +125,23 @@ public void ParametersInCallerBody() public void ParametersInNamedSegment() { var viewFolder = new InMemoryViewFolder - { - {Path.Combine("home", "index.spark"), "${xIndex}"}, - {Path.Combine("home", "_Guts.spark"), "

"} - }; - var loader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + { + { Path.Combine("home", "index.spark"), "${xIndex}" }, + { Path.Combine("home", "_Guts.spark"), "

" } + }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var loader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); var chunks = loader.Load(Path.Combine("home", "index.spark")); @@ -120,11 +158,23 @@ public void ParametersInNamedSegment() public void IterationInPartialFile() { var viewFolder = new InMemoryViewFolder - { - {Path.Combine("home", "index.spark"), "${xIndex}"}, - {Path.Combine("home", "_Guts.spark"), ""} - }; - var loader = new ViewLoader { SyntaxProvider = new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), ViewFolder = viewFolder }; + { + { Path.Combine("home", "index.spark"), "${xIndex}" }, + { Path.Combine("home", "_Guts.spark"), "" } + }; + + var settings = new SparkSettings(); + + var partialProvider = new DefaultPartialProvider(); + + var loader = new ViewLoader( + settings, + viewFolder, + new DefaultPartialProvider(), + new DefaultPartialReferenceProvider(partialProvider), + null, + new DefaultSyntaxProvider(ParserSettings.DefaultBehavior), + null); var chunks = loader.Load(Path.Combine("home", "index.spark")); diff --git a/src/Spark.Web.Tests/VisualBasicViewTester.cs b/src/Spark.Web.Tests/VisualBasicViewTester.cs index 667bc85f..44a8097f 100644 --- a/src/Spark.Web.Tests/VisualBasicViewTester.cs +++ b/src/Spark.Web.Tests/VisualBasicViewTester.cs @@ -4,6 +4,8 @@ using Spark.Tests.Models; using Spark.Tests.Stubs; using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Spark.Extensions; namespace Spark { @@ -17,16 +19,19 @@ public class VisualBasicViewTester [SetUp] public void Init() { - _viewFolder = new InMemoryViewFolder(); + var settings = new SparkSettings() + .SetDefaultLanguage(LanguageType.VisualBasic) + .SetPageBaseType(typeof(StubSparkView)); + + var sp = new ServiceCollection() + .AddSpark(settings) + .AddSingleton() + .BuildServiceProvider(); + + _viewFolder = (InMemoryViewFolder)sp.GetService(); _factory = new StubViewFactory { - Engine = new SparkViewEngine( - new SparkSettings() - .SetDefaultLanguage(LanguageType.VisualBasic) - .SetPageBaseType(typeof(StubSparkView))) - { - ViewFolder = _viewFolder - } + Engine = (SparkViewEngine)sp.GetService() }; } diff --git a/src/Spark.sln.DotSettings b/src/Spark.sln.DotSettings new file mode 100644 index 00000000..acf0b017 --- /dev/null +++ b/src/Spark.sln.DotSettings @@ -0,0 +1,3 @@ + + DO_NOT_SHOW + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> \ No newline at end of file diff --git a/src/Spark/Compiler/AssemblyExtensions.cs b/src/Spark/Compiler/AssemblyExtensions.cs new file mode 100644 index 00000000..45b89a40 --- /dev/null +++ b/src/Spark/Compiler/AssemblyExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +namespace Spark.Compiler +{ + public static class AssemblyExtensions + { + public static bool IsDynamic(this Assembly assembly) + { +#if NETFRAMEWORK || NET + if (assembly is AssemblyBuilder) + { + return true; + } +#endif + +#if NETFRAMEWORK + if (assembly.ManifestModule.GetType().Namespace == "System.Reflection.Emit" /* .Net 4 specific */) + { + return true; + } +#endif + + return assembly.HasNoLocation(); + } + + private static bool HasNoLocation(this Assembly assembly) + { + bool result; + + try + { + result = string.IsNullOrEmpty(assembly.Location); + } + catch (NotSupportedException) + { + return true; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/BatchCompilerException.cs b/src/Spark/Compiler/BatchCompilerException.cs deleted file mode 100644 index de8a2527..00000000 --- a/src/Spark/Compiler/BatchCompilerException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.CodeDom.Compiler; - -namespace Spark.Compiler -{ - public class BatchCompilerException : CompilerException - { - public BatchCompilerException(string message, CompilerResults results) : base(message) - { - Results = results; - } - - public CompilerResults Results { get; set; } - } -} \ No newline at end of file diff --git a/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs b/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs index 2edbbdaf..76abcec8 100644 --- a/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs +++ b/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs @@ -12,28 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using Spark.Compiler.CSharp.ChunkVisitors; namespace Spark.Compiler.CSharp { - public class CSharpViewCompiler : ViewCompiler + public class CSharpViewCompiler(IBatchCompiler compiler) : ViewCompiler { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { GenerateSourceCode(viewTemplates, allResources); - var batchCompiler = new BatchCompiler(); - var assembly = batchCompiler.Compile(Debug, "csharp", SourceCode); + var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); + CompiledType = assembly.GetType(ViewClassFullName); } - - public override void GenerateSourceCode(IEnumerable> viewTemplates, IEnumerable> allResources) + public override void GenerateSourceCode( + IEnumerable> viewTemplates, + IEnumerable> allResources) { var globalSymbols = new Dictionary(); @@ -77,7 +77,7 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source .WriteLine() - .WriteLine(string.Format("namespace {0}", TargetNamespace)) + .Write("namespace ").WriteLine(TargetNamespace) .WriteLine("{").AddIndent(); } @@ -88,11 +88,32 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, // [SparkView] attribute source.WriteLine("[global::Spark.SparkViewAttribute("); if (TargetNamespace != null) - source.WriteFormat(" TargetNamespace=\"{0}\",", TargetNamespace).WriteLine(); + { + source + .Write(" TargetNamespace=\"") + .Write(TargetNamespace) + .WriteLine("\","); + } + source.WriteLine(" Templates = new string[] {"); - source.Write(" ").WriteLine(string.Join(",\r\n ", - Descriptor.Templates.Select( - t => "\"" + SparkViewAttribute.ConvertToAttributeFormat(t) + "\"").ToArray())); + + source.Write(" "); + + for (var i = 0; i < Descriptor.Templates.Count; i++) + { + var template = Descriptor.Templates[i]; + + source + .Write("\"") + .Write(SparkViewAttribute.ConvertToAttributeFormat(template)) + .Write("\""); + + if (i < Descriptor.Templates.Count - 1) + { + source.WriteLine(","); + } + } + source.WriteLine(" })]"); } @@ -107,7 +128,11 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source.WriteLine(); EditorBrowsableStateNever(source, 4); - source.WriteLine("private static System.Guid _generatedViewId = new System.Guid(\"{0:n}\");", GeneratedViewId); + + source + .Write("private static System.Guid _generatedViewId = new System.Guid(\"") + .Write(GeneratedViewId.ToString("n")) + .WriteLine("\");"); source.WriteLine("public override System.Guid GeneratedViewId"); source.WriteLine("{ get { return _generatedViewId; } }"); @@ -127,13 +152,15 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, globalsGenerator.Accept(resource); } - // public void RenderViewLevelx() + // private void RenderViewLevelx() int renderLevel = 0; foreach (var viewTemplate in viewTemplates) { source.WriteLine(); - EditorBrowsableStateNever(source, 4); - source.WriteLine(string.Format("private void RenderViewLevel{0}()", renderLevel)); + EditorBrowsableStateNever(source, 4); + + source.Write("private void RenderViewLevel").Write(renderLevel.ToString()).WriteLine("()"); + source.WriteLine("{").AddIndent(); var viewGenerator = new GeneratedCodeVisitor(source, globalSymbols, NullBehaviour); viewGenerator.Accept(viewTemplate); @@ -142,9 +169,8 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, } // public void RenderView() - source.WriteLine(); - EditorBrowsableStateNever(source, 4); + EditorBrowsableStateNever(source, 4); source.WriteLine("public override void Render()"); source.WriteLine("{").AddIndent(); for (var invokeLevel = 0; invokeLevel != renderLevel; ++invokeLevel) @@ -152,18 +178,27 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, { if (invokeLevel != renderLevel - 1) { - source.WriteLine("using (OutputScope()) {{DelegateFirstRender(RenderViewLevel{0}); Content[\"view\"] = Output;}}", invokeLevel); + source + .Write("using (OutputScope()) {{DelegateFirstRender(RenderViewLevel") + .Write(invokeLevel.ToString()) + .WriteLine("); Content[\"view\"] = Output;}}"); } else { if (renderLevel <= 1) { - source.WriteLine(" DelegateFirstRender(RenderViewLevel{0});", invokeLevel); + source + .Write(" DelegateFirstRender(RenderViewLevel") + .Write(invokeLevel.ToString()) + .WriteLine(");"); + } + else + { + source + .Write(" RenderViewLevel") + .Write(invokeLevel.ToString()) + .WriteLine("();"); } - else - { - source.WriteLine(" RenderViewLevel{0}();", invokeLevel); - } } } source.RemoveIndent().WriteLine("}"); diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs similarity index 70% rename from src/Spark/Compiler/BatchCompiler.cs rename to src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs index cd6b239a..cbca4fe5 100644 --- a/src/Spark/Compiler/BatchCompiler.cs +++ b/src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs @@ -1,210 +1,185 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using Microsoft.CSharp; - -namespace Spark.Compiler -{ - public class BatchCompiler - { - public string OutputAssembly { get; set; } - - public Assembly Compile(bool debug, string languageOrExtension, params string[] sourceCode) - { - var language = languageOrExtension; - if (CodeDomProvider.IsDefinedLanguage(languageOrExtension) == false && - CodeDomProvider.IsDefinedExtension(languageOrExtension)) - { - language = CodeDomProvider.GetLanguageFromExtension(languageOrExtension); - } - - CodeDomProvider codeProvider; - CompilerParameters compilerParameters; - - if (ConfigurationManager.GetSection("system.codedom") != null) - { - var compilerInfo = CodeDomProvider.GetCompilerInfo(language); - codeProvider = compilerInfo.CreateProvider(); - compilerParameters = compilerInfo.CreateDefaultCompilerParameters(); - } - else - { - if (!language.Equals("c#", StringComparison.OrdinalIgnoreCase) && - !language.Equals("cs", StringComparison.OrdinalIgnoreCase) && - !language.Equals("csharp", StringComparison.OrdinalIgnoreCase)) - { - throw new CompilerException( - string.Format("When running the {0} in an AppDomain without a system.codedom config section only the csharp language is supported. This happens if you are precompiling your views.", - typeof(BatchCompiler).FullName)); - } - - var compilerVersion = GetCompilerVersion(); - - var providerOptions = new Dictionary { { "CompilerVersion", compilerVersion } }; - codeProvider = new CSharpCodeProvider(providerOptions); - compilerParameters = new CompilerParameters(); - } - - compilerParameters.TreatWarningsAsErrors = false; - var extension = codeProvider.FileExtension; - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - if (assembly.IsDynamic()) - { - continue; - } - - compilerParameters.ReferencedAssemblies.Add(assembly.Location); - } - - CompilerResults compilerResults; - - var dynamicBase = string.Empty; - -#if NETFRAMEWORK - dynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase; -#else - dynamicBase = AppDomain.CurrentDomain.DynamicDirectory; -#endif - - var basePath = !string.IsNullOrEmpty(dynamicBase) ? dynamicBase : Path.GetTempPath(); - compilerParameters.TempFiles = new TempFileCollection(basePath); //Without this, the generated code throws Access Denied exception with Impersonate mode on platforms like SharePoint - if (debug) - { - compilerParameters.IncludeDebugInformation = true; - - var baseFile = Path.Combine(basePath, Guid.NewGuid().ToString("n")); - - var codeFiles = new List(); - int fileCount = 0; - foreach (string sourceCodeItem in sourceCode) - { - ++fileCount; - var codeFile = baseFile + "-" + fileCount + "." + extension; - using (var stream = new FileStream(codeFile, FileMode.Create, FileAccess.Write)) - { - using (var writer = new StreamWriter(stream)) - { - writer.Write(sourceCodeItem); - } - } - codeFiles.Add(codeFile); - } - - if (!string.IsNullOrEmpty(OutputAssembly)) - { - compilerParameters.OutputAssembly = Path.Combine(basePath, OutputAssembly); - } - else - { - compilerParameters.OutputAssembly = baseFile + ".dll"; - } - compilerResults = codeProvider.CompileAssemblyFromFile(compilerParameters, codeFiles.ToArray()); - } - else - { - if (!string.IsNullOrEmpty(OutputAssembly)) - { - compilerParameters.OutputAssembly = Path.Combine(basePath, OutputAssembly); - } - else - { - // This should result in the assembly being loaded without keeping the file on disk - compilerParameters.GenerateInMemory = true; - } - - compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, sourceCode); - } - - if (compilerResults.Errors.HasErrors) - { - var sb = new StringBuilder(); - sb.AppendLine("Dynamic view compilation failed."); - - foreach (CompilerError err in compilerResults.Errors) - { - sb.AppendFormat("{4}({0},{1}): {2} {3}: ", err.Line, err.Column, err.IsWarning ? "warning" : "error", err.ErrorNumber, err.FileName); - sb.AppendLine(err.ErrorText); - } - - sb.AppendLine(); - foreach (var sourceCodeItem in sourceCode) - { - using (var reader = new StringReader(sourceCodeItem)) - { - for (int lineNumber = 1; ; ++lineNumber) - { - var line = reader.ReadLine(); - if (line == null) - break; - sb.Append(lineNumber).Append(' ').AppendLine(line); - } - } - } - throw new BatchCompilerException(sb.ToString(), compilerResults); - } - - return compilerResults.CompiledAssembly; - } - - private static string GetCompilerVersion() - { - return "v4.0"; - } - } - - public static class AssemblyExtensions - { - public static bool IsDynamic(this Assembly assembly) - { - if (assembly is AssemblyBuilder) - { - return true; - } - - if (assembly.ManifestModule.GetType().Namespace == "System.Reflection.Emit" /* .Net 4 specific */) - { - return true; - } - - return assembly.HasNoLocation(); - } - - private static bool HasNoLocation(this Assembly assembly) - { - bool result; - - try - { - result = string.IsNullOrEmpty(assembly.Location); - } - catch (NotSupportedException) - { - return true; - } - - return result; - } - } - -} +// Copyright 2008-2009 Louis DeJardin - http://whereslou.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CSharp; + +namespace Spark.Compiler.CodeDom +{ + [Obsolete("To be replaced with RoslynBatchCompiler")] + public class CodeDomBatchCompiler : IBatchCompiler + { + /// + /// Compiles the in the specified . + /// + /// When true the source is compiled in debug mode. + /// E.g. "csharp" or "visualbasic" + /// E.g. "File.Name.dll" (optional) + /// The source code to compile. + /// + /// + /// + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + { + var language = languageOrExtension; + if (CodeDomProvider.IsDefinedLanguage(languageOrExtension) == false && + CodeDomProvider.IsDefinedExtension(languageOrExtension)) + { + language = CodeDomProvider.GetLanguageFromExtension(languageOrExtension); + } + + CodeDomProvider codeProvider; + CompilerParameters compilerParameters; + + if (ConfigurationManager.GetSection("system.codedom") != null) + { + var compilerInfo = CodeDomProvider.GetCompilerInfo(language); + codeProvider = compilerInfo.CreateProvider(); + compilerParameters = compilerInfo.CreateDefaultCompilerParameters(); + } + else + { + if (!language.Equals("c#", StringComparison.OrdinalIgnoreCase) && + !language.Equals("cs", StringComparison.OrdinalIgnoreCase) && + !language.Equals("csharp", StringComparison.OrdinalIgnoreCase)) + { + throw new CompilerException( + $"When running the {typeof(CodeDomBatchCompiler).FullName} in an AppDomain without a system.codedom config section only the csharp language is supported. This happens if you are precompiling your views."); + } + + var compilerVersion = GetCompilerVersion(); + + var providerOptions = new Dictionary { { "CompilerVersion", compilerVersion } }; + codeProvider = new CSharpCodeProvider(providerOptions); + compilerParameters = new CompilerParameters(); + } + + compilerParameters.TreatWarningsAsErrors = false; + var extension = codeProvider.FileExtension; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.IsDynamic()) + { + continue; + } + + compilerParameters.ReferencedAssemblies.Add(assembly.Location); + } + + CompilerResults compilerResults; + + // ReSharper disable once RedundantAssignment + var dynamicBase = string.Empty; + +#if NETFRAMEWORK + dynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase; +#else + dynamicBase = AppDomain.CurrentDomain.DynamicDirectory; +#endif + + var basePath = !string.IsNullOrEmpty(dynamicBase) ? dynamicBase : Path.GetTempPath(); + compilerParameters.TempFiles = new TempFileCollection(basePath); //Without this, the generated code throws Access Denied exception with Impersonate mode on platforms like SharePoint + if (debug) + { + compilerParameters.IncludeDebugInformation = true; + + var baseFile = Path.Combine(basePath, Guid.NewGuid().ToString("n")); + + var codeFiles = new List(); + int fileCount = 0; + foreach (string sourceCodeItem in sourceCode) + { + ++fileCount; + var codeFile = baseFile + "-" + fileCount + "." + extension; + using (var stream = new FileStream(codeFile, FileMode.Create, FileAccess.Write)) + { + using (var writer = new StreamWriter(stream)) + { + writer.Write(sourceCodeItem); + } + } + codeFiles.Add(codeFile); + } + + if (!string.IsNullOrEmpty(outputAssembly)) + { + compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); + } + else + { + compilerParameters.OutputAssembly = baseFile + ".dll"; + } + compilerResults = codeProvider.CompileAssemblyFromFile(compilerParameters, codeFiles.ToArray()); + } + else + { + if (!string.IsNullOrEmpty(outputAssembly)) + { + compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); + } + else + { + // This should result in the assembly being loaded without keeping the file on disk + compilerParameters.GenerateInMemory = true; + } + + compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, sourceCode.ToArray()); + } + + if (compilerResults.Errors.HasErrors) + { + var sb = new StringBuilder().AppendLine("Dynamic view compilation failed."); + + foreach (CompilerError err in compilerResults.Errors) + { + sb.AppendFormat("{4}({0},{1}): {2} {3}: ", err.Line, err.Column, err.IsWarning ? "warning" : "error", err.ErrorNumber, err.FileName); + sb.AppendLine(err.ErrorText); + } + + sb.AppendLine(); + foreach (var sourceCodeItem in sourceCode) + { + using var reader = new StringReader(sourceCodeItem); + + for (int lineNumber = 1; ; ++lineNumber) + { + var line = reader.ReadLine(); + if (line == null) + { + break; + } + + sb.Append(lineNumber).Append(' ').AppendLine(line); + } + } + throw new CodeDomCompilerException(sb.ToString(), compilerResults); + } + + return compilerResults.CompiledAssembly; + } + + private static string GetCompilerVersion() + { + return "v4.0"; + } + } +} diff --git a/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs b/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs new file mode 100644 index 00000000..a3b3eb3a --- /dev/null +++ b/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs @@ -0,0 +1,9 @@ +using System.CodeDom.Compiler; + +namespace Spark.Compiler.CodeDom +{ + public class CodeDomCompilerException(string message, CompilerResults results) : CompilerException(message) + { + public CompilerResults Results { get; set; } = results; + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/CompilerException.cs b/src/Spark/Compiler/CompilerException.cs index 4f45e535..49a64787 100644 --- a/src/Spark/Compiler/CompilerException.cs +++ b/src/Spark/Compiler/CompilerException.cs @@ -17,8 +17,13 @@ namespace Spark.Compiler { + [Serializable] public class CompilerException : SystemException { + public CompilerException() + { + } + public CompilerException(string message) : base(message) { diff --git a/src/Spark/Compiler/IBatchCompiler.cs b/src/Spark/Compiler/IBatchCompiler.cs new file mode 100644 index 00000000..79ac9bf5 --- /dev/null +++ b/src/Spark/Compiler/IBatchCompiler.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Spark.Compiler +{ + public interface IBatchCompiler + { + /// + /// Compiles the in the specified . + /// + /// When true the source is compiled in debug mode. + /// E.g. "csharp" or "visualbasic" + /// E.g. "File.Name.dll" (optional) + /// The source code to compile. + /// + Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode); + } +} diff --git a/src/Spark/Compiler/Roslyn/CSharpLink.cs b/src/Spark/Compiler/Roslyn/CSharpLink.cs new file mode 100644 index 00000000..42513c63 --- /dev/null +++ b/src/Spark/Compiler/Roslyn/CSharpLink.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Spark.Compiler.Roslyn; + +public class CSharpLink : IRoslynCompilationLink +{ + public bool ShouldVisit(string languageOrExtension) => languageOrExtension is "c#" or "cs" or "csharp"; + + public Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode) + { + var syntaxTrees = sourceCode.Select(source => CSharpSyntaxTree.ParseText(source)); + + var optimizationLevel = debug ? OptimizationLevel.Debug : OptimizationLevel.Release; + + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithOptimizationLevel(optimizationLevel) + .WithPlatform(Platform.AnyCpu); + + var compilation = CSharpCompilation.Create( + assemblyName, + syntaxTrees: syntaxTrees, + references: references, + options: options); + + using var ms = new MemoryStream(); + + var result = compilation.Emit(ms); + + if (!result.Success) + { + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + var sb = new StringBuilder(); + + foreach (var diagnostic in failures) + { + sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); + } + + throw new RoslynCompilerException(sb.ToString(), result); + } + + ms.Seek(0, SeekOrigin.Begin); + + var assembly = Assembly.Load(ms.ToArray()); + + return assembly; + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs b/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs new file mode 100644 index 00000000..1180b7ee --- /dev/null +++ b/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Spark.Compiler.Roslyn; + +public interface IRoslynCompilationLink +{ + bool ShouldVisit(string languageOrExtension); + + Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode); +} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs b/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs new file mode 100644 index 00000000..28678ff9 --- /dev/null +++ b/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Spark.Compiler.Roslyn +{ + public class RoslynBatchCompiler : IBatchCompiler + { + private readonly IEnumerable links; + + public RoslynBatchCompiler() + { + this.links = new IRoslynCompilationLink[] { new CSharpLink(), new VisualBasicLink() }; + } + + public RoslynBatchCompiler(IEnumerable links) + { + this.links = links; + } + + /// + /// Compiles the in the specified . + /// + /// When true the source is compiled in debug mode. + /// E.g. "csharp" or "visualbasic" + /// E.g. "File.Name.dll" (optional) + /// The source code to compile. + /// + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + { + Assembly assembly = null; + + if (!this.links.Any()) + { + throw new ConfigurationErrorsException("No IRoslynCompilationLink links"); + } + + string assemblyName; + if (!string.IsNullOrEmpty(outputAssembly)) + { + var basePath = Path.GetTempPath(); + assemblyName = Path.Combine(basePath, outputAssembly); + } + else + { + assemblyName = Path.GetRandomFileName(); + } + + var references = new List(); + + foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (currentAssembly.IsDynamic()) + { + continue; + } + + var reference = MetadataReference.CreateFromFile(currentAssembly.Location); + + references.Add(reference); + } + + var match = false; + foreach (var visitor in this.links) + { + if (visitor.ShouldVisit(languageOrExtension)) + { + match = true; + + assembly = visitor.Compile(debug, assemblyName, references, sourceCode); + + // Chain of responsibility pattern + break; + } + } + + if (!match) + { + throw new ArgumentOutOfRangeException(nameof(languageOrExtension), languageOrExtension, "Un-handled value"); + } + + return assembly; + } + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs b/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs new file mode 100644 index 00000000..7314cb29 --- /dev/null +++ b/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis.Emit; + +namespace Spark.Compiler.Roslyn +{ + public class RoslynCompilerException(string message, EmitResult emitResult) : CompilerException(message) + { + public EmitResult EmitResult { get; set; } = emitResult; + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/VisualBasicLink.cs b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs new file mode 100644 index 00000000..eebbae0c --- /dev/null +++ b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace Spark.Compiler.Roslyn; + +public class VisualBasicLink : IRoslynCompilationLink +{ + public bool ShouldVisit(string languageOrExtension) => languageOrExtension is "vb" or "vbs" or "visualbasic" or "vbscript"; + + public Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode) + { + var syntaxTrees = sourceCode.Select(source => VisualBasicSyntaxTree.ParseText(source)); + + var optimizationLevel = debug ? OptimizationLevel.Debug : OptimizationLevel.Release; + + var options = new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithOptimizationLevel(optimizationLevel) + .WithPlatform(Platform.AnyCpu); + + var compilation = VisualBasicCompilation.Create( + assemblyName, + syntaxTrees: syntaxTrees, + references: references, + options: options); + + using var ms = new MemoryStream(); + + var result = compilation.Emit(ms); + + if (!result.Success) + { + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + var sb = new StringBuilder(); + + foreach (var diagnostic in failures) + { + sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); + } + + throw new RoslynCompilerException(sb.ToString(), result); + } + + ms.Seek(0, SeekOrigin.Begin); + + var assembly = Assembly.Load(ms.ToArray()); + + return assembly; + } +} \ No newline at end of file diff --git a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs index 8f1233d4..8ac9cf75 100644 --- a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs +++ b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs @@ -12,22 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. // + +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; +using Spark.Compiler.CodeDom; using Spark.Compiler.VisualBasic.ChunkVisitors; namespace Spark.Compiler.VisualBasic { - public class VisualBasicViewCompiler : ViewCompiler + public class VisualBasicViewCompiler(IBatchCompiler batchCompiler) : ViewCompiler { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { GenerateSourceCode(viewTemplates, allResources); - var batchCompiler = new BatchCompiler(); - var assembly = batchCompiler.Compile(Debug, "visualbasic", SourceCode); + var assembly = batchCompiler.Compile(Debug, "visualbasic", null, new[] { SourceCode }); CompiledType = assembly.GetType(ViewClassFullName); } @@ -100,8 +101,11 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, .AddIndent(); source.WriteLine(); - source.WriteLine(string.Format(" Private Shared ReadOnly _generatedViewId As Global.System.Guid = New Global.System.Guid(\"{0:n}\")", GeneratedViewId)); + source + .Write(" Private Shared ReadOnly _generatedViewId As Global.System.Guid = New Global.System.Guid(\"") + .Write(GeneratedViewId.ToString("n")) + .WriteLine("\")"); source .WriteLine(" Public Overrides ReadOnly Property GeneratedViewId() As Global.System.Guid") diff --git a/src/Spark/DefaultLanguageFactory.cs b/src/Spark/DefaultLanguageFactory.cs index 276dabd8..7800896a 100644 --- a/src/Spark/DefaultLanguageFactory.cs +++ b/src/Spark/DefaultLanguageFactory.cs @@ -19,7 +19,7 @@ namespace Spark { - public class DefaultLanguageFactory : ISparkLanguageFactory + public class DefaultLanguageFactory(IBatchCompiler batchCompiler) : ISparkLanguageFactory { public virtual ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkViewDescriptor descriptor) { @@ -40,16 +40,16 @@ public virtual ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkVie { case LanguageType.Default: case LanguageType.CSharp: - viewCompiler = new CSharpViewCompiler(); + viewCompiler = new CSharpViewCompiler(batchCompiler); break; case LanguageType.VisualBasic: - viewCompiler = new VisualBasicViewCompiler(); + viewCompiler = new VisualBasicViewCompiler(batchCompiler); break; case LanguageType.Javascript: viewCompiler = new JavascriptViewCompiler(); break; default: - throw new CompilerException(string.Format("Unknown language type {0}", descriptor.Language)); + throw new CompilerException($"Unknown language type {descriptor.Language}"); } viewCompiler.BaseClass = pageBaseType; diff --git a/src/Spark/ISparkServiceContainer.cs b/src/Spark/ISparkServiceContainer.cs deleted file mode 100644 index 6d5a268d..00000000 --- a/src/Spark/ISparkServiceContainer.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; - -namespace Spark -{ - public interface ISparkServiceContainer : IServiceProvider - { - T GetService(); - void SetService(TServiceInterface service); - void SetServiceBuilder(Func serviceBuilder); - } -} diff --git a/src/Spark/ISparkServiceInitialize.cs b/src/Spark/ISparkServiceInitialize.cs deleted file mode 100644 index 0bca4899..00000000 --- a/src/Spark/ISparkServiceInitialize.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spark -{ - public interface ISparkServiceInitialize - { - void Initialize(ISparkServiceContainer container); - } -} diff --git a/src/Spark/ISparkViewEngine.cs b/src/Spark/ISparkViewEngine.cs index c077f782..4d140b6a 100644 --- a/src/Spark/ISparkViewEngine.cs +++ b/src/Spark/ISparkViewEngine.cs @@ -14,7 +14,6 @@ // using System.Collections.Generic; using System.Reflection; -using Spark; using Spark.FileSystem; namespace Spark @@ -22,12 +21,12 @@ namespace Spark public interface ISparkViewEngine { ISparkSettings Settings { get; } + IViewFolder ViewFolder { get; set; } - IResourcePathManager ResourcePathManager { get; set; } - ISparkExtensionFactory ExtensionFactory { get; set; } - IViewActivatorFactory ViewActivatorFactory { get; set; } - string DefaultPageBaseType { get; set; } - ISparkSyntaxProvider SyntaxProvider { get; set; } + + IViewActivatorFactory ViewActivatorFactory { get; } + string DefaultPageBaseType { get; } + ISparkSyntaxProvider SyntaxProvider { get; } ISparkViewEntry GetEntry(SparkViewDescriptor descriptor); ISparkViewEntry CreateEntry(SparkViewDescriptor descriptor); diff --git a/src/Spark/Parser/ViewLoader.cs b/src/Spark/Parser/ViewLoader.cs index 8921a3b9..fab231be 100644 --- a/src/Spark/Parser/ViewLoader.cs +++ b/src/Spark/Parser/ViewLoader.cs @@ -29,58 +29,24 @@ namespace Spark.Parser using Spark.Compiler.CSharp.ChunkVisitors; using Spark.Compiler.NodeVisitors; using Spark.FileSystem; - using Spark.Parser.Markup; - public class ViewLoader + public class ViewLoader( + ISparkSettings settings, + IViewFolder viewFolder, + IPartialProvider partialProvider, + IPartialReferenceProvider partialReferenceProvider, + ISparkExtensionFactory extensionFactory, + ISparkSyntaxProvider syntaxProvider, + IBindingProvider bindingProvider) { - private readonly Dictionary entries = new Dictionary(); + private readonly Dictionary entries = new(); + private readonly List pending = new(); - private readonly List pending = new List(); + public string Prefix => settings.Prefix; - private IPartialProvider partialProvider; - private IPartialReferenceProvider partialReferenceProvider; + public bool ParseSectionTagAsSegment => settings.ParseSectionTagAsSegment; - public IViewFolder ViewFolder { get; set; } - - //public ParseAction> Parser { get; set; } - - public ISparkExtensionFactory ExtensionFactory { get; set; } - - public ISparkSyntaxProvider SyntaxProvider { get; set; } - - public string Prefix { get; set; } - - public bool ParseSectionTagAsSegment { get; set; } - - public AttributeBehaviour AttributeBehaviour { get; set; } - - public IBindingProvider BindingProvider { get; set; } - - public IPartialProvider PartialProvider - { - get - { - if (partialProvider == null) - { - partialProvider = new DefaultPartialProvider(); - }; - return partialProvider; - } - set { partialProvider = value; } - } - - public IPartialReferenceProvider PartialReferenceProvider - { - get - { - if (partialReferenceProvider == null) - { - partialReferenceProvider = new DefaultPartialReferenceProvider(() => PartialProvider); - }; - return partialReferenceProvider; - } - set { partialReferenceProvider = value; } - } + public AttributeBehaviour AttributeBehaviour => settings.AttributeBehaviour; /// /// Returns a value indicating whether this view loader is current. @@ -93,8 +59,7 @@ public virtual bool IsCurrent() { // The view is current if all entries' last modified value is the // same as when it was created. - return this.entries.All( - entry => entry.Value.ViewFile.LastModified == entry.Value.LastModified); + return this.entries.All(entry => entry.Value.ViewFile.LastModified == entry.Value.LastModified); } public IList Load(string viewPath) @@ -112,13 +77,13 @@ public IList Load(string viewPath) // import _global.spark files from template path and shared path var perFolderGlobal = Path.Combine(Path.GetDirectoryName(viewPath), Constants.GlobalSpark); - if (this.ViewFolder.HasView(perFolderGlobal)) + if (viewFolder.HasView(perFolderGlobal)) { this.BindEntry(perFolderGlobal); } var sharedGlobal = Path.Combine(Constants.Shared, Constants.GlobalSpark); - if (this.ViewFolder.HasView(sharedGlobal)) + if (viewFolder.HasView(sharedGlobal)) { this.BindEntry(sharedGlobal); } @@ -165,11 +130,11 @@ private IEnumerable PartialViewFolderPaths(string viewPath, bool allowCu { if (allowCustomReferencePath) { - return this.PartialReferenceProvider.GetPaths(viewPath, allowCustomReferencePath); + return partialReferenceProvider.GetPaths(viewPath, allowCustomReferencePath); } else { - return this.PartialProvider.GetPaths(viewPath); + return partialProvider.GetPaths(viewPath); } } @@ -209,7 +174,7 @@ private Entry BindEntry(string referencePath) return this.entries[referencePath]; } - var viewSource = this.ViewFolder.GetViewSource(referencePath); + var viewSource = viewFolder.GetViewSource(referencePath); var newEntry = new Entry { @@ -233,15 +198,16 @@ private void LoadInternal(string viewPath) var context = new VisitorContext { - ViewFolder = this.ViewFolder, + ViewFolder = viewFolder, Prefix = this.Prefix, - ExtensionFactory = this.ExtensionFactory, + ExtensionFactory = extensionFactory, PartialFileNames = this.FindPartialFiles(viewPath), Bindings = this.FindBindings(viewPath), ParseSectionTagAsSegment = this.ParseSectionTagAsSegment, AttributeBehaviour = this.AttributeBehaviour, }; - newEntry.Chunks = this.SyntaxProvider.GetChunks(context, viewPath); + + newEntry.Chunks = syntaxProvider.GetChunks(context, viewPath); var fileReferenceVisitor = new FileReferenceVisitor(); fileReferenceVisitor.Accept(newEntry.Chunks); @@ -269,7 +235,7 @@ private IEnumerable FindAllPartialFiles(IEnumerable folderPaths) { foreach (var folderPath in folderPaths.Distinct()) { - foreach (var view in this.ViewFolder.ListViews(folderPath)) + foreach (var view in viewFolder.ListViews(folderPath)) { var baseName = Path.GetFileNameWithoutExtension(view); if (baseName.StartsWith("_")) @@ -282,12 +248,12 @@ private IEnumerable FindAllPartialFiles(IEnumerable folderPaths) private IEnumerable FindBindings(string viewPath) { - if (this.BindingProvider == null) + if (bindingProvider == null) { - return new Binding[0]; + return Array.Empty(); } - return this.BindingProvider.GetBindings(new BindingRequest(this.ViewFolder) { ViewPath = viewPath }); + return bindingProvider.GetBindings(new BindingRequest(viewFolder) { ViewPath = viewPath }); } private string ResolveReference(string existingViewPath, string viewName) @@ -302,11 +268,11 @@ private string ResolveReference(string existingViewPath, string viewName) Path.Combine(x, viewNameWithShadeExtension) }); - var partialViewLocation = partialPaths.FirstOrDefault(x => this.ViewFolder.HasView(x)); + var partialViewLocation = partialPaths.FirstOrDefault(x => viewFolder.HasView(x)); if (partialViewLocation == null) { - var message = string.Format("Unable to locate {0} in {1}", viewName, string.Join(", ", partialPaths.ToArray())); + var message = $"Unable to locate {viewName} in {string.Join(", ", partialPaths.ToArray())}"; throw new FileNotFoundException(message, viewName); } @@ -319,37 +285,17 @@ private class Entry public string ViewPath { - get - { - return this.fileContext.ViewSourcePath; - } - - set - { - this.fileContext.ViewSourcePath = value; - } + get => this.fileContext.ViewSourcePath; + set => this.fileContext.ViewSourcePath = value; } public IList Chunks { - get - { - return this.fileContext.Contents; - } - - set - { - this.fileContext.Contents = value; - } + get => this.fileContext.Contents; + set => this.fileContext.Contents = value; } - public FileContext FileContext - { - get - { - return this.fileContext; - } - } + public FileContext FileContext => this.fileContext; public long LastModified { get; set; } diff --git a/src/Spark/Spark.csproj b/src/Spark/Spark.csproj index 6fe83070..7831a61b 100644 --- a/src/Spark/Spark.csproj +++ b/src/Spark/Spark.csproj @@ -26,6 +26,8 @@ + + diff --git a/src/Spark/SparkServiceContainer.cs b/src/Spark/SparkServiceContainer.cs deleted file mode 100644 index b6109299..00000000 --- a/src/Spark/SparkServiceContainer.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.Collections.Generic; -using System.Configuration; -using Spark.Bindings; -using Spark.FileSystem; -using Spark.Parser.Syntax; - -namespace Spark -{ - public class SparkServiceContainer : ISparkServiceContainer - { - internal static string MissingSparkSettingsConfigurationErrorExceptionMessage - = "Spark setting not configured. Missing spark section app configuration or no ISparkSetting instance registered in IoC container."; - - public SparkServiceContainer() - { - } - - public SparkServiceContainer(ISparkSettings settings) - { - _services[typeof(ISparkSettings)] = settings; - } - - readonly Dictionary _services = new Dictionary(); - - private readonly Dictionary> _defaults = - new Dictionary> - { - { - typeof(ISparkSettings), - c => ConfigurationManager.GetSection("spark") ?? - c.GetService() ?? - throw new ConfigurationErrorsException(MissingSparkSettingsConfigurationErrorExceptionMessage) - }, - { typeof(ISparkViewEngine), c => new SparkViewEngine(c.GetService()) }, - { typeof(ISparkLanguageFactory), c => new DefaultLanguageFactory() }, - { typeof(ISparkSyntaxProvider), c => new DefaultSyntaxProvider(c.GetService()) }, - { typeof(IViewActivatorFactory), c => new DefaultViewActivator() }, - { typeof(IResourcePathManager), c => new DefaultResourcePathManager(c.GetService()) }, - { typeof(ITemplateLocator), c => new DefaultTemplateLocator() }, - { typeof(IBindingProvider), c => new DefaultBindingProvider() }, - { typeof(IViewFolder), c => c.GetService().CreateDefaultViewFolder() }, - { typeof(ICompiledViewHolder), c => new CompiledViewHolder() }, - { typeof(IPartialProvider), c => new DefaultPartialProvider() }, - { typeof(IPartialReferenceProvider), c => new DefaultPartialReferenceProvider(c.GetService()) }, - }; - - - public T GetService() - { - return (T)GetService(typeof(T)); - } - - // This implementation throws stack overflow exceptions when a type is not found - public object GetService(Type serviceType) - { - lock (_services) - { - object service; - if (_services.TryGetValue(serviceType, out service)) - return service; - - Func serviceBuilder; - if (_defaults.TryGetValue(serviceType, out serviceBuilder)) - { - service = serviceBuilder(this); - _services.Add(serviceType, service); - if (service is ISparkServiceInitialize) - ((ISparkServiceInitialize)service).Initialize(this); - return service; - } - return null; - } - } - - public void SetService(TService service) - { - SetService(typeof(TService), service); - } - - public void SetService(Type serviceType, object service) - { - if (_services.ContainsKey(serviceType)) - throw new ApplicationException($"A service of type {serviceType} has already been created"); - if (!serviceType.IsInterface) - throw new ApplicationException($"Only an interface may be used as service type. {serviceType}"); - - lock (_services) - { - _services[serviceType] = service; - if (service is ISparkServiceInitialize) - ((ISparkServiceInitialize)service).Initialize(this); - } - } - - public void SetServiceBuilder(Func serviceBuilder) - { - SetServiceBuilder(typeof(TService), serviceBuilder); - } - - public void SetServiceBuilder(Type serviceType, Func serviceBuilder) - { - if (_services.ContainsKey(serviceType)) - throw new ApplicationException($"A service of type {serviceType} has already been created"); - if (!serviceType.IsInterface) - throw new ApplicationException($"Only an interface may be used as service type. {serviceType}"); - - lock (_services) - { - _defaults[serviceType] = serviceBuilder; - } - } - } -} diff --git a/src/Spark/SparkViewEngine.cs b/src/Spark/SparkViewEngine.cs index 1c81403f..eb005f93 100644 --- a/src/Spark/SparkViewEngine.cs +++ b/src/Spark/SparkViewEngine.cs @@ -15,119 +15,70 @@ using System; using System.Linq; using System.Collections.Generic; -using System.Configuration; using System.Reflection; using Spark.Bindings; using Spark.Compiler; using Spark.Compiler.CSharp; using Spark.Parser; using Spark.FileSystem; -using Spark.Parser.Syntax; namespace Spark { - public class SparkViewEngine : ISparkViewEngine, ISparkServiceInitialize + public class SparkViewEngine : ISparkViewEngine { - public SparkViewEngine() - : this(null) + public ISparkSettings Settings { get; } + public IViewFolder ViewFolder { get; set; } + public readonly ISparkLanguageFactory LanguageFactory; + public IViewActivatorFactory ViewActivatorFactory { get; } + public readonly ICompiledViewHolder CompiledViewHolder; + + public ISparkSyntaxProvider SyntaxProvider { get; } + + private readonly IBatchCompiler BatchCompiler; + private readonly IPartialProvider PartialProvider; + private readonly IPartialReferenceProvider PartialReferenceProvider; + private readonly IBindingProvider BindingProvider; + private readonly ISparkExtensionFactory SparkExtensionFactory; + + public SparkViewEngine( + ISparkSettings settings, + ISparkSyntaxProvider syntaxProvider, + IViewActivatorFactory viewActivatorFactory, + ISparkLanguageFactory languageFactory, + ICompiledViewHolder compiledViewHolder, + IViewFolder viewFolder, + IBatchCompiler batchCompiler, + IPartialProvider partialProvider, + IPartialReferenceProvider partialReferenceProvider, + IBindingProvider bindingProvider, + ISparkExtensionFactory sparkExtensionFactory) { - } - - public SparkViewEngine(ISparkSettings settings) - { - Settings = settings ?? - (ISparkSettings)ConfigurationManager.GetSection("spark") ?? - throw new ConfigurationErrorsException(SparkServiceContainer.MissingSparkSettingsConfigurationErrorExceptionMessage); - SyntaxProvider = new DefaultSyntaxProvider(Settings); - ViewActivatorFactory = new DefaultViewActivator(); - } - - public void Initialize(ISparkServiceContainer container) - { - Settings = container.GetService(); - SyntaxProvider = container.GetService(); - ViewActivatorFactory = container.GetService(); - LanguageFactory = container.GetService(); - BindingProvider = container.GetService(); - ResourcePathManager = container.GetService(); - TemplateLocator = container.GetService(); - CompiledViewHolder = container.GetService(); - PartialProvider = container.GetService(); - PartialReferenceProvider = container.GetService(); - SetViewFolder(container.GetService()); - } - - private IViewFolder _viewFolder; - public IViewFolder ViewFolder - { - get - { - if (_viewFolder == null) - { - SetViewFolder(Settings.CreateDefaultViewFolder()); - } - - return _viewFolder; - } - set { SetViewFolder(value); } - } - - private ISparkLanguageFactory _langaugeFactory; - public ISparkLanguageFactory LanguageFactory - { - get - { - if (_langaugeFactory == null) - _langaugeFactory = new DefaultLanguageFactory(); - return _langaugeFactory; - } - set { _langaugeFactory = value; } - } + Settings = settings; + SyntaxProvider = syntaxProvider; + ViewActivatorFactory = viewActivatorFactory; + LanguageFactory = languageFactory; + + CompiledViewHolder = compiledViewHolder; + + ViewFolder = this.InitialiseAggregateViewFolder(settings, viewFolder); - private IBindingProvider _bindingProvider; - public IBindingProvider BindingProvider - { - get - { - if (_bindingProvider == null) - _bindingProvider = new DefaultBindingProvider(); - return _bindingProvider; - } - set { _bindingProvider = value; } + BatchCompiler = batchCompiler; + PartialProvider = partialProvider; + PartialReferenceProvider = partialReferenceProvider; + BindingProvider = bindingProvider; + SparkExtensionFactory = sparkExtensionFactory; } - private IPartialProvider _partialProvider; - public IPartialProvider PartialProvider - { - get - { - if (_partialProvider == null) - _partialProvider = new DefaultPartialProvider(); - return _partialProvider; - } - set { _partialProvider = value; } - } + public string DefaultPageBaseType => Settings.PageBaseType; - private IPartialReferenceProvider _partialReferenceProvider; - public IPartialReferenceProvider PartialReferenceProvider - { - get - { - if (_partialReferenceProvider == null) - _partialReferenceProvider = new DefaultPartialReferenceProvider(() => PartialProvider); - return _partialReferenceProvider; - } - set { _partialReferenceProvider = value; } - } - - private void SetViewFolder(IViewFolder value) + private IViewFolder InitialiseAggregateViewFolder(ISparkSettings settings, IViewFolder value) { var aggregateViewFolder = value; - if (this.Settings.ViewFolders != null) + if (settings.ViewFolders != null) { - foreach (var viewFolderSettings in this.Settings.ViewFolders) + foreach (var viewFolderSettings in settings.ViewFolders) { IViewFolder viewFolder = this.ActivateViewFolder(viewFolderSettings); @@ -140,7 +91,7 @@ private void SetViewFolder(IViewFolder value) } } - _viewFolder = aggregateViewFolder; + return aggregateViewFolder; } private IViewFolder ActivateViewFolder(IViewFolderSettings viewFolderSettings) @@ -165,7 +116,7 @@ private IViewFolder ActivateViewFolder(IViewFolderSettings viewFolderSettings) } var args = bestConstructor.GetParameters() - .Select(param => ChangeType(viewFolderSettings, param)) + .Select(param => this.ChangeType(viewFolderSettings, param)) .ToArray(); return (IViewFolder)Activator.CreateInstance(type, args); @@ -174,55 +125,13 @@ private IViewFolder ActivateViewFolder(IViewFolderSettings viewFolderSettings) private object ChangeType(IViewFolderSettings viewFolderSettings, ParameterInfo param) { if (param.ParameterType == typeof(Assembly)) - return Assembly.Load(viewFolderSettings.Parameters[param.Name]); - - return Convert.ChangeType(viewFolderSettings.Parameters[param.Name], param.ParameterType); - } - - private IResourcePathManager _resourcePathManager; - public IResourcePathManager ResourcePathManager - { - get { - if (_resourcePathManager == null) - _resourcePathManager = new DefaultResourcePathManager(Settings); - return _resourcePathManager; - } - set { _resourcePathManager = value; } - } - - public ISparkExtensionFactory ExtensionFactory { get; set; } - public IViewActivatorFactory ViewActivatorFactory { get; set; } - - private ITemplateLocator _templateLocator; - public ITemplateLocator TemplateLocator - { - get - { - if (_templateLocator == null) - _templateLocator = new DefaultTemplateLocator(); - return _templateLocator; + return Assembly.Load(viewFolderSettings.Parameters[param.Name]); } - set { _templateLocator = value; } - } - private ICompiledViewHolder _compiledViewHolder; - public ICompiledViewHolder CompiledViewHolder - { - get - { - if (_compiledViewHolder == null) - _compiledViewHolder = new CompiledViewHolder(); - return _compiledViewHolder; - } - set { _compiledViewHolder = value; } + return Convert.ChangeType(viewFolderSettings.Parameters[param.Name], param.ParameterType); } - public ISparkSyntaxProvider SyntaxProvider { get; set; } - - public ISparkSettings Settings { get; set; } - public string DefaultPageBaseType { get; set; } - public ISparkViewEntry GetEntry(SparkViewDescriptor descriptor) { return CompiledViewHolder.Lookup(descriptor); @@ -235,13 +144,15 @@ public ISparkView CreateInstance(SparkViewDescriptor descriptor) public void ReleaseInstance(ISparkView view) { - if (view == null) throw new ArgumentNullException("view"); + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } var entry = CompiledViewHolder.Lookup(view.GeneratedViewId); - if (entry != null) - entry.ReleaseInstance(view); - } + entry?.ReleaseInstance(view); + } public ISparkViewEntry CreateEntry(SparkViewDescriptor descriptor) { @@ -301,25 +212,25 @@ void LoadTemplates(ViewLoader loader, IEnumerable templates, ICollection } } - private ViewLoader CreateViewLoader() + public Assembly BatchCompilation(IList descriptors) { - return new ViewLoader - { - ViewFolder = ViewFolder, - SyntaxProvider = SyntaxProvider, - ExtensionFactory = ExtensionFactory, - Prefix = Settings.Prefix, - BindingProvider = BindingProvider, - ParseSectionTagAsSegment = Settings.ParseSectionTagAsSegment, - AttributeBehaviour = Settings.AttributeBehaviour, - PartialProvider = PartialProvider, - PartialReferenceProvider = PartialReferenceProvider - }; + return BatchCompilation(null /*outputAssembly*/, descriptors); } - public Assembly BatchCompilation(IList descriptors) + /// + /// ViewLoader must be transient (due to its dictionary and list). + /// + /// + private ViewLoader CreateViewLoader() { - return BatchCompilation(null /*outputAssembly*/, descriptors); + return new ViewLoader( + this.Settings, + this.ViewFolder, + this.PartialProvider, + this.PartialReferenceProvider, + this.SparkExtensionFactory, + this.SyntaxProvider, + this.BindingProvider); } public Assembly BatchCompilation(string outputAssembly, IList descriptors) @@ -332,7 +243,7 @@ public Assembly BatchCompilation(string outputAssembly, IList LoadBatchCompilation(Assembly assembly) var descriptor = ((SparkViewAttribute)attributes[0]).BuildDescriptor(); var entry = new CompiledViewEntry - { - Descriptor = descriptor, - Loader = new ViewLoader { PartialProvider = PartialProvider, PartialReferenceProvider = PartialReferenceProvider }, - Compiler = new CSharpViewCompiler { CompiledType = type }, - Activator = ViewActivatorFactory.Register(type) - }; + { + Descriptor = descriptor, + Loader = this.CreateViewLoader(), + Compiler = new CSharpViewCompiler(this.BatchCompiler) { CompiledType = type }, + Activator = ViewActivatorFactory.Register(type) + }; + CompiledViewHolder.Store(entry); descriptors.Add(descriptor); diff --git a/src/Xpark/Program.cs b/src/Xpark/Program.cs index 0ddac8e0..47611459 100644 --- a/src/Xpark/Program.cs +++ b/src/Xpark/Program.cs @@ -1,12 +1,14 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; using Spark; +using Spark.Bindings; +using Spark.Compiler.Roslyn; using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; namespace Xpark { @@ -41,29 +43,45 @@ The Model in the template is an XDocument loaded from the source. var templateName = Path.GetFileName(templatePath); var templateDirPath = Path.GetDirectoryName(templatePath); + // Look for views in the root and shared location var viewFolder = new FileSystemViewFolder(templateDirPath); + viewFolder.Append(new SubViewFolder(viewFolder, "Shared")); - // Create an engine using the templates path as the root location - // as well as the shared location - var engine = new SparkViewEngine(new SparkSettings()) - { - DefaultPageBaseType = typeof(SparkView).FullName, - ViewFolder = viewFolder.Append(new SubViewFolder(viewFolder, "Shared")) - }; + var settings = new SparkSettings() + .SetPageBaseType(typeof(SparkView)); - SparkView view; - // compile and instantiate the template - view = (SparkView)engine.CreateInstance( - new SparkViewDescriptor() - .AddTemplate(templateName)); + var partialProvider = new DefaultPartialProvider(); + + var batchCompiler = new RoslynBatchCompiler(); + var engine = new SparkViewEngine( + settings, + new DefaultSyntaxProvider(settings), + new DefaultViewActivator(), + new DefaultLanguageFactory(batchCompiler), + new CompiledViewHolder(), + viewFolder, + batchCompiler, + partialProvider, + new DefaultPartialReferenceProvider(partialProvider), + new DefaultBindingProvider(), + null); + + // compile and instantiate the template + var view = + (SparkView)engine.CreateInstance( + new SparkViewDescriptor() + .AddTemplate(templateName)); // load the second argument, or default to reading stdin if (args.Length >= 2) + { view.Model = XDocument.Load(args[1]); + } else + { view.Model = XDocument.Load(XmlReader.Create(Console.OpenStandardInput())); - + } // write out to the third argument, or default to writing stdout if (args.Length >= 3) From 67c0bcfdd417bb15ed832310b264863b0c4d68fe Mon Sep 17 00:00:00 2001 From: bounav Date: Wed, 24 Jan 2024 09:21:33 +0000 Subject: [PATCH 02/14] Added package reference to Microsoft.NET.Test.Sdk in test projects --- .../Castle.MonoRail.Views.Spark.Tests.csproj | 1 + src/Spark.Python.Tests/Spark.Python.Tests.csproj | 3 ++- src/Spark.Ruby.Tests/Spark.Ruby.Tests.csproj | 3 ++- src/Spark.Tests/Spark.Tests.csproj | 3 ++- src/Spark.Web.Mvc.Pdf.Tests/Spark.Web.Mvc.Pdf.Tests.csproj | 1 + src/Spark.Web.Mvc.Ruby.Tests/Spark.Web.Mvc.Ruby.Tests.csproj | 1 + src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj | 3 ++- src/Spark.Web.Tests/Spark.Web.Tests.csproj | 1 + 8 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Castle.MonoRail.Views.Spark.Tests/Castle.MonoRail.Views.Spark.Tests.csproj b/src/Castle.MonoRail.Views.Spark.Tests/Castle.MonoRail.Views.Spark.Tests.csproj index 2ac78745..779bb8fc 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/Castle.MonoRail.Views.Spark.Tests.csproj +++ b/src/Castle.MonoRail.Views.Spark.Tests/Castle.MonoRail.Views.Spark.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Spark.Python.Tests/Spark.Python.Tests.csproj b/src/Spark.Python.Tests/Spark.Python.Tests.csproj index dabe7593..d05dcf03 100644 --- a/src/Spark.Python.Tests/Spark.Python.Tests.csproj +++ b/src/Spark.Python.Tests/Spark.Python.Tests.csproj @@ -20,7 +20,8 @@ - + + diff --git a/src/Spark.Ruby.Tests/Spark.Ruby.Tests.csproj b/src/Spark.Ruby.Tests/Spark.Ruby.Tests.csproj index 04459c71..7199cc3b 100644 --- a/src/Spark.Ruby.Tests/Spark.Ruby.Tests.csproj +++ b/src/Spark.Ruby.Tests/Spark.Ruby.Tests.csproj @@ -24,7 +24,8 @@ - + + diff --git a/src/Spark.Tests/Spark.Tests.csproj b/src/Spark.Tests/Spark.Tests.csproj index 6c2549ee..045b38d3 100644 --- a/src/Spark.Tests/Spark.Tests.csproj +++ b/src/Spark.Tests/Spark.Tests.csproj @@ -15,7 +15,8 @@ - + + diff --git a/src/Spark.Web.Mvc.Pdf.Tests/Spark.Web.Mvc.Pdf.Tests.csproj b/src/Spark.Web.Mvc.Pdf.Tests/Spark.Web.Mvc.Pdf.Tests.csproj index 5adfdc1a..33f50209 100644 --- a/src/Spark.Web.Mvc.Pdf.Tests/Spark.Web.Mvc.Pdf.Tests.csproj +++ b/src/Spark.Web.Mvc.Pdf.Tests/Spark.Web.Mvc.Pdf.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Spark.Web.Mvc.Ruby.Tests/Spark.Web.Mvc.Ruby.Tests.csproj b/src/Spark.Web.Mvc.Ruby.Tests/Spark.Web.Mvc.Ruby.Tests.csproj index 23253f1a..ec9c043a 100644 --- a/src/Spark.Web.Mvc.Ruby.Tests/Spark.Web.Mvc.Ruby.Tests.csproj +++ b/src/Spark.Web.Mvc.Ruby.Tests/Spark.Web.Mvc.Ruby.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj index 5671e1c3..7988669e 100644 --- a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj +++ b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj @@ -11,7 +11,8 @@ - + + diff --git a/src/Spark.Web.Tests/Spark.Web.Tests.csproj b/src/Spark.Web.Tests/Spark.Web.Tests.csproj index d71bda42..ee28d7ee 100644 --- a/src/Spark.Web.Tests/Spark.Web.Tests.csproj +++ b/src/Spark.Web.Tests/Spark.Web.Tests.csproj @@ -17,6 +17,7 @@ + From 0ef3206f6915fe73445372294ce05a5a34e557b0 Mon Sep 17 00:00:00 2001 From: bounav Date: Fri, 26 Jan 2024 13:49:02 +0000 Subject: [PATCH 03/14] RoslynBatchCompiler can now generate files --- src/Spark.Web.Mvc/SparkViewFactory.cs | 1 - src/Spark/Compiler/Roslyn/CSharpLink.cs | 57 +++++++++++++------ .../Compiler/Roslyn/IRoslynCompilationLink.cs | 16 +++++- .../Compiler/Roslyn/RoslynBatchCompiler.cs | 27 +++++---- src/Spark/Compiler/Roslyn/VisualBasicLink.cs | 36 ++++++------ src/Spark/SparkBatchDescriptor.cs | 7 +-- 6 files changed, 94 insertions(+), 50 deletions(-) diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 700d5911..d48fcb65 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -202,7 +202,6 @@ public SparkViewDescriptor CreateDescriptor( return descriptor; } - public Assembly Precompile(SparkBatchDescriptor batch) { return Engine.BatchCompilation(batch.OutputAssembly, CreateDescriptors(batch)); diff --git a/src/Spark/Compiler/Roslyn/CSharpLink.cs b/src/Spark/Compiler/Roslyn/CSharpLink.cs index 42513c63..7857d9c6 100644 --- a/src/Spark/Compiler/Roslyn/CSharpLink.cs +++ b/src/Spark/Compiler/Roslyn/CSharpLink.cs @@ -5,6 +5,7 @@ using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; namespace Spark.Compiler.Roslyn; @@ -12,7 +13,28 @@ public class CSharpLink : IRoslynCompilationLink { public bool ShouldVisit(string languageOrExtension) => languageOrExtension is "c#" or "cs" or "csharp"; - public Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode) + public static void ThrowIfCompilationNotSuccessful(EmitResult result) + { + if (result.Success) + { + return; + } + + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + var sb = new StringBuilder(); + + foreach (var diagnostic in failures) + { + sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); + } + + throw new RoslynCompilerException(sb.ToString(), result); + } + + public Assembly Compile(bool debug, string assemblyName, string outputAssembly, IEnumerable references, IEnumerable sourceCode) { var syntaxTrees = sourceCode.Select(source => CSharpSyntaxTree.ParseText(source)); @@ -28,29 +50,32 @@ public Assembly Compile(bool debug, string assemblyName, List references: references, options: options); - using var ms = new MemoryStream(); - - var result = compilation.Emit(ms); + EmitResult result; + Assembly assembly = null; - if (!result.Success) + if (string.IsNullOrEmpty(outputAssembly)) { - var failures = result.Diagnostics.Where(diagnostic => - diagnostic.IsWarningAsError || - diagnostic.Severity == DiagnosticSeverity.Error); + using var ms = new MemoryStream(); + + result = compilation.Emit(ms); - var sb = new StringBuilder(); + ThrowIfCompilationNotSuccessful(result); - foreach (var diagnostic in failures) - { - sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); - } + ms.Seek(0, SeekOrigin.Begin); - throw new RoslynCompilerException(sb.ToString(), result); + assembly = Assembly.Load(ms.ToArray()); } + else + { + using (var fs = new FileStream(outputAssembly, FileMode.Create)) + { + result = compilation.Emit(fs); + } - ms.Seek(0, SeekOrigin.Begin); + ThrowIfCompilationNotSuccessful(result); - var assembly = Assembly.Load(ms.ToArray()); + assembly = Assembly.Load(outputAssembly); + } return assembly; } diff --git a/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs b/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs index 1180b7ee..b2a188c4 100644 --- a/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs +++ b/src/Spark/Compiler/Roslyn/IRoslynCompilationLink.cs @@ -6,7 +6,21 @@ namespace Spark.Compiler.Roslyn; public interface IRoslynCompilationLink { + /// + /// When true, only the method for this link should be executed. + /// + /// + /// bool ShouldVisit(string languageOrExtension); - Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode); + /// + /// Compiles the specified . + /// + /// + /// + /// When set the source code is compiled to a file. + /// + /// + /// + Assembly Compile(bool debug, string assemblyName, string outputAssembly, IEnumerable references, IEnumerable sourceCode); } \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs b/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs index 28678ff9..c40bbf24 100644 --- a/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs +++ b/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs @@ -39,19 +39,24 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss throw new ConfigurationErrorsException("No IRoslynCompilationLink links"); } - string assemblyName; - if (!string.IsNullOrEmpty(outputAssembly)) - { - var basePath = Path.GetTempPath(); - assemblyName = Path.Combine(basePath, outputAssembly); - } - else - { - assemblyName = Path.GetRandomFileName(); - } + var assemblyName = !string.IsNullOrEmpty(outputAssembly) + // Strips the path from the outputAssembly full path... + ? Path.GetFileName(outputAssembly) + // ... or generates a random assembly name + : Path.GetRandomFileName(); + + assemblyName = Path.GetFileNameWithoutExtension(assemblyName); var references = new List(); + // This won't work when targeting .net core + // https://github.com/jaredpar/basic-reference-assemblies/ + var systemCoreAssembly = typeof(System.Linq.Enumerable).Assembly; + var systemCorePath = systemCoreAssembly.Location; + MetadataReference systemCoreRef = AssemblyMetadata.CreateFromFile(systemCorePath).GetReference(); + + references.Add(systemCoreRef); + foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) { if (currentAssembly.IsDynamic()) @@ -71,7 +76,7 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss { match = true; - assembly = visitor.Compile(debug, assemblyName, references, sourceCode); + assembly = visitor.Compile(debug, assemblyName, outputAssembly, references, sourceCode); // Chain of responsibility pattern break; diff --git a/src/Spark/Compiler/Roslyn/VisualBasicLink.cs b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs index eebbae0c..4c484eb2 100644 --- a/src/Spark/Compiler/Roslyn/VisualBasicLink.cs +++ b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs @@ -2,8 +2,8 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.VisualBasic; namespace Spark.Compiler.Roslyn; @@ -12,7 +12,7 @@ public class VisualBasicLink : IRoslynCompilationLink { public bool ShouldVisit(string languageOrExtension) => languageOrExtension is "vb" or "vbs" or "visualbasic" or "vbscript"; - public Assembly Compile(bool debug, string assemblyName, List references, IEnumerable sourceCode) + public Assembly Compile(bool debug, string assemblyName, string outputAssembly, IEnumerable references, IEnumerable sourceCode) { var syntaxTrees = sourceCode.Select(source => VisualBasicSyntaxTree.ParseText(source)); @@ -28,29 +28,31 @@ public Assembly Compile(bool debug, string assemblyName, List references: references, options: options); - using var ms = new MemoryStream(); + EmitResult result; + Assembly assembly = null; - var result = compilation.Emit(ms); - - if (!result.Success) + if (string.IsNullOrEmpty(outputAssembly)) { - var failures = result.Diagnostics.Where(diagnostic => - diagnostic.IsWarningAsError || - diagnostic.Severity == DiagnosticSeverity.Error); + using var ms = new MemoryStream(); + + result = compilation.Emit(ms); - var sb = new StringBuilder(); + CSharpLink.ThrowIfCompilationNotSuccessful(result); - foreach (var diagnostic in failures) - { - sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); - } + ms.Seek(0, SeekOrigin.Begin); - throw new RoslynCompilerException(sb.ToString(), result); + assembly = Assembly.Load(ms.ToArray()); } + else + { + using var fs = new FileStream(outputAssembly, FileMode.Create); - ms.Seek(0, SeekOrigin.Begin); + result = compilation.Emit(fs); - var assembly = Assembly.Load(ms.ToArray()); + CSharpLink.ThrowIfCompilationNotSuccessful(result); + + assembly = Assembly.Load(outputAssembly); + } return assembly; } diff --git a/src/Spark/SparkBatchDescriptor.cs b/src/Spark/SparkBatchDescriptor.cs index 0a0126bc..aca32696 100644 --- a/src/Spark/SparkBatchDescriptor.cs +++ b/src/Spark/SparkBatchDescriptor.cs @@ -20,14 +20,13 @@ namespace Spark { public class SparkBatchDescriptor { - public SparkBatchDescriptor() - : this(null /*assemblyName*/) + public SparkBatchDescriptor() : this(null /*outputAssemblyName*/) { } - public SparkBatchDescriptor(string assemblyName) + public SparkBatchDescriptor(string outputAssemblyName) { - OutputAssembly = assemblyName; + OutputAssembly = outputAssemblyName; Entries = new List(); } From b0349d710ba8dd184d81460d757bc871a5d96472 Mon Sep 17 00:00:00 2001 From: bounav Date: Mon, 29 Jan 2024 17:00:51 +0000 Subject: [PATCH 04/14] No depedency to System.CodeDom when targetting .net 8.0 - CodeDom complilation cannot target .net core - BatchCompiler.cs contains the code to complile with codedom and/or roslyn --- .../SparkViewFactory.cs | 3 - src/Spark.JsTests/Generate.ashx.cs | 3 +- .../Compiler/PythonViewCompiler.cs | 1 - src/Spark.Ruby/Compiler/RubyViewCompiler.cs | 1 - .../HtmlHelperExtensionsTester.cs | 2 +- .../SparkBatchCompilerTester.cs | 1 - .../DynamicViewDataDictionary.cs | 36 +- .../Extensions/ServiceCollectionExtensions.cs | 1 - src/Spark.Web.Mvc/SparkView.cs | 31 +- src/Spark.Web.Mvc/SparkViewFactory.cs | 10 +- .../Compiler/CSharpViewCompilerTester.cs | 2 - .../Compiler/VisualBasicViewCompilerTester.cs | 2 - src/Spark/Compiler/BatchCompiler.cs | 373 ++++++++++++++++++ .../Compiler/CodeDom/CodeDomBatchCompiler.cs | 185 --------- .../CodeDom/CodeDomCompilerException.cs | 9 - src/Spark/Compiler/Roslyn/CSharpLink.cs | 9 +- .../Compiler/Roslyn/RoslynBatchCompiler.cs | 94 ----- .../Roslyn/RoslynCompilerException.cs | 9 - .../VisualBasic/VisualBasicViewCompiler.cs | 1 - src/Spark/Spark.csproj | 2 +- src/Xpark/Program.cs | 3 +- 21 files changed, 420 insertions(+), 358 deletions(-) create mode 100644 src/Spark/Compiler/BatchCompiler.cs delete mode 100644 src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs delete mode 100644 src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs delete mode 100644 src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs delete mode 100644 src/Spark/Compiler/Roslyn/RoslynCompilerException.cs diff --git a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs index 6ffb7099..991d3672 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs @@ -28,9 +28,6 @@ using Castle.MonoRail.Views.Spark.Wrappers; using Spark.Bindings; using Spark.Compiler; -using Spark.Compiler.CodeDom; -using Spark.Compiler.Roslyn; -using Spark.Parser; using Spark.Parser.Syntax; namespace Castle.MonoRail.Views.Spark diff --git a/src/Spark.JsTests/Generate.ashx.cs b/src/Spark.JsTests/Generate.ashx.cs index 299b1bd4..5096a44e 100644 --- a/src/Spark.JsTests/Generate.ashx.cs +++ b/src/Spark.JsTests/Generate.ashx.cs @@ -1,3 +1,4 @@ + // Copyright 2008-2009 Louis DeJardin - http://whereslou.com // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +18,7 @@ using System.Web; using System.Web.Services; using Spark.Bindings; -using Spark.Compiler.Roslyn; +using Spark.Compiler; using Spark.FileSystem; using Spark.Parser; using Spark.Parser.Syntax; diff --git a/src/Spark.Python/Compiler/PythonViewCompiler.cs b/src/Spark.Python/Compiler/PythonViewCompiler.cs index b60418f8..cfe9ff50 100644 --- a/src/Spark.Python/Compiler/PythonViewCompiler.cs +++ b/src/Spark.Python/Compiler/PythonViewCompiler.cs @@ -16,7 +16,6 @@ using System.Linq; using System.Text; using Spark.Compiler; -using Spark.Compiler.CodeDom; using Spark.Compiler.CSharp.ChunkVisitors; using GeneratedCodeVisitor=Spark.Python.Compiler.ChunkVisitors.GeneratedCodeVisitor; using GlobalFunctionsVisitor=Spark.Python.Compiler.ChunkVisitors.GlobalFunctionsVisitor; diff --git a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs index 72e210a7..643155bd 100644 --- a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs +++ b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs @@ -17,7 +17,6 @@ using System.Linq; using System.Text; using Spark.Compiler; -using Spark.Compiler.CodeDom; using Spark.Ruby.Compiler.ChunkVisitors; using BaseClassVisitor = Spark.Compiler.CSharp.ChunkVisitors.BaseClassVisitor; diff --git a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs index a4d3c1ea..42002f12 100644 --- a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs +++ b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs @@ -20,7 +20,7 @@ using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using Spark.Compiler.Roslyn; +using Spark.Compiler; using Spark.FileSystem; using Spark.Web.Mvc.Extensions; diff --git a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs index 0933dadc..6c9472a9 100644 --- a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs @@ -20,7 +20,6 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Spark.Compiler; -using Spark.Compiler.CodeDom; using Spark.FileSystem; using Spark.Web.Mvc.Extensions; using Spark.Web.Mvc.Tests.Controllers; diff --git a/src/Spark.Web.Mvc/DynamicViewDataDictionary.cs b/src/Spark.Web.Mvc/DynamicViewDataDictionary.cs index 7807e792..17626179 100644 --- a/src/Spark.Web.Mvc/DynamicViewDataDictionary.cs +++ b/src/Spark.Web.Mvc/DynamicViewDataDictionary.cs @@ -3,25 +3,25 @@ namespace Spark.Web.Mvc { - public class DynamicViewDataDictionary : DynamicObject - { - private readonly ViewDataDictionary _viewData; + public class DynamicViewDataDictionary : DynamicObject + { + private readonly ViewDataDictionary _viewData; - public DynamicViewDataDictionary( ViewDataDictionary viewData ) - { - _viewData = viewData; - } + public DynamicViewDataDictionary(ViewDataDictionary viewData) + { + _viewData = viewData; + } - public override bool TryGetMember( GetMemberBinder binder, out object result ) - { - result = _viewData[binder.Name]; - return true; - } + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = _viewData[binder.Name]; + return true; + } - public override bool TrySetMember( SetMemberBinder binder, object value ) - { - _viewData[binder.Name] = value; - return true; - } - } + public override bool TrySetMember(SetMemberBinder binder, object value) + { + _viewData[binder.Name] = value; + return true; + } + } } diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs index fcbd8052..6caa0205 100644 --- a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Spark.Bindings; using Spark.Compiler; -using Spark.Compiler.CodeDom; using Spark.Compiler.Roslyn; using Spark.FileSystem; using Spark.Parser; diff --git a/src/Spark.Web.Mvc/SparkView.cs b/src/Spark.Web.Mvc/SparkView.cs index 7b2a1f5f..b075dedc 100644 --- a/src/Spark.Web.Mvc/SparkView.cs +++ b/src/Spark.Web.Mvc/SparkView.cs @@ -25,7 +25,7 @@ public abstract class SparkView : SparkViewBase, IViewDataContainer, ITextWriter private string _siteRoot; private ViewDataDictionary _viewData; private ViewContext _viewContext; - private dynamic _viewBag; + private dynamic _viewBag; public TempDataDictionary TempData => ViewContext.TempData; @@ -52,19 +52,19 @@ public ViewDataDictionary ViewData set { SetViewData(value); } } - public dynamic ViewBag - { - get - { - if( _viewBag == null ) - SetViewBag( new DynamicViewDataDictionary(ViewData) ); - return _viewBag; - } - } + public dynamic ViewBag + { + get + { + if( _viewBag == null ) + SetViewBag(new DynamicViewDataDictionary(ViewData)); + return _viewBag; + } + } public ViewContext ViewContext { - get { return _viewContext; } + get => _viewContext; set { SetViewContext(value); } } @@ -73,10 +73,10 @@ protected virtual void SetViewData(ViewDataDictionary viewData) _viewData = viewData; } - protected virtual void SetViewBag( DynamicViewDataDictionary viewBag ) - { - _viewBag = viewBag; - } + protected virtual void SetViewBag(DynamicViewDataDictionary viewBag) + { + _viewBag = viewBag; + } protected virtual void SetViewContext(ViewContext viewContext) { @@ -135,7 +135,6 @@ public string SiteResource(string path) return ResourcePathManager.GetResourcePath(SiteRoot, path); } - public void Render(ViewContext viewContext, TextWriter writer) { var wrappedViewContext = new ViewContextWrapper(viewContext, this); diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index d48fcb65..8e1b98a2 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -336,17 +336,17 @@ private static string RemoveSuffix(string value, string suffix) ViewEngineResult IViewEngine.FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { - return FindPartialView(controllerContext, partialViewName, useCache); + return this.FindPartialView(controllerContext, partialViewName, useCache); } ViewEngineResult IViewEngine.FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { - return FindView(controllerContext, viewName, masterName, useCache); + return this.FindView(controllerContext, viewName, masterName, useCache); } void IViewEngine.ReleaseView(ControllerContext controllerContext, IView view) { - ReleaseView(controllerContext, view); + this.ReleaseView(controllerContext, view); } #endregion @@ -355,8 +355,8 @@ void IViewEngine.ReleaseView(ControllerContext controllerContext, IView view) IViewFolder IViewFolderContainer.ViewFolder { - get => Engine.ViewFolder; - set => Engine.ViewFolder = value; + get => this.Engine.ViewFolder; + set => this.Engine.ViewFolder = value; } #endregion diff --git a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs index acef2857..4a62a992 100644 --- a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs @@ -15,9 +15,7 @@ using System.Collections.Generic; using NUnit.Framework; -using Spark.Compiler.CodeDom; using Spark.Compiler.CSharp; -using Spark.Compiler.Roslyn; using Spark.Tests; using Spark.Tests.Models; using Spark.Tests.Stubs; diff --git a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs index 3d87f227..88a9ad76 100644 --- a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs @@ -16,8 +16,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using Spark.Compiler.CodeDom; -using Spark.Compiler.Roslyn; using Spark.Compiler.VisualBasic; using Spark.Tests; using Spark.Tests.Models; diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/BatchCompiler.cs new file mode 100644 index 00000000..0063def5 --- /dev/null +++ b/src/Spark/Compiler/BatchCompiler.cs @@ -0,0 +1,373 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CSharp; +using Spark.Compiler.Roslyn; + +namespace Spark.Compiler; + +#if NETFRAMEWORK + +[Obsolete("To be replaced with RoslynBatchCompiler (code dom cannot target .net 'core'")] +public class CodeDomBatchCompiler : IBatchCompiler +{ + /// + /// Compiles the in the specified . + /// + /// When true the source is compiled in debug mode. + /// E.g. "csharp" or "visualbasic" + /// E.g. "File.Name.dll" (optional) + /// The source code to compile. + /// + /// + /// + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + { + var language = languageOrExtension; + if (CodeDomProvider.IsDefinedLanguage(languageOrExtension) == false && + CodeDomProvider.IsDefinedExtension(languageOrExtension)) + { + language = CodeDomProvider.GetLanguageFromExtension(languageOrExtension); + } + + CodeDomProvider codeProvider; + CompilerParameters compilerParameters; + + if (ConfigurationManager.GetSection("system.codedom") != null) + { + var compilerInfo = CodeDomProvider.GetCompilerInfo(language); + codeProvider = compilerInfo.CreateProvider(); + compilerParameters = compilerInfo.CreateDefaultCompilerParameters(); + } + else + { + if (!language.Equals("c#", StringComparison.OrdinalIgnoreCase) && + !language.Equals("cs", StringComparison.OrdinalIgnoreCase) && + !language.Equals("csharp", StringComparison.OrdinalIgnoreCase)) + { + throw new CompilerException( + $"When running the {typeof(CodeDomBatchCompiler).FullName} in an AppDomain without a system.codedom config section only the csharp language is supported. This happens if you are precompiling your views."); + } + + var compilerVersion = GetCompilerVersion(); + + var providerOptions = new Dictionary { { "CompilerVersion", compilerVersion } }; + codeProvider = new CSharpCodeProvider(providerOptions); + compilerParameters = new CompilerParameters(); + } + + compilerParameters.TreatWarningsAsErrors = false; + var extension = codeProvider.FileExtension; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.IsDynamic()) + { + continue; + } + + compilerParameters.ReferencedAssemblies.Add(assembly.Location); + } + + CompilerResults compilerResults; + + // ReSharper disable once RedundantAssignment + var dynamicBase = string.Empty; + + dynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase; + + var basePath = !string.IsNullOrEmpty(dynamicBase) ? dynamicBase : Path.GetTempPath(); + compilerParameters.TempFiles = new TempFileCollection(basePath); //Without this, the generated code throws Access Denied exception with Impersonate mode on platforms like SharePoint + if (debug) + { + compilerParameters.IncludeDebugInformation = true; + + var baseFile = Path.Combine(basePath, Guid.NewGuid().ToString("n")); + + var codeFiles = new List(); + int fileCount = 0; + foreach (string sourceCodeItem in sourceCode) + { + ++fileCount; + var codeFile = baseFile + "-" + fileCount + "." + extension; + using (var stream = new FileStream(codeFile, FileMode.Create, FileAccess.Write)) + { + using (var writer = new StreamWriter(stream)) + { + writer.Write(sourceCodeItem); + } + } + codeFiles.Add(codeFile); + } + + if (!string.IsNullOrEmpty(outputAssembly)) + { + compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); + } + else + { + compilerParameters.OutputAssembly = baseFile + ".dll"; + } + compilerResults = codeProvider.CompileAssemblyFromFile(compilerParameters, codeFiles.ToArray()); + } + else + { + if (!string.IsNullOrEmpty(outputAssembly)) + { + compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); + } + else + { + // This should result in the assembly being loaded without keeping the file on disk + compilerParameters.GenerateInMemory = true; + } + + compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, sourceCode.ToArray()); + } + + if (compilerResults.Errors.HasErrors) + { + var sb = new StringBuilder().AppendLine("Dynamic view compilation failed."); + + foreach (CompilerError err in compilerResults.Errors) + { + sb.AppendFormat("{4}({0},{1}): {2} {3}: ", err.Line, err.Column, err.IsWarning ? "warning" : "error", err.ErrorNumber, err.FileName); + sb.AppendLine(err.ErrorText); + } + + sb.AppendLine(); + foreach (var sourceCodeItem in sourceCode) + { + using var reader = new StringReader(sourceCodeItem); + + for (int lineNumber = 1; ; ++lineNumber) + { + var line = reader.ReadLine(); + if (line == null) + { + break; + } + + sb.Append(lineNumber).Append(' ').AppendLine(line); + } + } + throw new CodeDomCompilerException(sb.ToString(), compilerResults); + } + + return compilerResults.CompiledAssembly; + } + + private static string GetCompilerVersion() + { + return "v4.0"; + } +} + +public class CodeDomCompilerException(string message, CompilerResults results) : CompilerException(message) +{ + public CompilerResults Results { get; set; } = results; +} + +#endif + +public class RoslynBatchCompiler : IBatchCompiler +{ + private readonly IEnumerable links; + + private readonly IList References; + + public RoslynBatchCompiler() : this(new IRoslynCompilationLink[] { new CSharpLink(), new VisualBasicLink() }) + { + } + + public RoslynBatchCompiler(IEnumerable links) + { + this.links = links; + this.References = new List(); + } + + public void AddAssemblies(params string[] dlls) + { + foreach (var dll in dlls) + { + this.AddAssembly(dll); + } + } + + public bool AddAssembly(string assemblyDll) + { + if (string.IsNullOrEmpty(assemblyDll)) + { + return false; + } + + var file = Path.GetFullPath(assemblyDll); + + if (!File.Exists(file)) + { + // check framework or dedicated runtime app folder + var path = Path.GetDirectoryName(typeof(object).Assembly.Location); + file = Path.Combine(path, assemblyDll); + if (!File.Exists(file)) + { + return false; + } + } + + if (this.References.Any(r => r.FilePath == file)) + { + return true; + } + + try + { + var reference = MetadataReference.CreateFromFile(file); + this.References.Add(reference); + } + catch + { + return false; + } + + return true; + } + + public bool AddAssembly(Type type) + { + try + { + if (this.References.Any(r => r.FilePath == type.Assembly.Location)) + return true; + + var systemReference = MetadataReference.CreateFromFile(type.Assembly.Location); + this.References.Add(systemReference); + } + catch + { + return false; + } + + return true; + } + + public void AddNetCoreDefaultReferences() + { + var rtPath = Path.GetDirectoryName(typeof(object).Assembly.Location) + Path.DirectorySeparatorChar; + + this.AddAssemblies( + rtPath + "System.Private.CoreLib.dll", + rtPath + "System.Runtime.dll", + rtPath + "System.Console.dll", + rtPath + "netstandard.dll", + + rtPath + "System.Text.RegularExpressions.dll", // IMPORTANT! + rtPath + "System.Linq.dll", + rtPath + "System.Linq.Expressions.dll", // IMPORTANT! + + rtPath + "System.IO.dll", + rtPath + "System.Net.Primitives.dll", + rtPath + "System.Net.Http.dll", + rtPath + "System.Private.Uri.dll", + rtPath + "System.Reflection.dll", + rtPath + "System.ComponentModel.Primitives.dll", + rtPath + "System.Globalization.dll", + rtPath + "System.Collections.Concurrent.dll", + rtPath + "System.Collections.NonGeneric.dll", + rtPath + "Microsoft.CSharp.dll"); + + // this library and CodeAnalysis libs + this.AddAssembly(typeof(RoslynBatchCompiler)); // Scripting Library + } + + public void AddNetFrameworkDefaultReferences() + { + this.AddAssembly("mscorlib.dll"); + this.AddAssembly("System.dll"); + this.AddAssembly("System.Core.dll"); + this.AddAssembly("Microsoft.CSharp.dll"); + this.AddAssembly("System.Net.Http.dll"); + } + + /// + /// Compiles the in the specified . + /// + /// When true the source is compiled in debug mode. + /// E.g. "csharp" or "visualbasic" + /// E.g. "File.Name.dll" (optional) + /// The source code to compile. + /// + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + { + Assembly assembly = null; + + if (!this.links.Any()) + { + throw new ConfigurationErrorsException("No IRoslynCompilationLink links"); + } + + var assemblyName = !string.IsNullOrEmpty(outputAssembly) + // Strips the path from the outputAssembly full path... + ? Path.GetFileName(outputAssembly) + // ... or generates a random assembly name + : Path.GetRandomFileName(); + + assemblyName = Path.GetFileNameWithoutExtension(assemblyName); + + // We need to add different references when we target .net framework or .net core + // https://github.com/jaredpar/basic-reference-assemblies/ + // https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-CSharp-Code-Compilation-Revisited-for-Roslyn#adding-references + +#if NETFRAMEWORK + this.AddNetFrameworkDefaultReferences(); +#else + this.AddNetCoreDefaultReferences(); +#endif + + this.AddAssembly(typeof(System.Drawing.Color)); + + foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (currentAssembly.IsDynamic()) + { + continue; + } + + var reference = MetadataReference.CreateFromFile(currentAssembly.Location); + + this.References.Add(reference); + } + + var match = false; + foreach (var visitor in this.links) + { + if (visitor.ShouldVisit(languageOrExtension)) + { + match = true; + + assembly = visitor.Compile(debug, assemblyName, outputAssembly, this.References, sourceCode); + + // Chain of responsibility pattern + break; + } + } + + if (!match) + { + throw new ArgumentOutOfRangeException(nameof(languageOrExtension), languageOrExtension, "Un-handled value"); + } + + return assembly; + } +} + +public class RoslynCompilerException(string message, EmitResult emitResult) : CompilerException(message) +{ + public EmitResult EmitResult { get; set; } = emitResult; +} \ No newline at end of file diff --git a/src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs b/src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs deleted file mode 100644 index cbca4fe5..00000000 --- a/src/Spark/Compiler/CodeDom/CodeDomBatchCompiler.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2008-2009 Louis DeJardin - http://whereslou.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using Microsoft.CSharp; - -namespace Spark.Compiler.CodeDom -{ - [Obsolete("To be replaced with RoslynBatchCompiler")] - public class CodeDomBatchCompiler : IBatchCompiler - { - /// - /// Compiles the in the specified . - /// - /// When true the source is compiled in debug mode. - /// E.g. "csharp" or "visualbasic" - /// E.g. "File.Name.dll" (optional) - /// The source code to compile. - /// - /// - /// - public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) - { - var language = languageOrExtension; - if (CodeDomProvider.IsDefinedLanguage(languageOrExtension) == false && - CodeDomProvider.IsDefinedExtension(languageOrExtension)) - { - language = CodeDomProvider.GetLanguageFromExtension(languageOrExtension); - } - - CodeDomProvider codeProvider; - CompilerParameters compilerParameters; - - if (ConfigurationManager.GetSection("system.codedom") != null) - { - var compilerInfo = CodeDomProvider.GetCompilerInfo(language); - codeProvider = compilerInfo.CreateProvider(); - compilerParameters = compilerInfo.CreateDefaultCompilerParameters(); - } - else - { - if (!language.Equals("c#", StringComparison.OrdinalIgnoreCase) && - !language.Equals("cs", StringComparison.OrdinalIgnoreCase) && - !language.Equals("csharp", StringComparison.OrdinalIgnoreCase)) - { - throw new CompilerException( - $"When running the {typeof(CodeDomBatchCompiler).FullName} in an AppDomain without a system.codedom config section only the csharp language is supported. This happens if you are precompiling your views."); - } - - var compilerVersion = GetCompilerVersion(); - - var providerOptions = new Dictionary { { "CompilerVersion", compilerVersion } }; - codeProvider = new CSharpCodeProvider(providerOptions); - compilerParameters = new CompilerParameters(); - } - - compilerParameters.TreatWarningsAsErrors = false; - var extension = codeProvider.FileExtension; - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - if (assembly.IsDynamic()) - { - continue; - } - - compilerParameters.ReferencedAssemblies.Add(assembly.Location); - } - - CompilerResults compilerResults; - - // ReSharper disable once RedundantAssignment - var dynamicBase = string.Empty; - -#if NETFRAMEWORK - dynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase; -#else - dynamicBase = AppDomain.CurrentDomain.DynamicDirectory; -#endif - - var basePath = !string.IsNullOrEmpty(dynamicBase) ? dynamicBase : Path.GetTempPath(); - compilerParameters.TempFiles = new TempFileCollection(basePath); //Without this, the generated code throws Access Denied exception with Impersonate mode on platforms like SharePoint - if (debug) - { - compilerParameters.IncludeDebugInformation = true; - - var baseFile = Path.Combine(basePath, Guid.NewGuid().ToString("n")); - - var codeFiles = new List(); - int fileCount = 0; - foreach (string sourceCodeItem in sourceCode) - { - ++fileCount; - var codeFile = baseFile + "-" + fileCount + "." + extension; - using (var stream = new FileStream(codeFile, FileMode.Create, FileAccess.Write)) - { - using (var writer = new StreamWriter(stream)) - { - writer.Write(sourceCodeItem); - } - } - codeFiles.Add(codeFile); - } - - if (!string.IsNullOrEmpty(outputAssembly)) - { - compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); - } - else - { - compilerParameters.OutputAssembly = baseFile + ".dll"; - } - compilerResults = codeProvider.CompileAssemblyFromFile(compilerParameters, codeFiles.ToArray()); - } - else - { - if (!string.IsNullOrEmpty(outputAssembly)) - { - compilerParameters.OutputAssembly = Path.Combine(basePath, outputAssembly); - } - else - { - // This should result in the assembly being loaded without keeping the file on disk - compilerParameters.GenerateInMemory = true; - } - - compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, sourceCode.ToArray()); - } - - if (compilerResults.Errors.HasErrors) - { - var sb = new StringBuilder().AppendLine("Dynamic view compilation failed."); - - foreach (CompilerError err in compilerResults.Errors) - { - sb.AppendFormat("{4}({0},{1}): {2} {3}: ", err.Line, err.Column, err.IsWarning ? "warning" : "error", err.ErrorNumber, err.FileName); - sb.AppendLine(err.ErrorText); - } - - sb.AppendLine(); - foreach (var sourceCodeItem in sourceCode) - { - using var reader = new StringReader(sourceCodeItem); - - for (int lineNumber = 1; ; ++lineNumber) - { - var line = reader.ReadLine(); - if (line == null) - { - break; - } - - sb.Append(lineNumber).Append(' ').AppendLine(line); - } - } - throw new CodeDomCompilerException(sb.ToString(), compilerResults); - } - - return compilerResults.CompiledAssembly; - } - - private static string GetCompilerVersion() - { - return "v4.0"; - } - } -} diff --git a/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs b/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs deleted file mode 100644 index a3b3eb3a..00000000 --- a/src/Spark/Compiler/CodeDom/CodeDomCompilerException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.CodeDom.Compiler; - -namespace Spark.Compiler.CodeDom -{ - public class CodeDomCompilerException(string message, CompilerResults results) : CompilerException(message) - { - public CompilerResults Results { get; set; } = results; - } -} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/CSharpLink.cs b/src/Spark/Compiler/Roslyn/CSharpLink.cs index 7857d9c6..04193d87 100644 --- a/src/Spark/Compiler/Roslyn/CSharpLink.cs +++ b/src/Spark/Compiler/Roslyn/CSharpLink.cs @@ -67,14 +67,13 @@ public Assembly Compile(bool debug, string assemblyName, string outputAssembly, } else { - using (var fs = new FileStream(outputAssembly, FileMode.Create)) - { - result = compilation.Emit(fs); - } + result = debug + ? compilation.Emit(outputAssembly, outputAssembly.Replace(".dll", ".pdb")) + : compilation.Emit(outputAssembly); ThrowIfCompilationNotSuccessful(result); - assembly = Assembly.Load(outputAssembly); + assembly = Assembly.LoadFile(outputAssembly); } return assembly; diff --git a/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs b/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs deleted file mode 100644 index c40bbf24..00000000 --- a/src/Spark/Compiler/Roslyn/RoslynBatchCompiler.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; - -namespace Spark.Compiler.Roslyn -{ - public class RoslynBatchCompiler : IBatchCompiler - { - private readonly IEnumerable links; - - public RoslynBatchCompiler() - { - this.links = new IRoslynCompilationLink[] { new CSharpLink(), new VisualBasicLink() }; - } - - public RoslynBatchCompiler(IEnumerable links) - { - this.links = links; - } - - /// - /// Compiles the in the specified . - /// - /// When true the source is compiled in debug mode. - /// E.g. "csharp" or "visualbasic" - /// E.g. "File.Name.dll" (optional) - /// The source code to compile. - /// - public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) - { - Assembly assembly = null; - - if (!this.links.Any()) - { - throw new ConfigurationErrorsException("No IRoslynCompilationLink links"); - } - - var assemblyName = !string.IsNullOrEmpty(outputAssembly) - // Strips the path from the outputAssembly full path... - ? Path.GetFileName(outputAssembly) - // ... or generates a random assembly name - : Path.GetRandomFileName(); - - assemblyName = Path.GetFileNameWithoutExtension(assemblyName); - - var references = new List(); - - // This won't work when targeting .net core - // https://github.com/jaredpar/basic-reference-assemblies/ - var systemCoreAssembly = typeof(System.Linq.Enumerable).Assembly; - var systemCorePath = systemCoreAssembly.Location; - MetadataReference systemCoreRef = AssemblyMetadata.CreateFromFile(systemCorePath).GetReference(); - - references.Add(systemCoreRef); - - foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) - { - if (currentAssembly.IsDynamic()) - { - continue; - } - - var reference = MetadataReference.CreateFromFile(currentAssembly.Location); - - references.Add(reference); - } - - var match = false; - foreach (var visitor in this.links) - { - if (visitor.ShouldVisit(languageOrExtension)) - { - match = true; - - assembly = visitor.Compile(debug, assemblyName, outputAssembly, references, sourceCode); - - // Chain of responsibility pattern - break; - } - } - - if (!match) - { - throw new ArgumentOutOfRangeException(nameof(languageOrExtension), languageOrExtension, "Un-handled value"); - } - - return assembly; - } - } -} \ No newline at end of file diff --git a/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs b/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs deleted file mode 100644 index 7314cb29..00000000 --- a/src/Spark/Compiler/Roslyn/RoslynCompilerException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.CodeAnalysis.Emit; - -namespace Spark.Compiler.Roslyn -{ - public class RoslynCompilerException(string message, EmitResult emitResult) : CompilerException(message) - { - public EmitResult EmitResult { get; set; } = emitResult; - } -} \ No newline at end of file diff --git a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs index 8b8e74b3..2504c855 100644 --- a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs +++ b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs @@ -16,7 +16,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Spark.Compiler.CodeDom; using Spark.Compiler.VisualBasic.ChunkVisitors; namespace Spark.Compiler.VisualBasic diff --git a/src/Spark/Spark.csproj b/src/Spark/Spark.csproj index 7831a61b..bebc0fa5 100644 --- a/src/Spark/Spark.csproj +++ b/src/Spark/Spark.csproj @@ -25,7 +25,7 @@ Spark is a view engine allowing the HTML to dominate the flow and any code to fit seamlessly. - + diff --git a/src/Xpark/Program.cs b/src/Xpark/Program.cs index 47611459..0ce4435f 100644 --- a/src/Xpark/Program.cs +++ b/src/Xpark/Program.cs @@ -5,9 +5,8 @@ using System.Xml.Linq; using Spark; using Spark.Bindings; -using Spark.Compiler.Roslyn; +using Spark.Compiler; using Spark.FileSystem; -using Spark.Parser; using Spark.Parser.Syntax; namespace Xpark From 16c4250360cc1758e1663c1d3b76953b6aa8b9f5 Mon Sep 17 00:00:00 2001 From: bounav Date: Tue, 30 Jan 2024 10:17:24 +0000 Subject: [PATCH 05/14] Using the Roslyn compiler in more places --- .../PrecompileInstallerTests.cs | 6 +++++- src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs | 2 +- src/Spark.Python/Compiler/PythonViewCompiler.cs | 2 +- src/Spark.Ruby/Compiler/RubyViewCompiler.cs | 2 +- src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs | 2 +- src/Spark/CompiledViewHolder.cs | 1 - src/Spark/Compiler/BatchCompiler.cs | 2 +- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Castle.MonoRail.Views.Spark.Tests/PrecompileInstallerTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/PrecompileInstallerTests.cs index 244de8f9..7cbdef4e 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/PrecompileInstallerTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/PrecompileInstallerTests.cs @@ -50,7 +50,11 @@ public void RunPrecompiler() Assert.That(File.Exists(targetFile), "File exists"); var result = Assembly.LoadFrom(targetFile); - Assert.AreEqual(3, result.GetTypes().Count()); + + var views = result.GetTypes().Where(x => x.BaseType == typeof(SparkView)) + .ToArray(); + + Assert.AreEqual(3, views.Length); } public class ParentInstaller : Installer diff --git a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs index 991d3672..e1827590 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs @@ -68,7 +68,7 @@ public ISparkViewEngine Engine var viewFolder = new ViewSourceLoaderWrapper(this); - var batchCompiler = new CodeDomBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(); this._engine = new SparkViewEngine( diff --git a/src/Spark.Python/Compiler/PythonViewCompiler.cs b/src/Spark.Python/Compiler/PythonViewCompiler.cs index cfe9ff50..5d7badd6 100644 --- a/src/Spark.Python/Compiler/PythonViewCompiler.cs +++ b/src/Spark.Python/Compiler/PythonViewCompiler.cs @@ -29,7 +29,7 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new CodeDomBatchCompiler(); + var compiler = new RoslynBatchCompiler(); var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs index 643155bd..e91124dc 100644 --- a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs +++ b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs @@ -30,7 +30,7 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new CodeDomBatchCompiler(); + var compiler = new RoslynBatchCompiler(); var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs index 4a62a992..71c49bcd 100644 --- a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs @@ -167,7 +167,7 @@ public void ProvideFullException() { new SendExpressionChunk { Code = "NoSuchVariable" } }), - Throws.TypeOf()); + Throws.TypeOf()); } [Test] diff --git a/src/Spark/CompiledViewHolder.cs b/src/Spark/CompiledViewHolder.cs index bfd3c163..ed82121e 100644 --- a/src/Spark/CompiledViewHolder.cs +++ b/src/Spark/CompiledViewHolder.cs @@ -15,7 +15,6 @@ using System; using System.Linq; using System.Collections.Generic; -using Spark; namespace Spark { diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/BatchCompiler.cs index 0063def5..16bc25ce 100644 --- a/src/Spark/Compiler/BatchCompiler.cs +++ b/src/Spark/Compiler/BatchCompiler.cs @@ -15,7 +15,7 @@ namespace Spark.Compiler; #if NETFRAMEWORK -[Obsolete("To be replaced with RoslynBatchCompiler (code dom cannot target .net 'core'")] +[Obsolete("To be replaced with RoslynBatchCompiler (code dom cannot target .net 'core')")] public class CodeDomBatchCompiler : IBatchCompiler { /// From ab0caca0aeb404a1d9b77934f00481732d00b994 Mon Sep 17 00:00:00 2001 From: bounav Date: Tue, 20 Feb 2024 10:26:33 +0000 Subject: [PATCH 06/14] Templates of generic controllers are now pre-compiled - Improved readability of RemoveSuffix method --- src/Spark.Web.Mvc/SparkViewFactory.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 8e1b98a2..c035186d 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -223,7 +223,22 @@ public IList CreateDescriptors(SparkBatchEntry entry) { var descriptors = new List(); - var controllerName = RemoveSuffix(entry.ControllerType.Name, "Controller"); + string controllerName = null; + + if (entry.ControllerType.ContainsGenericParameters) + { + // generic controller have a backtick suffix in their (name e.g. "SomeController`2") + var indexOfBacktick = entry.ControllerType.Name.IndexOf("Controller`", StringComparison.Ordinal); + if (indexOfBacktick > -1) + { + // removing it otherwise locating the view templates will fail + controllerName = entry.ControllerType.Name.Substring(0, indexOfBacktick); + } + } + else + { + controllerName = RemoveSuffix(entry.ControllerType.Name, "Controller"); + } var viewNames = new List(); @@ -324,12 +339,9 @@ private static bool TestMatch(string potentialMatch, string pattern) private static string RemoveSuffix(string value, string suffix) { - if (value.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)) - { - return value.Substring(0, value.Length - suffix.Length); - } - - return value; + return value.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase) + ? value.Substring(0, value.Length - suffix.Length) + : value; } #region IViewEngine Members From 87455e7fe4eb77facd250f73a0a77f1099d087e6 Mon Sep 17 00:00:00 2001 From: bounav Date: Tue, 20 Feb 2024 17:27:47 +0000 Subject: [PATCH 07/14] Reduced code duplication between ISparkSettings and ViewCompiler class - Renamed ISparkSettings.PageBaseType to BaseClassTypeName - An instance of ISparkSettings is used instead of duplicated properties on the ViewCompiler base class - New ISparkSettings.ExcludeAssemblies property that can be used to the prevent the view compiler from loading .DLLs that would containt precompile views (and would might already be loaded) --- .../SparkBatchCompilerTester.cs | 2 +- ...parkViewFactoryStrictNullBehaviourTests.cs | 2 +- .../SparkViewFactoryTests.cs | 2 +- .../ViewComponents/BaseViewComponentTests.cs | 2 +- .../Install/PrecompileInstaller.cs | 2 +- .../SparkViewFactory.cs | 4 +- src/Spark.JsTests/Generate.ashx.cs | 2 +- .../PythonViewCompilerTests.cs | 20 +-- .../ScriptingLanguageFactoryTests.cs | 2 +- .../Compiler/PythonViewCompiler.cs | 10 +- src/Spark.Python/PythonLanguageFactory.cs | 21 +--- src/Spark.Python/Spark.Python.csproj | 1 + src/Spark.Ruby.Tests/RubyViewCompilerTests.cs | 14 +-- src/Spark.Ruby/Compiler/RubyViewCompiler.cs | 10 +- src/Spark.Ruby/RubyLanguageFactory.cs | 20 +-- src/Spark.Ruby/Spark.Ruby.csproj | 1 + .../PythonLanguageFactoryWithExtensions.cs | 3 +- .../HtmlHelperExtensionsTester.cs | 4 +- .../RubyLanguageFactoryWithExtensions.cs | 3 +- src/Spark.Web.Mvc/SparkViewFactory.cs | 4 +- src/Spark.Web.Tests/App.config | 8 +- src/Spark.Web.Tests/BatchCompilationTester.cs | 2 +- .../Bindings/BindingExecutionTester.cs | 2 +- .../Caching/CacheElementTester.cs | 2 +- .../Compiler/CSharpViewCompilerTester.cs | 115 ++++++++++-------- .../Compiler/SourceMappingTester.cs | 4 +- .../Compiler/VisualBasicViewCompilerTester.cs | 107 +++++++++------- .../SparkSectionHandlerTester.cs | 4 +- .../FileSystem/InMemoryViewFolderTester.cs | 4 +- src/Spark.Web.Tests/ImportAndIncludeTester.cs | 2 +- .../Parser/AutomaticEncodingTester.cs | 2 +- .../Parser/CSharpSyntaxProviderTester.cs | 4 +- src/Spark.Web.Tests/PrefixSupportTester.cs | 8 +- src/Spark.Web.Tests/SparkExtensionTester.cs | 2 +- src/Spark.Web.Tests/SparkViewFactoryTester.cs | 2 +- src/Spark.Web.Tests/ViewActivatorTester.cs | 2 +- src/Spark.Web.Tests/VisualBasicViewTester.cs | 2 +- .../Configuration/CompilationElement.cs | 8 ++ .../Configuration/ExcludeAssemblyElement.cs | 14 +++ .../ExcludeAssemblyElementCollection.cs | 22 ++++ src/Spark.Web/Configuration/PagesElement.cs | 8 +- .../Configuration/SparkSectionHandler.cs | 37 ++++-- src/Spark/Compiler/BatchCompiler.cs | 17 ++- .../Compiler/CSharp/CSharpViewCompiler.cs | 14 +-- src/Spark/Compiler/IBatchCompiler.cs | 3 +- .../Javascript/JavascriptViewCompiler.cs | 12 +- src/Spark/Compiler/Roslyn/CSharpLink.cs | 8 +- src/Spark/Compiler/Roslyn/VisualBasicLink.cs | 10 +- src/Spark/Compiler/ViewCompiler.cs | 6 - .../VisualBasic/VisualBasicViewCompiler.cs | 29 +++-- src/Spark/CompositeViewEntry.cs | 22 +--- src/Spark/DefaultLanguageFactory.cs | 17 +-- src/Spark/ISparkSettings.cs | 11 +- src/Spark/ISparkViewEngine.cs | 1 - src/Spark/SparkSettings.cs | 39 +++++- src/Spark/SparkViewEngine.cs | 6 +- src/Xpark/Program.cs | 4 +- 57 files changed, 400 insertions(+), 289 deletions(-) create mode 100644 src/Spark.Web/Configuration/ExcludeAssemblyElement.cs create mode 100644 src/Spark.Web/Configuration/ExcludeAssemblyElementCollection.cs diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs index b0932118..3ae4bb27 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkBatchCompilerTester.cs @@ -36,7 +36,7 @@ public class SparkBatchCompilerTester public void Init() { var settings = new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); var services = new StubMonoRailServices(); services.AddService(typeof(ISparkSettings), settings); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs index 1f75a9c4..0f0078c6 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryStrictNullBehaviourTests.cs @@ -29,7 +29,7 @@ protected override void Configure() { var settings = new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); settings.SetNullBehaviour(NullBehaviour.Strict); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs index b969bcb3..be3168fa 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs @@ -31,7 +31,7 @@ public class SparkViewFactoryTests : SparkViewFactoryTestsBase protected override void Configure() { var settings = new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); serviceProvider.AddService(typeof(ISparkSettings), settings); diff --git a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs index e813af53..68ccc496 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/ViewComponents/BaseViewComponentTests.cs @@ -45,7 +45,7 @@ public virtual void Init() services.AddService(typeof(IViewComponentRegistry), viewComponentFactory.Registry); var settings = new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); services.AddService(typeof(ISparkSettings), settings); services.AddService(typeof(IResourcePathManager), new DefaultResourcePathManager(settings)); diff --git a/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs b/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs index e604106b..69f3d4f5 100644 --- a/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs +++ b/src/Castle.MonoRail.Views.Spark/Install/PrecompileInstaller.cs @@ -78,7 +78,7 @@ public override void Install(IDictionary stateSaver) var settings = (ISparkSettings)config.GetSection("spark") ?? new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); var services = new StubMonoRailServices(); services.AddService(typeof(ISparkSettings), settings); diff --git a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs index e1827590..2dd13be8 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs @@ -62,7 +62,7 @@ public ISparkViewEngine Engine var settings = (ISparkSettings)this.serviceProvider.GetService(typeof(ISparkSettings)) ?? new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); var partialProvider = new DefaultPartialProvider(); @@ -75,7 +75,7 @@ public ISparkViewEngine Engine settings, new DefaultSyntaxProvider(settings), this._viewActivatorFactory ?? new DefaultViewActivator(), - new DefaultLanguageFactory(batchCompiler), + new DefaultLanguageFactory(batchCompiler, settings), new CompiledViewHolder(), viewFolder, batchCompiler, diff --git a/src/Spark.JsTests/Generate.ashx.cs b/src/Spark.JsTests/Generate.ashx.cs index 5096a44e..586f0736 100644 --- a/src/Spark.JsTests/Generate.ashx.cs +++ b/src/Spark.JsTests/Generate.ashx.cs @@ -46,7 +46,7 @@ public void ProcessRequest(HttpContext context) settings, new DefaultSyntaxProvider(settings), new DefaultViewActivator(), - new DefaultLanguageFactory(batchCompiler), + new DefaultLanguageFactory(batchCompiler, settings), new CompiledViewHolder(), viewFolder, batchCompiler, diff --git a/src/Spark.Python.Tests/PythonViewCompilerTests.cs b/src/Spark.Python.Tests/PythonViewCompilerTests.cs index c3720f99..7e90de21 100644 --- a/src/Spark.Python.Tests/PythonViewCompilerTests.cs +++ b/src/Spark.Python.Tests/PythonViewCompilerTests.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; + using System.Collections.Generic; using System.IO; using NUnit.Framework; @@ -28,17 +28,21 @@ namespace Spark.Python.Tests [TestFixture] public class PythonViewCompilerTests { + private ISparkSettings _settings; private PythonViewCompiler _compiler; private PythonLanguageFactory _languageFactory; [SetUp] public void Init() { - _compiler = new PythonViewCompiler - { - BaseClass = typeof(StubSparkView).FullName - }; - _languageFactory = new PythonLanguageFactory(new RoslynBatchCompiler()); + _settings = new SparkSettings + { + BaseClassTypeName = typeof(StubSparkView).FullName + }; + + _compiler = new PythonViewCompiler(_settings); + + _languageFactory = new PythonLanguageFactory(new RoslynBatchCompiler(), _settings); // Load up assemblies IronPython.Hosting.Python.CreateEngine(); @@ -86,7 +90,7 @@ public void CodeInheritsBaseClass() { var chunks = Chunks(); - _compiler.BaseClass = "ThisIsTheBaseClass"; + this._settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); @@ -97,7 +101,7 @@ public void CodeInheritsBaseClassWithTModel() { var chunks = Chunks(new ViewDataModelChunk { TModel = "ThisIsTheModelClass" }); - _compiler.BaseClass = "ThisIsTheBaseClass"; + this._settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); diff --git a/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs b/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs index e4f90f4d..b167ac6c 100644 --- a/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs +++ b/src/Spark.Python.Tests/ScriptingLanguageFactoryTests.cs @@ -29,7 +29,7 @@ public class ScriptingLanguageFactoryTests [SetUp] public void Init() { - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Python/Compiler/PythonViewCompiler.cs b/src/Spark.Python/Compiler/PythonViewCompiler.cs index 5d7badd6..e4936fbe 100644 --- a/src/Spark.Python/Compiler/PythonViewCompiler.cs +++ b/src/Spark.Python/Compiler/PythonViewCompiler.cs @@ -23,14 +23,14 @@ namespace Spark.Python.Compiler { - public class PythonViewCompiler : ViewCompiler + public class PythonViewCompiler(ISparkSettings settings) : ViewCompiler { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { GenerateSourceCode(viewTemplates, allResources); var compiler = new RoslynBatchCompiler(); - var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); + var assembly = compiler.Compile(settings.Debug, "csharp", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } @@ -83,13 +83,13 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, } } - var baseClassGenerator = new BaseClassVisitor { BaseClass = BaseClass }; + var baseClassGenerator = new BaseClassVisitor { BaseClass = settings.BaseClassTypeName }; foreach (var resource in allResources) { baseClassGenerator.Accept(resource); } - BaseClass = baseClassGenerator.BaseClassTypeName; + var baseClass = baseClassGenerator.BaseClassTypeName; var source = new StringBuilder(); @@ -121,7 +121,7 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source.AppendLine(" })]"); } - source.Append("public class ").Append(viewClassName).Append(" : ").Append(BaseClass).AppendLine(", global::Spark.Python.IScriptingSparkView"); + source.Append("public class ").Append(viewClassName).Append(" : ").Append(baseClass).AppendLine(", global::Spark.Python.IScriptingSparkView"); source.AppendLine("{"); source.Append("static System.Guid _generatedViewId = new System.Guid(\"").Append(GeneratedViewId).AppendLine("\");"); diff --git a/src/Spark.Python/PythonLanguageFactory.cs b/src/Spark.Python/PythonLanguageFactory.cs index 21aa6705..791861c5 100644 --- a/src/Spark.Python/PythonLanguageFactory.cs +++ b/src/Spark.Python/PythonLanguageFactory.cs @@ -18,12 +18,8 @@ namespace Spark.Python { - public class PythonLanguageFactory : DefaultLanguageFactory + public class PythonLanguageFactory(IBatchCompiler batchCompiler, ISparkSettings settings) : DefaultLanguageFactory(batchCompiler, settings) { - public PythonLanguageFactory(IBatchCompiler batchCompiler) : base(batchCompiler) - { - } - private PythonEngineManager _PythonEngineManager; public PythonEngineManager PythonEngineManager @@ -46,25 +42,14 @@ public override ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkVi { case LanguageType.Default: case LanguageType.Python: - viewCompiler = new PythonViewCompiler(); + viewCompiler = new PythonViewCompiler(settings); break; default: return base.CreateViewCompiler(engine, descriptor); } - var pageBaseType = engine.Settings.PageBaseType; - if (string.IsNullOrEmpty(pageBaseType)) - { - pageBaseType = engine.DefaultPageBaseType; - } - - viewCompiler.BaseClass = pageBaseType; viewCompiler.Descriptor = descriptor; - viewCompiler.Debug = engine.Settings.Debug; - viewCompiler.NullBehaviour = engine.Settings.NullBehaviour; - viewCompiler.UseAssemblies = engine.Settings.UseAssemblies; - viewCompiler.UseNamespaces = engine.Settings.UseNamespaces; - + return viewCompiler; } diff --git a/src/Spark.Python/Spark.Python.csproj b/src/Spark.Python/Spark.Python.csproj index 3cd3964e..4f8693cf 100644 --- a/src/Spark.Python/Spark.Python.csproj +++ b/src/Spark.Python/Spark.Python.csproj @@ -1,6 +1,7 @@ net48 + latest Library AllRules.ruleset False diff --git a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs index 7099520d..366260aa 100644 --- a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs +++ b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs @@ -12,12 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; using System.Collections.Generic; using System.IO; using NUnit.Framework; using Spark.Compiler; -using Spark.Compiler.Roslyn; using Spark.Parser; using Spark.Ruby.Compiler; using Spark.Tests.Models; @@ -28,18 +26,20 @@ namespace Spark.Ruby.Tests [TestFixture] public class RubyViewCompilerTests { + private ISparkSettings _settings; private RubyViewCompiler _compiler; private RubyLanguageFactory _languageFactory; [SetUp] public void Init() { - _compiler = new RubyViewCompiler + _settings = new SparkSettings { - BaseClass = typeof(StubSparkView).FullName, + BaseClassTypeName = typeof(StubSparkView).FullName, Debug = true }; - _languageFactory = new RubyLanguageFactory(new RoslynBatchCompiler()); + _compiler = new RubyViewCompiler(this._settings); + _languageFactory = new RubyLanguageFactory(new RoslynBatchCompiler(), this._settings); //load assemblies global::IronRuby.Ruby.CreateEngine(); @@ -88,7 +88,7 @@ public void CodeInheritsBaseClass() { var chunks = Chunks(); - _compiler.BaseClass = "ThisIsTheBaseClass"; + _settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); @@ -99,7 +99,7 @@ public void CodeInheritsBaseClassWithTModel() { var chunks = Chunks(new ViewDataModelChunk { TModel = "ThisIsTheModelClass" }); - _compiler.BaseClass = "ThisIsTheBaseClass"; + _settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); diff --git a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs index e91124dc..7ebee1d2 100644 --- a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs +++ b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs @@ -22,7 +22,7 @@ namespace Spark.Ruby.Compiler { - public class RubyViewCompiler : ViewCompiler + public class RubyViewCompiler(ISparkSettings settings) : ViewCompiler { public string ScriptHeader { get; set; } @@ -31,7 +31,7 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume GenerateSourceCode(viewTemplates, allResources); var compiler = new RoslynBatchCompiler(); - var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); + var assembly = compiler.Compile(settings.Debug, "csharp", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } @@ -104,13 +104,13 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, script.WriteLine("view.render"); - var baseClassGenerator = new BaseClassVisitor { BaseClass = BaseClass }; + var baseClassGenerator = new BaseClassVisitor { BaseClass = settings.BaseClassTypeName }; foreach (var resource in allResources) { baseClassGenerator.Accept(resource); } - BaseClass = baseClassGenerator.BaseClassTypeName; + var baseClass = baseClassGenerator.BaseClassTypeName; var source = new StringBuilder(); @@ -142,7 +142,7 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source.AppendLine(" })]"); } - source.Append("public class ").Append(viewClassName).Append(" : ").Append(BaseClass).AppendLine(", global::Spark.Ruby.IScriptingSparkView"); + source.Append("public class ").Append(viewClassName).Append(" : ").Append(baseClass).AppendLine(", global::Spark.Ruby.IScriptingSparkView"); source.AppendLine("{"); source.Append("static System.Guid _generatedViewId = new System.Guid(\"").Append(GeneratedViewId).AppendLine("\");"); diff --git a/src/Spark.Ruby/RubyLanguageFactory.cs b/src/Spark.Ruby/RubyLanguageFactory.cs index 5917fd62..0978e701 100644 --- a/src/Spark.Ruby/RubyLanguageFactory.cs +++ b/src/Spark.Ruby/RubyLanguageFactory.cs @@ -18,12 +18,9 @@ namespace Spark.Ruby { - public class RubyLanguageFactory : DefaultLanguageFactory + public class RubyLanguageFactory(IBatchCompiler batchCompiler, ISparkSettings settings) + : DefaultLanguageFactory(batchCompiler, settings) { - public RubyLanguageFactory(IBatchCompiler batchCompiler) : base(batchCompiler) - { - } - private RubyEngineManager _RubyEngineManager; public RubyEngineManager RubyEngineManager @@ -43,23 +40,14 @@ public override ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkVi { case LanguageType.Default: case LanguageType.Ruby: - viewCompiler = new RubyViewCompiler(); + viewCompiler = new RubyViewCompiler(settings); break; default: return base.CreateViewCompiler(engine, descriptor); } - var pageBaseType = engine.Settings.PageBaseType; - if (string.IsNullOrEmpty(pageBaseType)) - pageBaseType = engine.DefaultPageBaseType; - - viewCompiler.BaseClass = pageBaseType; viewCompiler.Descriptor = descriptor; - viewCompiler.Debug = engine.Settings.Debug; - viewCompiler.NullBehaviour = engine.Settings.NullBehaviour; - viewCompiler.UseAssemblies = engine.Settings.UseAssemblies; - viewCompiler.UseNamespaces = engine.Settings.UseNamespaces; - + return viewCompiler; } diff --git a/src/Spark.Ruby/Spark.Ruby.csproj b/src/Spark.Ruby/Spark.Ruby.csproj index bcbee7e1..4fbb3834 100644 --- a/src/Spark.Ruby/Spark.Ruby.csproj +++ b/src/Spark.Ruby/Spark.Ruby.csproj @@ -1,6 +1,7 @@ net48 + latest Library AllRules.ruleset False diff --git a/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs b/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs index 71adfd2e..edfe64bc 100644 --- a/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs +++ b/src/Spark.Web.Mvc.Python/PythonLanguageFactoryWithExtensions.cs @@ -15,7 +15,6 @@ using System.Web.Mvc; using Microsoft.Scripting.Runtime; using Spark.Compiler; -using Spark.Compiler.Roslyn; using Spark.Python; [assembly: ExtensionType(typeof(HtmlHelper), typeof(System.Web.Mvc.Html.FormExtensions))] @@ -32,7 +31,7 @@ namespace Spark.Web.Mvc.Python { public class PythonLanguageFactoryWithExtensions : PythonLanguageFactory { - public PythonLanguageFactoryWithExtensions(IBatchCompiler batchCompiler) : base(batchCompiler) + public PythonLanguageFactoryWithExtensions(IBatchCompiler batchCompiler, ISparkSettings settings) : base(batchCompiler, settings) { } diff --git a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs index 42002f12..60701dee 100644 --- a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs +++ b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs @@ -41,9 +41,11 @@ public class StubController : Controller [Test] public void BuildingScriptHeader() { + var settings = new SparkSettings(); + var batchCompiler = new RoslynBatchCompiler(); - var languageFactory = new RubyLanguageFactoryWithExtensions(batchCompiler); + var languageFactory = new RubyLanguageFactoryWithExtensions(batchCompiler, settings); var header = languageFactory.BuildScriptHeader(languageFactory.GetType().Assembly); diff --git a/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs b/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs index b427e760..45b85f81 100644 --- a/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs +++ b/src/Spark.Web.Mvc.Ruby/RubyLanguageFactoryWithExtensions.cs @@ -22,7 +22,6 @@ using IronRuby.Runtime; using Microsoft.Scripting.Runtime; using Spark.Compiler; -using Spark.Compiler.Roslyn; using Spark.Ruby; using Spark.Ruby.Compiler; @@ -40,7 +39,7 @@ namespace Spark.Web.Mvc.Ruby { public class RubyLanguageFactoryWithExtensions : RubyLanguageFactory { - public RubyLanguageFactoryWithExtensions(IBatchCompiler batchCompiler) : base(batchCompiler) + public RubyLanguageFactoryWithExtensions(IBatchCompiler batchCompiler, ISparkSettings settings) : base(batchCompiler, settings) { } diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index c035186d..99d237e6 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -44,9 +44,9 @@ public SparkViewFactory(ISparkSettings settings, { Settings = settings; - if (string.IsNullOrEmpty(settings.PageBaseType)) + if (string.IsNullOrEmpty(settings.BaseClassTypeName)) { - settings.PageBaseType = typeof(SparkView).FullName; + settings.BaseClassTypeName = typeof(SparkView).FullName; } Engine = viewEngine; diff --git a/src/Spark.Web.Tests/App.config b/src/Spark.Web.Tests/App.config index e035c404..a7392152 100644 --- a/src/Spark.Web.Tests/App.config +++ b/src/Spark.Web.Tests/App.config @@ -9,7 +9,7 @@ - + @@ -23,11 +23,5 @@ - diff --git a/src/Spark.Web.Tests/BatchCompilationTester.cs b/src/Spark.Web.Tests/BatchCompilationTester.cs index ad18ffba..61d58e57 100644 --- a/src/Spark.Web.Tests/BatchCompilationTester.cs +++ b/src/Spark.Web.Tests/BatchCompilationTester.cs @@ -36,7 +36,7 @@ public class BatchCompilationTester public void Init() { var settings = new SparkSettings() - .SetPageBaseType(typeof(Tests.Stubs.StubSparkView)); + .SetBaseClassTypeName(typeof(Tests.Stubs.StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs b/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs index be896215..11df38c6 100644 --- a/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs +++ b/src/Spark.Web.Tests/Bindings/BindingExecutionTester.cs @@ -18,7 +18,7 @@ public class BindingExecutionTester [SetUp] public void Init() { - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/Caching/CacheElementTester.cs b/src/Spark.Web.Tests/Caching/CacheElementTester.cs index 269f49c9..8624b3c1 100644 --- a/src/Spark.Web.Tests/Caching/CacheElementTester.cs +++ b/src/Spark.Web.Tests/Caching/CacheElementTester.cs @@ -41,7 +41,7 @@ public class CacheElementTester [SetUp] public void Init() { - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs index 71c49bcd..375a55b7 100644 --- a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs @@ -41,7 +41,8 @@ private static void DoCompileView(ViewCompiler compiler, IList chunks) [Test] public void MakeAndCompile() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendLiteralChunk { Text = "hello world" } }); @@ -55,8 +56,9 @@ public void MakeAndCompile() [Test] public void UnsafeLiteralCharacters() { + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; var text = "hello\t\r\n\"world"; - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendLiteralChunk { Text = text } }); Assert.That(compiler.SourceCode.Contains("Write(\"hello\\t\\r\\n\\\"world\")")); @@ -70,7 +72,8 @@ public void UnsafeLiteralCharacters() [Test] public void SimpleOutput() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); string contents = instance.RenderView(); @@ -81,7 +84,8 @@ public void SimpleOutput() [Test] public void LocalVariableDecl() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new LocalVariableChunk { Name = "i", Value = "5" }, @@ -96,7 +100,8 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new[]{3,4,5}"}, @@ -122,7 +127,8 @@ public void ForEachLoop() [Test] public void GlobalVariables() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new SendExpressionChunk{Code="title"}, @@ -141,9 +147,9 @@ public void GlobalVariables() [Test] public void TargetNamespace() { - var compiler = new CSharpViewCompiler(this.batchCompiler) + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings) { - BaseClass = "Spark.SparkViewBase", Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } }; @@ -157,7 +163,8 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); Assert.That( () => @@ -173,7 +180,8 @@ public void ProvideFullException() [Test] public void IfTrueCondition() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -196,7 +204,8 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -219,7 +228,8 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; var falseChunks = new Chunk[] { new SendLiteralChunk { Text = "wasfalse" } }; @@ -244,7 +254,8 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -267,7 +278,8 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -290,11 +302,12 @@ public void UnlessFalseCondition() [Test] public void LenientSilentNullDoesNotCauseWarningCS0168() { - var compiler = new CSharpViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Lenient - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Lenient + }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] { @@ -310,11 +323,12 @@ public void LenientSilentNullDoesNotCauseWarningCS0168() [Test] public void LenientOutputNullDoesNotCauseWarningCS0168() { - var compiler = new CSharpViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Lenient - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Lenient + }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] { new ViewDataChunk { Name = "comment", Type = "Spark.Tests.Models.Comment" }, @@ -329,11 +343,12 @@ public void LenientOutputNullDoesNotCauseWarningCS0168() [Test] public void StrictNullUsesException() { - var compiler = new CSharpViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] { @@ -351,11 +366,12 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var compiler = new CSharpViewCompiler(this.batchCompiler) + var settings = new SparkSettings { - BaseClass = "Spark.Tests.Stubs.StubSparkView", + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -372,11 +388,12 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var compiler = new CSharpViewCompiler(this.batchCompiler) + var settings = new SparkSettings { - BaseClass = "Spark.Tests.Stubs.StubSparkView", + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView( compiler, @@ -396,11 +413,12 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var compiler = new CSharpViewCompiler(this.batchCompiler) + var settings = new SparkSettings { - BaseClass = "Spark.Tests.Stubs.StubSparkView", + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -418,24 +436,25 @@ public void PageBaseTypeWorksWithGenericParametersIncluded() [Test] public void Markdown() { - var compiler = new CSharpViewCompiler(this.batchCompiler) { BaseClass = "Spark.SparkViewBase" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); - var innerChunks = new Chunk[] { new SendLiteralChunk { Text = "*test*" } }; + var innerChunks = new Chunk[] { new SendLiteralChunk { Text = "*test*" } }; - DoCompileView( - compiler, - new Chunk[] - { - new MarkdownChunk { Body = innerChunks } - }); + DoCompileView( + compiler, + new Chunk[] + { + new MarkdownChunk { Body = innerChunks } + }); - Assert.That(compiler.SourceCode, Does.Contain("using(MarkdownOutputScope())")); - Assert.That(compiler.SourceCode, Does.Contain("Output.Write(\"*test*\");")); + Assert.That(compiler.SourceCode, Does.Contain("using(MarkdownOutputScope())")); + Assert.That(compiler.SourceCode, Does.Contain("Output.Write(\"*test*\");")); - var instance = compiler.CreateInstance(); - var contents = instance.RenderView().Trim(); + var instance = compiler.CreateInstance(); + var contents = instance.RenderView().Trim(); - Assert.That(contents, Is.EqualTo("

test

")); + Assert.That(contents, Is.EqualTo("

test

")); } } } \ No newline at end of file diff --git a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs index 59b31871..d70274b8 100644 --- a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs +++ b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs @@ -36,7 +36,7 @@ public class SourceMappingTester public void Init() { var settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)); + .SetBaseClassTypeName(typeof(StubSparkView)); var partialProvider = new DefaultPartialProvider(); @@ -48,7 +48,7 @@ public void Init() settings, new DefaultSyntaxProvider(settings), new DefaultViewActivator(), - new DefaultLanguageFactory(batchCompiler), + new DefaultLanguageFactory(batchCompiler, settings), new CompiledViewHolder(), _viewFolder, batchCompiler, diff --git a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs index 88a9ad76..58133a4b 100644 --- a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs +++ b/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs @@ -56,7 +56,8 @@ public void MakeAndCompile() [Test] public void StronglyTypedBase() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.Tests.Stubs.StubSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -85,23 +86,26 @@ public void UnsafeLiteralCharacters() Assert.That(contents, Is.EqualTo(text)); } - private VisualBasicViewCompiler CreateCompiler() + private VisualBasicViewCompiler CreateCompiler(ISparkSettings settings = null) { - return new VisualBasicViewCompiler(this.batchCompiler) + if (settings == null) { - BaseClass = "Spark.AbstractSparkView", - UseAssemblies = new[] { "Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" }, - UseNamespaces = new[] { "Microsoft.VisualBasic" } - }; + settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" } + .AddAssembly("Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") + .AddNamespace("Microsoft.VisualBasic"); + } + + return new VisualBasicViewCompiler(this.batchCompiler, settings); } [Test] public void SimpleOutput() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); - string contents = instance.RenderView(); + var contents = instance.RenderView(); Assert.AreEqual("7", contents); } @@ -133,9 +137,9 @@ public void SilentNullBehavior() [Test] public void RethrowNullBehavior() { - var compiler = CreateCompiler(); - compiler.NullBehaviour = NullBehaviour.Strict; - + var settings = new SparkSettings { NullBehaviour = NullBehaviour.Strict }; + var compiler = CreateCompiler(settings); + DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "CType(Nothing, String).Length" } }); var instance = compiler.CreateInstance(); @@ -145,7 +149,9 @@ public void RethrowNullBehavior() [Test] public void LocalVariableDecl() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new LocalVariableChunk { Name = "i", Value = "5" }, @@ -160,7 +166,8 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new Integer(){3,4,5}"}, @@ -186,7 +193,8 @@ public void ForEachLoop() [Test] public void ForEachAutoVariables() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new LocalVariableChunk {Name = "data", Value = "new Integer(){3,4,5}"}, @@ -216,7 +224,8 @@ public void ForEachAutoVariables() [Test] public void GlobalVariables() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new SendExpressionChunk{Code="title"}, @@ -236,9 +245,9 @@ public void GlobalVariables() [Platform(Exclude = "Mono", Reason = "Problems with Mono-2.10+/Linux and the VB compiler prevent this from running.")] public void TargetNamespace() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings) { - BaseClass = "Spark.AbstractSparkView", Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } }; @@ -251,7 +260,8 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); Assert.That(() => DoCompileView(compiler, new Chunk[] @@ -264,7 +274,8 @@ public void ProvideFullException() [Test] public void IfTrueCondition() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -284,7 +295,8 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -304,7 +316,8 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; var falseChunks = new Chunk[] { new SendLiteralChunk { Text = "wasfalse" } }; @@ -326,7 +339,8 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -346,7 +360,8 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) { BaseClass = "Spark.AbstractSparkView" }; + var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -366,11 +381,12 @@ public void UnlessFalseCondition() [Test] public void StrictNullUsesException() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] { new ViewDataChunk { Name="comment", Type="Spark.Tests.Models.Comment"}, @@ -386,11 +402,12 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new PageBaseTypeChunk { BaseClass="Spark.Tests.Stubs.StubSparkView2"}, @@ -405,11 +422,12 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new PageBaseTypeChunk {BaseClass = "Spark.Tests.Stubs.StubSparkView2"}, @@ -425,11 +443,12 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var compiler = new VisualBasicViewCompiler(this.batchCompiler) - { - BaseClass = "Spark.Tests.Stubs.StubSparkView", - NullBehaviour = NullBehaviour.Strict - }; + var settings = new SparkSettings + { + BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", + NullBehaviour = NullBehaviour.Strict + }; + var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { new PageBaseTypeChunk {BaseClass = "Spark.Tests.Stubs.StubSparkView3(Of Spark.Tests.Models.Comment, string)"}, diff --git a/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs b/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs index 0ae88148..22a4814a 100644 --- a/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs +++ b/src/Spark.Web.Tests/Configuration/SparkSectionHandlerTester.cs @@ -35,7 +35,7 @@ public void CanLoadFromAppConfig() Assert.IsTrue(config.Compilation.Debug); Assert.AreEqual(NullBehaviour.Strict, config.Compilation.NullBehaviour); Assert.AreEqual(1, config.Compilation.Assemblies.Count); - Assert.AreEqual(typeof(StubSparkView).FullName, config.Pages.PageBaseType); + Assert.AreEqual(typeof(StubSparkView).FullName, config.Pages.BaseClassTypeName); Assert.AreEqual(1, config.Pages.Namespaces.Count); } @@ -79,7 +79,7 @@ public void UseAssemblyAndNamespaceFromSettings() var settings = new SparkSettings() .AddNamespace("System.Web") .AddAssembly("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") - .SetPageBaseType(typeof(StubSparkView)); + .SetBaseClassTypeName(typeof(StubSparkView)); var viewFolder = new InMemoryViewFolder { diff --git a/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs b/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs index d93144dd..760505cc 100644 --- a/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs +++ b/src/Spark.Web.Tests/FileSystem/InMemoryViewFolderTester.cs @@ -126,7 +126,7 @@ public void InMemoryViewFolderUsedByEngine() { Path.Combine("home", "index.spark"), "

Hello world

" } }; - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) @@ -177,7 +177,7 @@ public void UnicodeCharactersSurviveConversionToByteArrayAndBack() Assert.That(ReadToEnd(viewFolder, Path.Combine("Home", "ru.spark")), Is.EqualTo("Русский")); Assert.That(ReadToEnd(viewFolder, Path.Combine("Home", "ja.spark")), Is.EqualTo("日本語")); - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/ImportAndIncludeTester.cs b/src/Spark.Web.Tests/ImportAndIncludeTester.cs index b4b6d0b6..aad6a7ec 100644 --- a/src/Spark.Web.Tests/ImportAndIncludeTester.cs +++ b/src/Spark.Web.Tests/ImportAndIncludeTester.cs @@ -29,7 +29,7 @@ public class ImportAndIncludeTester { private ISparkView CreateView(IViewFolder viewFolder, string template) { - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs index 2674bddb..c853cee8 100644 --- a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs +++ b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs @@ -42,7 +42,7 @@ public void Init() public void Init(bool automaticEncoding) { this._settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)) + .SetBaseClassTypeName(typeof(StubSparkView)) .SetAutomaticEncoding(automaticEncoding); this._viewFolder = new InMemoryViewFolder(); diff --git a/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs b/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs index 5adeec30..1d84f615 100644 --- a/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs +++ b/src/Spark.Web.Tests/Parser/CSharpSyntaxProviderTester.cs @@ -46,7 +46,7 @@ public void CanParseSimpleFile() [Test] public void UsingCSharpSyntaxInsideEngine() { - var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + var settings = new SparkSettings().SetBaseClassTypeName("Spark.Tests.Stubs.StubSparkView"); var sp = new ServiceCollection() .AddSpark(settings) @@ -70,7 +70,7 @@ public void UsingCSharpSyntaxInsideEngine() [Test] public void StatementAndExpressionInCode() { - var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + var settings = new SparkSettings().SetBaseClassTypeName("Spark.Tests.Stubs.StubSparkView"); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/PrefixSupportTester.cs b/src/Spark.Web.Tests/PrefixSupportTester.cs index 12c6cadc..b502ccc8 100644 --- a/src/Spark.Web.Tests/PrefixSupportTester.cs +++ b/src/Spark.Web.Tests/PrefixSupportTester.cs @@ -32,7 +32,7 @@ public class PrefixSupportTester public void Init() { _settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)); + .SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(_settings) @@ -57,7 +57,7 @@ static void ContainsInOrder(string content, params string[] values) public void PrefixFromSettings() { var settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)) + .SetBaseClassTypeName(typeof(StubSparkView)) .SetPrefix("s"); var sp = new ServiceCollection() @@ -183,7 +183,7 @@ public void SegmentAndRenderPrefixes() public void SectionAsSegmentAndRenderPrefixes() { var settings = new SparkSettings() - .SetPageBaseType(typeof(StubSparkView)) + .SetBaseClassTypeName(typeof(StubSparkView)) .SetParseSectionTagAsSegment(true); var sp = new ServiceCollection() @@ -214,7 +214,7 @@ public void SectionAsSegmentAndRenderPrefixes() [Test] public void MacroAndContentPrefixesFromSettings() { - this._settings.SetPageBaseType(typeof(StubSparkView)) + this._settings.SetBaseClassTypeName(typeof(StubSparkView)) .SetPrefix("s"); var view = diff --git a/src/Spark.Web.Tests/SparkExtensionTester.cs b/src/Spark.Web.Tests/SparkExtensionTester.cs index c490c59b..3a733721 100644 --- a/src/Spark.Web.Tests/SparkExtensionTester.cs +++ b/src/Spark.Web.Tests/SparkExtensionTester.cs @@ -34,7 +34,7 @@ public class SparkExtensionTester [SetUp] public void Init() { - var settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + var settings = new SparkSettings().SetBaseClassTypeName("Spark.Tests.Stubs.StubSparkView"); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Tests/SparkViewFactoryTester.cs index fe734fb5..50bcb07b 100644 --- a/src/Spark.Web.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Tests/SparkViewFactoryTester.cs @@ -47,7 +47,7 @@ public class SparkViewFactoryTester [SetUp] public void Init() { - settings = new SparkSettings().SetPageBaseType("Spark.Tests.Stubs.StubSparkView"); + settings = new SparkSettings().SetBaseClassTypeName("Spark.Tests.Stubs.StubSparkView"); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/ViewActivatorTester.cs b/src/Spark.Web.Tests/ViewActivatorTester.cs index d1ced9e8..adac9568 100644 --- a/src/Spark.Web.Tests/ViewActivatorTester.cs +++ b/src/Spark.Web.Tests/ViewActivatorTester.cs @@ -90,7 +90,7 @@ public void FastCreateViewInstance() [Test] public void CustomViewActivator() { - var settings = new SparkSettings().SetPageBaseType(typeof(StubSparkView)); + var settings = new SparkSettings().SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web.Tests/VisualBasicViewTester.cs b/src/Spark.Web.Tests/VisualBasicViewTester.cs index 44a8097f..1094652c 100644 --- a/src/Spark.Web.Tests/VisualBasicViewTester.cs +++ b/src/Spark.Web.Tests/VisualBasicViewTester.cs @@ -21,7 +21,7 @@ public void Init() { var settings = new SparkSettings() .SetDefaultLanguage(LanguageType.VisualBasic) - .SetPageBaseType(typeof(StubSparkView)); + .SetBaseClassTypeName(typeof(StubSparkView)); var sp = new ServiceCollection() .AddSpark(settings) diff --git a/src/Spark.Web/Configuration/CompilationElement.cs b/src/Spark.Web/Configuration/CompilationElement.cs index 7a75cd24..26638774 100644 --- a/src/Spark.Web/Configuration/CompilationElement.cs +++ b/src/Spark.Web/Configuration/CompilationElement.cs @@ -53,5 +53,13 @@ public AssemblyElementCollection Assemblies get => (AssemblyElementCollection)this["assemblies"]; set => this["assemblies"] = value; } + + [ConfigurationProperty("excludeAssemblies")] + [ConfigurationCollection(typeof(ExcludeAssemblyElementCollection))] + public ExcludeAssemblyElementCollection ExcludeAssemblies + { + get => (ExcludeAssemblyElementCollection)this["excludeAssemblies"]; + set => this["excludeAssemblies"] = value; + } } } diff --git a/src/Spark.Web/Configuration/ExcludeAssemblyElement.cs b/src/Spark.Web/Configuration/ExcludeAssemblyElement.cs new file mode 100644 index 00000000..2426966a --- /dev/null +++ b/src/Spark.Web/Configuration/ExcludeAssemblyElement.cs @@ -0,0 +1,14 @@ +using System.Configuration; + +namespace Spark.Configuration +{ + public class ExcludeAssemblyElement : ConfigurationElement + { + [ConfigurationProperty("excludeAssembly")] + public string Assembly + { + get => (string)this["excludeAssembly"]; + set => this["excludeAssembly"] = value; + } + } +} \ No newline at end of file diff --git a/src/Spark.Web/Configuration/ExcludeAssemblyElementCollection.cs b/src/Spark.Web/Configuration/ExcludeAssemblyElementCollection.cs new file mode 100644 index 00000000..18aa62a7 --- /dev/null +++ b/src/Spark.Web/Configuration/ExcludeAssemblyElementCollection.cs @@ -0,0 +1,22 @@ +using System.Configuration; + +namespace Spark.Configuration +{ + public class ExcludeAssemblyElementCollection : ConfigurationElementCollection + { + protected override ConfigurationElement CreateNewElement() + { + return new AssemblyElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((ExcludeAssemblyElement)element).Assembly; + } + + public void Add(string assembly) + { + this.BaseAdd(new ExcludeAssemblyElement { Assembly = assembly }); + } + } +} \ No newline at end of file diff --git a/src/Spark.Web/Configuration/PagesElement.cs b/src/Spark.Web/Configuration/PagesElement.cs index a343c2e3..6e35e0fc 100644 --- a/src/Spark.Web/Configuration/PagesElement.cs +++ b/src/Spark.Web/Configuration/PagesElement.cs @@ -19,11 +19,11 @@ namespace Spark.Configuration { public class PagesElement : ConfigurationElement { - [ConfigurationProperty("pageBaseType")] - public string PageBaseType + [ConfigurationProperty("baseClassTypeName")] + public string BaseClassTypeName { - get => (string)this["pageBaseType"]; - set => this["pageBaseType"] = value; + get => (string)this["baseClassTypeName"]; + set => this["baseClassTypeName"] = value; } [ConfigurationProperty("prefix")] diff --git a/src/Spark.Web/Configuration/SparkSectionHandler.cs b/src/Spark.Web/Configuration/SparkSectionHandler.cs index 84ccb931..d610daf7 100644 --- a/src/Spark.Web/Configuration/SparkSectionHandler.cs +++ b/src/Spark.Web/Configuration/SparkSectionHandler.cs @@ -56,15 +56,15 @@ public SparkSectionHandler SetDebug(bool debug) return this; } - public SparkSectionHandler SetPageBaseType(string typeName) + public SparkSectionHandler SetBaseClassTypeName(string typeName) { - Pages.PageBaseType = typeName; + Pages.BaseClassTypeName = typeName; return this; } - public SparkSectionHandler SetPageBaseType(Type type) + public SparkSectionHandler SetBaseClassTypeName(Type type) { - Pages.PageBaseType = type.FullName; + Pages.BaseClassTypeName = type.FullName; return this; } @@ -105,10 +105,10 @@ public SparkSectionHandler AddNamespace(string ns) bool ISparkSettings.ParseSectionTagAsSegment => Pages.ParseSectionTagAsSegment; - string ISparkSettings.PageBaseType + string ISparkSettings.BaseClassTypeName { - get => Pages.PageBaseType; - set => Pages.PageBaseType = value; + get => Pages.BaseClassTypeName; + set => Pages.BaseClassTypeName = value; } LanguageType ISparkSettings.DefaultLanguage => Compilation.DefaultLanguage; @@ -118,7 +118,9 @@ IEnumerable ISparkSettings.UseNamespaces get { foreach (NamespaceElement ns in Pages.Namespaces) + { yield return ns.Namespace; + } } } @@ -126,8 +128,21 @@ IEnumerable ISparkSettings.UseAssemblies { get { - foreach (AssemblyElement assembly in Compilation.Assemblies) - yield return assembly.Assembly; + foreach (AssemblyElement include in Compilation.Assemblies) + { + yield return include.Assembly; + } + } + } + + IEnumerable ISparkSettings.ExcludeAssemblies + { + get + { + foreach (ExcludeAssemblyElement exclude in Compilation.ExcludeAssemblies) + { + yield return exclude.Assembly; + } } } @@ -136,7 +151,9 @@ IEnumerable ISparkSettings.ResourceMappings get { foreach (ResourcePathElement resource in Pages.Resources) + { yield return new SimpleResourceMapping { Match = resource.Match, Location = resource.Location }; + } } } @@ -145,7 +162,9 @@ IEnumerable ISparkSettings.ViewFolders get { foreach (ViewFolderElement viewFolder in Views) + { yield return viewFolder; + } } } } diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/BatchCompiler.cs index 16bc25ce..01ae0da7 100644 --- a/src/Spark/Compiler/BatchCompiler.cs +++ b/src/Spark/Compiler/BatchCompiler.cs @@ -25,10 +25,11 @@ public class CodeDomBatchCompiler : IBatchCompiler /// E.g. "csharp" or "visualbasic" /// E.g. "File.Name.dll" (optional) /// The source code to compile. + /// The full name of assemblies to exclude. /// /// /// - public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode, IEnumerable excludeAssemblies) { var language = languageOrExtension; if (CodeDomProvider.IsDefinedLanguage(languageOrExtension) == false && @@ -73,6 +74,11 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss continue; } + if (excludeAssemblies.Contains(assembly.FullName)) + { + continue; + } + compilerParameters.ReferencedAssemblies.Add(assembly.Location); } @@ -302,8 +308,9 @@ public void AddNetFrameworkDefaultReferences() /// E.g. "csharp" or "visualbasic" /// E.g. "File.Name.dll" (optional) /// The source code to compile. + /// The full name of assemblies to exclude. /// - public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode) + public Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode, IEnumerable excludeAssemblies) { Assembly assembly = null; @@ -330,6 +337,7 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss this.AddNetCoreDefaultReferences(); #endif + // TODO: Is this needed? this.AddAssembly(typeof(System.Drawing.Color)); foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) @@ -339,6 +347,11 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss continue; } + if (excludeAssemblies.Contains(currentAssembly.FullName)) + { + continue; + } + var reference = MetadataReference.CreateFromFile(currentAssembly.Location); this.References.Add(reference); diff --git a/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs b/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs index 76abcec8..6e5c869f 100644 --- a/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs +++ b/src/Spark/Compiler/CSharp/CSharpViewCompiler.cs @@ -20,13 +20,13 @@ namespace Spark.Compiler.CSharp { - public class CSharpViewCompiler(IBatchCompiler compiler) : ViewCompiler + public class CSharpViewCompiler(IBatchCompiler compiler, ISparkSettings settings) : ViewCompiler() { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { GenerateSourceCode(viewTemplates, allResources); - var assembly = compiler.Compile(Debug, "csharp", null, new[] { SourceCode }); + var assembly = compiler.Compile(settings.Debug, "csharp", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } @@ -41,16 +41,16 @@ public override void GenerateSourceCode( var source = new SourceWriter(writer); var usingGenerator = new UsingNamespaceVisitor(source); - var baseClassGenerator = new BaseClassVisitor { BaseClass = BaseClass }; - var globalsGenerator = new GlobalMembersVisitor(source, globalSymbols, NullBehaviour); + var baseClassGenerator = new BaseClassVisitor { BaseClass = settings.BaseClassTypeName }; + var globalsGenerator = new GlobalMembersVisitor(source, globalSymbols, settings.NullBehaviour); // using ; - foreach (var ns in UseNamespaces ?? Array.Empty()) + foreach (var ns in settings.UseNamespaces ?? Array.Empty()) { usingGenerator.UsingNamespace(ns); } - foreach (var assembly in UseAssemblies ?? Array.Empty()) + foreach (var assembly in settings.UseAssemblies ?? Array.Empty()) { usingGenerator.UsingAssembly(assembly); } @@ -162,7 +162,7 @@ public override void GenerateSourceCode( source.Write("private void RenderViewLevel").Write(renderLevel.ToString()).WriteLine("()"); source.WriteLine("{").AddIndent(); - var viewGenerator = new GeneratedCodeVisitor(source, globalSymbols, NullBehaviour); + var viewGenerator = new GeneratedCodeVisitor(source, globalSymbols, settings.NullBehaviour); viewGenerator.Accept(viewTemplate); source.RemoveIndent().WriteLine("}"); ++renderLevel; diff --git a/src/Spark/Compiler/IBatchCompiler.cs b/src/Spark/Compiler/IBatchCompiler.cs index 79ac9bf5..153ddcf5 100644 --- a/src/Spark/Compiler/IBatchCompiler.cs +++ b/src/Spark/Compiler/IBatchCompiler.cs @@ -12,7 +12,8 @@ public interface IBatchCompiler /// E.g. "csharp" or "visualbasic" /// E.g. "File.Name.dll" (optional) /// The source code to compile. + /// The full names of assemblies to exclude. /// - Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode); + Assembly Compile(bool debug, string languageOrExtension, string outputAssembly, IEnumerable sourceCode, IEnumerable excludeAssemblies); } } diff --git a/src/Spark/Compiler/Javascript/JavascriptViewCompiler.cs b/src/Spark/Compiler/Javascript/JavascriptViewCompiler.cs index 9d048c4e..51dfd0b9 100644 --- a/src/Spark/Compiler/Javascript/JavascriptViewCompiler.cs +++ b/src/Spark/Compiler/Javascript/JavascriptViewCompiler.cs @@ -21,7 +21,7 @@ namespace Spark.Compiler.Javascript { - public class JavascriptViewCompiler : ViewCompiler + public class JavascriptViewCompiler() : ViewCompiler() { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { @@ -45,10 +45,15 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, // convert some syntax from csharp to javascript foreach (var template in viewTemplates) + { anonymousTypeVisitor.Accept(template); + } + foreach (var template in allResources) + { anonymousTypeVisitor.Accept(template); - + } + var cumulativeName = "window.Spark"; foreach (var part in nameParts.Where(p => p != "~")) { @@ -119,7 +124,10 @@ static string SafeName(string name) { var safeName = name; if (safeName.EndsWith(".spark")) + { safeName = safeName.Substring(0, safeName.Length - ".spark".Length); + } + return safeName.Replace(".", ""); } } diff --git a/src/Spark/Compiler/Roslyn/CSharpLink.cs b/src/Spark/Compiler/Roslyn/CSharpLink.cs index 04193d87..a86791d3 100644 --- a/src/Spark/Compiler/Roslyn/CSharpLink.cs +++ b/src/Spark/Compiler/Roslyn/CSharpLink.cs @@ -44,11 +44,9 @@ public Assembly Compile(bool debug, string assemblyName, string outputAssembly, .WithOptimizationLevel(optimizationLevel) .WithPlatform(Platform.AnyCpu); - var compilation = CSharpCompilation.Create( - assemblyName, - syntaxTrees: syntaxTrees, - references: references, - options: options); + var compilation = CSharpCompilation.Create(assemblyName, options: options) + .AddSyntaxTrees(syntaxTrees) + .AddReferences(references); EmitResult result; Assembly assembly = null; diff --git a/src/Spark/Compiler/Roslyn/VisualBasicLink.cs b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs index 4c484eb2..65586654 100644 --- a/src/Spark/Compiler/Roslyn/VisualBasicLink.cs +++ b/src/Spark/Compiler/Roslyn/VisualBasicLink.cs @@ -22,12 +22,10 @@ public Assembly Compile(bool debug, string assemblyName, string outputAssembly, .WithOptimizationLevel(optimizationLevel) .WithPlatform(Platform.AnyCpu); - var compilation = VisualBasicCompilation.Create( - assemblyName, - syntaxTrees: syntaxTrees, - references: references, - options: options); - + var compilation = VisualBasicCompilation.Create(assemblyName, options: options) + .AddSyntaxTrees(syntaxTrees) + .AddReferences(references); + EmitResult result; Assembly assembly = null; diff --git a/src/Spark/Compiler/ViewCompiler.cs b/src/Spark/Compiler/ViewCompiler.cs index 9f533a3b..3521b6fb 100644 --- a/src/Spark/Compiler/ViewCompiler.cs +++ b/src/Spark/Compiler/ViewCompiler.cs @@ -25,7 +25,6 @@ protected ViewCompiler() GeneratedViewId = Guid.NewGuid(); } - public string BaseClass { get; set; } public SparkViewDescriptor Descriptor { get; set; } public string ViewClassFullName { get; set; } @@ -34,11 +33,6 @@ protected ViewCompiler() public Type CompiledType { get; set; } public Guid GeneratedViewId { get; set; } - public bool Debug { get; set; } - public NullBehaviour NullBehaviour { get; set; } - public IEnumerable UseNamespaces { get; set; } - public IEnumerable UseAssemblies { get; set; } - public string TargetNamespace => Descriptor?.TargetNamespace; public abstract void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources); diff --git a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs index 2504c855..43c41c7e 100644 --- a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs +++ b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs @@ -20,13 +20,13 @@ namespace Spark.Compiler.VisualBasic { - public class VisualBasicViewCompiler(IBatchCompiler batchCompiler) : ViewCompiler + public class VisualBasicViewCompiler(IBatchCompiler batchCompiler, ISparkSettings settings) : ViewCompiler() { public override void CompileView(IEnumerable> viewTemplates, IEnumerable> allResources) { GenerateSourceCode(viewTemplates, allResources); - var assembly = batchCompiler.Compile(Debug, "visualbasic", null, new[] { SourceCode }); + var assembly = batchCompiler.Compile(settings.Debug, "visualbasic", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } @@ -41,25 +41,33 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source.AdjustDebugSymbols = false; var usingGenerator = new UsingNamespaceVisitor(source); - var baseClassGenerator = new BaseClassVisitor { BaseClass = BaseClass }; - var globalsGenerator = new GlobalMembersVisitor(source, globalSymbols, NullBehaviour); + var baseClassGenerator = new BaseClassVisitor { BaseClass = settings.BaseClassTypeName }; + var globalsGenerator = new GlobalMembersVisitor(source, globalSymbols, settings.NullBehaviour); // needed for proper vb functionality source.WriteLine("Option Infer On"); usingGenerator.UsingNamespace("Microsoft.VisualBasic"); - foreach (var ns in UseNamespaces ?? Array.Empty()) + foreach (var ns in settings.UseNamespaces ?? Array.Empty()) + { usingGenerator.UsingNamespace(ns); + } usingGenerator.UsingAssembly("Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); - foreach (var assembly in UseAssemblies ?? Array.Empty()) + foreach (var assembly in settings.UseAssemblies ?? Array.Empty()) + { usingGenerator.UsingAssembly(assembly); + } foreach (var resource in allResources) + { usingGenerator.Accept(resource); + } foreach (var resource in allResources) + { baseClassGenerator.Accept(resource); + } var viewClassName = "View" + GeneratedViewId.ToString("n"); @@ -82,7 +90,10 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, // [SparkView] attribute source.WriteLine(" "\"" + SparkViewAttribute.ConvertToAttributeFormat(t) + "\"").ToArray())); @@ -125,7 +136,9 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, // properties and macros foreach (var resource in allResources) + { globalsGenerator.Accept(resource); + } // public void RenderViewLevelx() int renderLevel = 0; @@ -136,7 +149,7 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source .WriteLine("Private Sub RenderViewLevel{0}()", renderLevel) .AddIndent(); - var viewGenerator = new GeneratedCodeVisitor(source, globalSymbols, NullBehaviour); + var viewGenerator = new GeneratedCodeVisitor(source, globalSymbols, settings.NullBehaviour); viewGenerator.Accept(viewTemplate); source .RemoveIndent() diff --git a/src/Spark/CompositeViewEntry.cs b/src/Spark/CompositeViewEntry.cs index c68a85f2..4a0c8734 100644 --- a/src/Spark/CompositeViewEntry.cs +++ b/src/Spark/CompositeViewEntry.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using Spark.Compiler; using Spark.Parser; namespace Spark { + [DebuggerDisplay("{ViewId}")] public class CompositeViewEntry : ISparkViewEntry { - public CompositeViewEntry() - { - ViewId = Guid.NewGuid(); - } - - public Guid ViewId { get; set; } + public Guid ViewId { get; set; } = Guid.NewGuid(); public SparkViewDescriptor Descriptor { get; set; } public ViewLoader Loader { get; set; } @@ -22,7 +17,6 @@ public CompositeViewEntry() public IViewActivator Activator { get; set; } public ISparkLanguageFactory LanguageFactory { get; set; } - public ISparkView CreateInstance() { throw new System.NotImplementedException(); @@ -38,14 +32,8 @@ public bool IsCurrent() throw new System.NotImplementedException(); } - public string SourceCode - { - get { throw new System.NotImplementedException(); } - } + public string SourceCode => throw new System.NotImplementedException(); - public IList SourceMappings - { - get { throw new System.NotImplementedException(); } - } + public IList SourceMappings => throw new System.NotImplementedException(); } } diff --git a/src/Spark/DefaultLanguageFactory.cs b/src/Spark/DefaultLanguageFactory.cs index 7800896a..69ae1fd8 100644 --- a/src/Spark/DefaultLanguageFactory.cs +++ b/src/Spark/DefaultLanguageFactory.cs @@ -19,16 +19,10 @@ namespace Spark { - public class DefaultLanguageFactory(IBatchCompiler batchCompiler) : ISparkLanguageFactory + public class DefaultLanguageFactory(IBatchCompiler batchCompiler, ISparkSettings settings) : ISparkLanguageFactory { public virtual ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkViewDescriptor descriptor) { - var pageBaseType = engine.Settings.PageBaseType; - if (string.IsNullOrEmpty(pageBaseType)) - { - pageBaseType = engine.DefaultPageBaseType; - } - var language = descriptor.Language; if (language == LanguageType.Default) { @@ -40,10 +34,10 @@ public virtual ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkVie { case LanguageType.Default: case LanguageType.CSharp: - viewCompiler = new CSharpViewCompiler(batchCompiler); + viewCompiler = new CSharpViewCompiler(batchCompiler, settings); break; case LanguageType.VisualBasic: - viewCompiler = new VisualBasicViewCompiler(batchCompiler); + viewCompiler = new VisualBasicViewCompiler(batchCompiler, settings); break; case LanguageType.Javascript: viewCompiler = new JavascriptViewCompiler(); @@ -52,12 +46,7 @@ public virtual ViewCompiler CreateViewCompiler(ISparkViewEngine engine, SparkVie throw new CompilerException($"Unknown language type {descriptor.Language}"); } - viewCompiler.BaseClass = pageBaseType; viewCompiler.Descriptor = descriptor; - viewCompiler.Debug = engine.Settings.Debug; - viewCompiler.NullBehaviour = engine.Settings.NullBehaviour; - viewCompiler.UseAssemblies = engine.Settings.UseAssemblies; - viewCompiler.UseNamespaces = engine.Settings.UseNamespaces; return viewCompiler; } diff --git a/src/Spark/ISparkSettings.cs b/src/Spark/ISparkSettings.cs index 863cb2fc..864dd533 100644 --- a/src/Spark/ISparkSettings.cs +++ b/src/Spark/ISparkSettings.cs @@ -52,11 +52,20 @@ public interface ISparkSettings : IParserSettings bool Debug { get; } NullBehaviour NullBehaviour { get; } string Prefix { get; } - string PageBaseType { get; set; } + string BaseClassTypeName { get; set; } LanguageType DefaultLanguage { get; } IEnumerable UseNamespaces { get; } IEnumerable UseAssemblies { get; } + + /// + /// A list of assemblies to avoid adding as reference when compiling views (e.g. any precompiled assemblies) + /// + /// + /// Leave this empty unless you are pre-compiling views. Example value: "HelloWorld.Views.dll" + /// + IEnumerable ExcludeAssemblies { get; } + IEnumerable ResourceMappings { get; } IEnumerable ViewFolders { get; } bool ParseSectionTagAsSegment { get; } diff --git a/src/Spark/ISparkViewEngine.cs b/src/Spark/ISparkViewEngine.cs index 4d140b6a..c0fd1ef5 100644 --- a/src/Spark/ISparkViewEngine.cs +++ b/src/Spark/ISparkViewEngine.cs @@ -25,7 +25,6 @@ public interface ISparkViewEngine IViewFolder ViewFolder { get; set; } IViewActivatorFactory ViewActivatorFactory { get; } - string DefaultPageBaseType { get; } ISparkSyntaxProvider SyntaxProvider { get; } ISparkViewEntry GetEntry(SparkViewDescriptor descriptor); diff --git a/src/Spark/SparkSettings.cs b/src/Spark/SparkSettings.cs index 8434656a..02ee65dd 100644 --- a/src/Spark/SparkSettings.cs +++ b/src/Spark/SparkSettings.cs @@ -39,6 +39,7 @@ public SparkSettings(string rootPath) _useNamespaces = new List(); _useAssemblies = new List(); + _excludeAssemblies = new List(); _resourceMappings = new List(); _viewFolders = new List(); NullBehaviour = NullBehaviour.Lenient; @@ -57,7 +58,7 @@ public SparkSettings(string rootPath) public bool AutomaticEncoding { get; set; } public string StatementMarker { get; set; } public string Prefix { get; set; } - public string PageBaseType { get; set; } + public string BaseClassTypeName { get; set; } public LanguageType DefaultLanguage { get; set; } public bool ParseSectionTagAsSegment { get; set; } @@ -69,6 +70,9 @@ public SparkSettings(string rootPath) private readonly IList _useAssemblies; public IEnumerable UseAssemblies => _useAssemblies; + private readonly IList _excludeAssemblies; + public IEnumerable ExcludeAssemblies => _excludeAssemblies; + private readonly IList _resourceMappings; public IEnumerable ResourceMappings => _resourceMappings; @@ -108,9 +112,9 @@ public SparkSettings SetNullBehaviour(NullBehaviour nullBehaviour) ///
/// The full name of the type. /// - public SparkSettings SetPageBaseType(string typeName) + public SparkSettings SetBaseClassTypeName(string typeName) { - PageBaseType = typeName; + BaseClassTypeName = typeName; return this; } @@ -120,9 +124,9 @@ public SparkSettings SetPageBaseType(string typeName) ///
/// The type. /// - public SparkSettings SetPageBaseType(Type type) + public SparkSettings SetBaseClassTypeName(Type type) { - PageBaseType = type.FullName; + BaseClassTypeName = type.FullName; return this; } @@ -151,6 +155,9 @@ public SparkSettings AddAssembly(string assembly) return this; } + /// + /// Adds an assembly for the view compiler to be aware of. + /// public SparkSettings AddAssembly(Assembly assembly) { _useAssemblies.Add(assembly.FullName); @@ -158,6 +165,28 @@ public SparkSettings AddAssembly(Assembly assembly) return this; } + /// + /// Adds the full name of an assembly for the view compiler to exclude (it won't be added as a reference when compiling). + /// + /// The full name of an assembly. + /// + public SparkSettings ExcludeAssembly(string assembly) + { + this._excludeAssemblies.Add(assembly); + + return this; + } + + /// + /// Keeps track of an assembly for the view compiler to exclude (it won't be added as a reference when compiling). + /// + public SparkSettings ExcludeAssembly(Assembly assembly) + { + this._excludeAssemblies.Add(assembly.FullName); + + return this; + } + public SparkSettings AddNamespace(string ns) { _useNamespaces.Add(ns); diff --git a/src/Spark/SparkViewEngine.cs b/src/Spark/SparkViewEngine.cs index eb005f93..88c2730d 100644 --- a/src/Spark/SparkViewEngine.cs +++ b/src/Spark/SparkViewEngine.cs @@ -70,8 +70,6 @@ public SparkViewEngine( SparkExtensionFactory = sparkExtensionFactory; } - public string DefaultPageBaseType => Settings.PageBaseType; - private IViewFolder InitialiseAggregateViewFolder(ISparkSettings settings, IViewFolder value) { var aggregateViewFolder = value; @@ -257,7 +255,7 @@ public Assembly BatchCompilation(string outputAssembly, IList LoadBatchCompilation(Assembly assembly) { Descriptor = descriptor, Loader = this.CreateViewLoader(), - Compiler = new CSharpViewCompiler(this.BatchCompiler) { CompiledType = type }, + Compiler = new CSharpViewCompiler(this.BatchCompiler, this.Settings) { CompiledType = type }, Activator = ViewActivatorFactory.Register(type) }; diff --git a/src/Xpark/Program.cs b/src/Xpark/Program.cs index 0ce4435f..95e32504 100644 --- a/src/Xpark/Program.cs +++ b/src/Xpark/Program.cs @@ -47,7 +47,7 @@ The Model in the template is an XDocument loaded from the source. viewFolder.Append(new SubViewFolder(viewFolder, "Shared")); var settings = new SparkSettings() - .SetPageBaseType(typeof(SparkView)); + .SetBaseClassTypeName(typeof(SparkView)); var partialProvider = new DefaultPartialProvider(); @@ -57,7 +57,7 @@ The Model in the template is an XDocument loaded from the source. settings, new DefaultSyntaxProvider(settings), new DefaultViewActivator(), - new DefaultLanguageFactory(batchCompiler), + new DefaultLanguageFactory(batchCompiler, settings), new CompiledViewHolder(), viewFolder, batchCompiler, From ace12218d930acd8e545c6ee94c7a03e2f9e8b72 Mon Sep 17 00:00:00 2001 From: bounav Date: Wed, 28 Feb 2024 09:46:18 +0000 Subject: [PATCH 08/14] Moved Markdown package reference to Spark.Web.csproj --- src/Spark.Web/Spark.Web.csproj | 3 +++ src/Spark.Web/SparkViewBase.cs | 1 - src/Spark/Spark.csproj | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Spark.Web/Spark.Web.csproj b/src/Spark.Web/Spark.Web.csproj index 136e0f29..d76470b1 100644 --- a/src/Spark.Web/Spark.Web.csproj +++ b/src/Spark.Web/Spark.Web.csproj @@ -28,6 +28,9 @@ + + + diff --git a/src/Spark.Web/SparkViewBase.cs b/src/Spark.Web/SparkViewBase.cs index 4d461e59..159b4534 100644 --- a/src/Spark.Web/SparkViewBase.cs +++ b/src/Spark.Web/SparkViewBase.cs @@ -242,5 +242,4 @@ protected virtual void DelegateFirstRender(Action render) render(); } } - } diff --git a/src/Spark/Spark.csproj b/src/Spark/Spark.csproj index bebc0fa5..81e988fe 100644 --- a/src/Spark/Spark.csproj +++ b/src/Spark/Spark.csproj @@ -29,7 +29,6 @@ - From 23b5dd45feceafa42ffaba22ff796e1d05ff57f4 Mon Sep 17 00:00:00 2001 From: bounav Date: Wed, 28 Feb 2024 09:49:16 +0000 Subject: [PATCH 09/14] SparkViewEngine.ActiveViewFolder() is now an extension method --- src/Spark/SparkViewEngine.cs | 41 +------------------- src/Spark/ViewFolderSettingsExtensions.cs | 47 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 src/Spark/ViewFolderSettingsExtensions.cs diff --git a/src/Spark/SparkViewEngine.cs b/src/Spark/SparkViewEngine.cs index 88c2730d..f5a6010c 100644 --- a/src/Spark/SparkViewEngine.cs +++ b/src/Spark/SparkViewEngine.cs @@ -13,7 +13,6 @@ // limitations under the License. // using System; -using System.Linq; using System.Collections.Generic; using System.Reflection; using Spark.Bindings; @@ -78,7 +77,7 @@ private IViewFolder InitialiseAggregateViewFolder(ISparkSettings settings, IView { foreach (var viewFolderSettings in settings.ViewFolders) { - IViewFolder viewFolder = this.ActivateViewFolder(viewFolderSettings); + IViewFolder viewFolder = viewFolderSettings.ActivateViewFolder(); if (!string.IsNullOrEmpty(viewFolderSettings.Subfolder)) { @@ -92,44 +91,6 @@ private IViewFolder InitialiseAggregateViewFolder(ISparkSettings settings, IView return aggregateViewFolder; } - private IViewFolder ActivateViewFolder(IViewFolderSettings viewFolderSettings) - { - var type = Type.GetType(viewFolderSettings.Type); - - ConstructorInfo bestConstructor = null; - foreach (var constructor in type.GetConstructors()) - { - if (bestConstructor == null || bestConstructor.GetParameters().Length < constructor.GetParameters().Length) - { - if (constructor.GetParameters().All(param => viewFolderSettings.Parameters.ContainsKey(param.Name))) - { - bestConstructor = constructor; - } - } - } - - if (bestConstructor == null) - { - throw new MissingMethodException($"No suitable constructor for {type.FullName} located"); - } - - var args = bestConstructor.GetParameters() - .Select(param => this.ChangeType(viewFolderSettings, param)) - .ToArray(); - - return (IViewFolder)Activator.CreateInstance(type, args); - } - - private object ChangeType(IViewFolderSettings viewFolderSettings, ParameterInfo param) - { - if (param.ParameterType == typeof(Assembly)) - { - return Assembly.Load(viewFolderSettings.Parameters[param.Name]); - } - - return Convert.ChangeType(viewFolderSettings.Parameters[param.Name], param.ParameterType); - } - public ISparkViewEntry GetEntry(SparkViewDescriptor descriptor) { return CompiledViewHolder.Lookup(descriptor); diff --git a/src/Spark/ViewFolderSettingsExtensions.cs b/src/Spark/ViewFolderSettingsExtensions.cs new file mode 100644 index 00000000..45c0fc2a --- /dev/null +++ b/src/Spark/ViewFolderSettingsExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Reflection; +using Spark.FileSystem; + +namespace Spark; + +public static class ViewFolderSettingsExtensions +{ + public static IViewFolder ActivateViewFolder(this IViewFolderSettings viewFolderSettings) + { + var type = Type.GetType(viewFolderSettings.Type); + + ConstructorInfo bestConstructor = null; + foreach (var constructor in type.GetConstructors()) + { + if (bestConstructor == null || bestConstructor.GetParameters().Length < constructor.GetParameters().Length) + { + if (constructor.GetParameters().All(param => viewFolderSettings.Parameters.ContainsKey(param.Name))) + { + bestConstructor = constructor; + } + } + } + + if (bestConstructor == null) + { + throw new MissingMethodException($"No suitable constructor for {type.FullName} located"); + } + + var args = bestConstructor.GetParameters() + .Select(viewFolderSettings.ChangeType) + .ToArray(); + + return (IViewFolder)Activator.CreateInstance(type, args); + } + + private static object ChangeType(this IViewFolderSettings viewFolderSettings, ParameterInfo param) + { + if (param.ParameterType == typeof(Assembly)) + { + return Assembly.Load(viewFolderSettings.Parameters[param.Name]); + } + + return Convert.ChangeType(viewFolderSettings.Parameters[param.Name], param.ParameterType); + } +} \ No newline at end of file From d8152fb566c465ff1091dbae4028a3a01b64f158 Mon Sep 17 00:00:00 2001 From: bounav Date: Thu, 29 Feb 2024 12:24:37 +0000 Subject: [PATCH 10/14] Removed DefaultCacheServiceProvider - Replaced by a callback in the IoC configuration - See ServiceCollectionExtensions.cs --- src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs | 5 +++-- src/Spark.Web.Mvc/DefaultCacheServiceProvider.cs | 15 --------------- .../Descriptors/AreaDescriptorFilter.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 12 +++++++++++- src/Spark.Web.Mvc/ICacheServiceProvider.cs | 9 --------- src/Spark.Web.Mvc/SparkViewFactory.cs | 14 +++++++------- src/Spark.Web/Caching/DefaultCacheService.cs | 5 ----- 7 files changed, 22 insertions(+), 40 deletions(-) delete mode 100644 src/Spark.Web.Mvc/DefaultCacheServiceProvider.cs delete mode 100644 src/Spark.Web.Mvc/ICacheServiceProvider.cs diff --git a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs index e541eef8..a712147d 100644 --- a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs @@ -516,7 +516,7 @@ public void CreatingViewEngineWithSimpleContainer() var viewEngine = sp.GetService(); var viewFolder = sp.GetService(); var descriptorBuilder = sp.GetService(); - var cacheServiceProvider = sp.GetService(); + var cacheService = sp.GetService(); var viewActivatorFactory = sp.GetService(); Assert.AreSame(settings, viewFactory.Settings); @@ -525,7 +525,7 @@ public void CreatingViewEngineWithSimpleContainer() Assert.AreSame(viewFolder, viewEngine.ViewFolder); Assert.AreSame(viewFolder, viewFactory.Engine.ViewFolder); Assert.AreSame(descriptorBuilder, viewFactory.DescriptorBuilder); - Assert.AreSame(cacheServiceProvider, viewFactory.CacheServiceProvider); + Assert.AreSame(cacheService, viewFactory.CacheService); Assert.AreSame(viewActivatorFactory, viewFactory.ViewActivatorFactory); } @@ -705,6 +705,7 @@ public class RenderActionController : Controller { public ActionResult Header() { + // ReSharper disable once Mvc.ViewNotResolved return View("FuturesRenderActionCanRunThroughItsProcess_Header"); } } diff --git a/src/Spark.Web.Mvc/DefaultCacheServiceProvider.cs b/src/Spark.Web.Mvc/DefaultCacheServiceProvider.cs deleted file mode 100644 index 5a707808..00000000 --- a/src/Spark.Web.Mvc/DefaultCacheServiceProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Web.Routing; -using Spark.Caching; - -namespace Spark.Web.Mvc -{ - public class DefaultCacheServiceProvider : ICacheServiceProvider - { - public ICacheService GetCacheService(RequestContext context) - { - if (context.HttpContext != null && context.HttpContext.Cache != null) - return new DefaultCacheService(context.HttpContext.Cache); - return null; - } - } -} diff --git a/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs b/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs index 6013ff6a..81557c10 100644 --- a/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs +++ b/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs @@ -24,7 +24,7 @@ public override IEnumerable PotentialLocations(IEnumerable locat : locations; } - + private static string GetAreaName(RouteBase route) { var routeWithArea = route as IRouteWithArea; diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs index 6caa0205..a99a77bb 100644 --- a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Configuration; +using System.Web; using Microsoft.Extensions.DependencyInjection; using Spark.Bindings; +using Spark.Caching; using Spark.Compiler; using Spark.Compiler.Roslyn; using Spark.FileSystem; @@ -63,7 +65,15 @@ public static IServiceCollection AddSpark(this IServiceCollection services, ISpa services .AddSingleton() - .AddSingleton() + .AddTransient(sp => + { + if (HttpContext.Current != null && HttpContext.Current.Cache != null) + { + return new DefaultCacheService(HttpContext.Current.Cache); + } + + return null; + }) .AddSingleton(); return services; diff --git a/src/Spark.Web.Mvc/ICacheServiceProvider.cs b/src/Spark.Web.Mvc/ICacheServiceProvider.cs deleted file mode 100644 index 43cab076..00000000 --- a/src/Spark.Web.Mvc/ICacheServiceProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Web.Routing; - -namespace Spark.Web.Mvc -{ - public interface ICacheServiceProvider - { - ICacheService GetCacheService(RequestContext context); - } -} \ No newline at end of file diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 99d237e6..0d193a95 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -31,7 +31,7 @@ public class SparkViewFactory : IViewEngine, IViewFolderContainer public ISparkViewEngine Engine { get; protected set; } public IDescriptorBuilder DescriptorBuilder { get; protected set; } public IResourcePathManager ResourcePathManager { get; protected set; } - public ICacheServiceProvider CacheServiceProvider { get; protected set; } + public ICacheService CacheService { get; protected set; } private readonly Dictionary _cache; private readonly ViewEngineResult _cacheMissResult; @@ -40,7 +40,7 @@ public SparkViewFactory(ISparkSettings settings, ISparkViewEngine viewEngine, IDescriptorBuilder descriptorBuilder, IResourcePathManager resourcePathManager, - ICacheServiceProvider cacheServiceProvider) + ICacheService cacheService) { Settings = settings; @@ -52,7 +52,7 @@ public SparkViewFactory(ISparkSettings settings, Engine = viewEngine; DescriptorBuilder = descriptorBuilder; ResourcePathManager = resourcePathManager; - CacheServiceProvider = cacheServiceProvider; + CacheService = cacheService; _cache = new Dictionary(); _cacheMissResult = new ViewEngineResult(Array.Empty()); @@ -108,7 +108,7 @@ private ViewEngineResult FindViewInternal(ControllerContext controllerContext, s { if (TryGetCacheValue(descriptorParams, out entry) && entry.IsCurrent()) { - return BuildResult(controllerContext.RequestContext, entry); + return BuildResult(entry); } return _cacheMissResult; @@ -127,7 +127,7 @@ private ViewEngineResult FindViewInternal(ControllerContext controllerContext, s SetCacheValue(descriptorParams, entry); - return BuildResult(controllerContext.RequestContext, entry); + return BuildResult(entry); } private bool TryGetCacheValue(BuildDescriptorParams descriptorParams, out ISparkViewEntry entry) @@ -140,14 +140,14 @@ private void SetCacheValue(BuildDescriptorParams descriptorParams, ISparkViewEnt lock (_cache) _cache[descriptorParams] = entry; } - private ViewEngineResult BuildResult(RequestContext requestContext, ISparkViewEntry entry) + private ViewEngineResult BuildResult(ISparkViewEntry entry) { var view = (IView)entry.CreateInstance(); if (view is SparkView sparkView) { sparkView.ResourcePathManager = ResourcePathManager; - sparkView.CacheService = CacheServiceProvider.GetCacheService(requestContext); + sparkView.CacheService = CacheService; } return new ViewEngineResult(view, this); diff --git a/src/Spark.Web/Caching/DefaultCacheService.cs b/src/Spark.Web/Caching/DefaultCacheService.cs index e692259d..2a1a4785 100644 --- a/src/Spark.Web/Caching/DefaultCacheService.cs +++ b/src/Spark.Web/Caching/DefaultCacheService.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Caching; namespace Spark.Caching @@ -60,7 +57,5 @@ protected override void DependencyDispose() _signal.Changed -= SignalChanged; } } - } - } From 09e540ca1d9e680c4bff9514867df7afd176920d Mon Sep 17 00:00:00 2001 From: bounav Date: Thu, 29 Feb 2024 16:26:59 +0000 Subject: [PATCH 11/14] New InMemoryCacheService implementation of ICacheService in Spark - InMemoryCacheService cache can be used in any .net standard project - CacheExpires class not longer has depenency on System.Web.Caching.Cache - Renamed DefaultCacheService to WebCacheService - Moved NullCacheService to Castle.MonoRail.Views project (it's the only place using it) - Moved some classes back to Spark project when possible - Moved markdown dependency back to spark --- .../NullCacheService.cs | 0 .../Wrappers/HybridCacheService.cs | 2 +- src/Spark.Tests/InMemoryServiceTest.cs | 88 +++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Caching/CacheElementTester.cs | 1 - src/Spark.Web/CacheExpires.cs | 39 -------- .../Caching/SpoolWriterOriginator.cs | 38 -------- .../Caching/StringWriterOriginator.cs | 39 -------- src/Spark.Web/Spark.Web.csproj | 3 - ...aultCacheService.cs => WebCacheService.cs} | 14 +-- src/{Spark.Web => Spark}/AbstractSparkView.cs | 0 src/Spark/CacheExpires.cs | 60 +++++++++++++ src/{Spark.Web => Spark}/CacheSignal.cs | 12 +-- .../Caching/CacheMemento.cs | 13 +-- .../Caching/CacheOriginator.cs | 39 ++++---- src/Spark/Caching/SpoolWriterOriginator.cs | 31 +++++++ src/Spark/Caching/StringWriterOriginator.cs | 32 +++++++ .../Caching/TextWriterOriginator.cs | 6 ++ .../ChunkVisitors/GeneratedCodeVisitor.cs | 1 + .../DetectCodeExpressionVisitor.cs | 7 +- src/{Spark.Web => Spark}/ICacheService.cs | 2 - src/Spark/InMemoryCacheService.cs | 71 +++++++++++++++ src/Spark/Spark.csproj | 6 +- src/{Spark.Web => Spark}/SparkViewBase.cs | 0 .../SparkViewDecorator.cs | 0 25 files changed, 330 insertions(+), 176 deletions(-) rename src/{Spark.Web => Castle.MonoRail.Views.Spark}/NullCacheService.cs (100%) create mode 100644 src/Spark.Tests/InMemoryServiceTest.cs delete mode 100644 src/Spark.Web/CacheExpires.cs delete mode 100644 src/Spark.Web/Caching/SpoolWriterOriginator.cs delete mode 100644 src/Spark.Web/Caching/StringWriterOriginator.cs rename src/Spark.Web/{Caching/DefaultCacheService.cs => WebCacheService.cs} (83%) rename src/{Spark.Web => Spark}/AbstractSparkView.cs (100%) create mode 100644 src/Spark/CacheExpires.cs rename src/{Spark.Web => Spark}/CacheSignal.cs (92%) rename src/{Spark.Web => Spark}/Caching/CacheMemento.cs (60%) rename src/{Spark.Web => Spark}/Caching/CacheOriginator.cs (71%) create mode 100644 src/Spark/Caching/SpoolWriterOriginator.cs create mode 100644 src/Spark/Caching/StringWriterOriginator.cs rename src/{Spark.Web => Spark}/Caching/TextWriterOriginator.cs (94%) rename src/{Spark.Web => Spark}/ICacheService.cs (93%) create mode 100644 src/Spark/InMemoryCacheService.cs rename src/{Spark.Web => Spark}/SparkViewBase.cs (100%) rename src/{Spark.Web => Spark}/SparkViewDecorator.cs (100%) diff --git a/src/Spark.Web/NullCacheService.cs b/src/Castle.MonoRail.Views.Spark/NullCacheService.cs similarity index 100% rename from src/Spark.Web/NullCacheService.cs rename to src/Castle.MonoRail.Views.Spark/NullCacheService.cs diff --git a/src/Castle.MonoRail.Views.Spark/Wrappers/HybridCacheService.cs b/src/Castle.MonoRail.Views.Spark/Wrappers/HybridCacheService.cs index f9bab1a3..a8a36ab0 100644 --- a/src/Castle.MonoRail.Views.Spark/Wrappers/HybridCacheService.cs +++ b/src/Castle.MonoRail.Views.Spark/Wrappers/HybridCacheService.cs @@ -32,7 +32,7 @@ public HybridCacheService(IEngineContext context) } else { - _fallbackCacheService = new DefaultCacheService(context.UnderlyingContext.Cache); + _fallbackCacheService = new WebCacheService(context.UnderlyingContext.Cache); } } diff --git a/src/Spark.Tests/InMemoryServiceTest.cs b/src/Spark.Tests/InMemoryServiceTest.cs new file mode 100644 index 00000000..8599b4dd --- /dev/null +++ b/src/Spark.Tests/InMemoryServiceTest.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading; +using Microsoft.Extensions.Caching.Memory; +using NUnit.Framework; + +namespace Spark.Tests +{ + [TestFixture] + public class InMemoryServiceTest + { + [Test] + public void TestStoreValueThenRetrieveIt() + { + var service = new InMemoryCacheService(new MemoryCache(new MemoryCacheOptions())); + + var item = new { }; + + service.Store("identifier", CacheExpires.Empty, null, item); + + var retrieved = service.Get("identifier"); + + Assert.AreSame(item, retrieved); + } + + [Test] + public void TestStoreValueThenRetrieveItAfterAbsoluteExpiration() + { + var service = new InMemoryCacheService(new MemoryCache(new MemoryCacheOptions())); + + var item = new { }; + + service.Store("identifier", new CacheExpires(DateTime.UtcNow.AddMilliseconds(50)), null, item); + + Thread.Sleep(100); + + var retrieved = service.Get("identifier"); + + Assert.IsNull(retrieved); + } + + [Test] + public void TestStoreValueThenRetrieveItWhenExpirationSlides() + { + var service = new InMemoryCacheService(new MemoryCache(new MemoryCacheOptions())); + + var item = new { }; + + service.Store("identifier", new CacheExpires(TimeSpan.FromMilliseconds(75)), null, item); + + object retrieved; + + for (var i = 0; i < 3; i++) + { + Thread.Sleep(50); + + retrieved = service.Get("identifier"); + } + + retrieved = service.Get("identifier"); + + Assert.IsNotNull(retrieved); + + Assert.AreSame(item, retrieved); + } + + [Test] + public void TestStoreValueWithSignal() + { + var service = new InMemoryCacheService(new MemoryCache(new MemoryCacheOptions())); + + var item = new { }; + + var signal = new CacheSignal(); + + service.Store("identifier", null, signal, item); + + var retrieved = service.Get("identifier"); + + Assert.AreSame(item, retrieved); + + signal.FireChanged(); + + retrieved = service.Get("identifier"); + + Assert.IsNull(retrieved); + } + } +} diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs index a99a77bb..fd33e2cc 100644 --- a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -69,7 +69,7 @@ public static IServiceCollection AddSpark(this IServiceCollection services, ISpa { if (HttpContext.Current != null && HttpContext.Current.Cache != null) { - return new DefaultCacheService(HttpContext.Current.Cache); + return new WebCacheService(HttpContext.Current.Cache); } return null; diff --git a/src/Spark.Web.Tests/Caching/CacheElementTester.cs b/src/Spark.Web.Tests/Caching/CacheElementTester.cs index 8624b3c1..d51b261e 100644 --- a/src/Spark.Web.Tests/Caching/CacheElementTester.cs +++ b/src/Spark.Web.Tests/Caching/CacheElementTester.cs @@ -553,7 +553,6 @@ public void SignalWillExpireOutputCachingEntry()

2

")); Assert.That(calls, Is.EqualTo(2)); - } } } diff --git a/src/Spark.Web/CacheExpires.cs b/src/Spark.Web/CacheExpires.cs deleted file mode 100644 index ba91cbca..00000000 --- a/src/Spark.Web/CacheExpires.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace Spark -{ - public class CacheExpires - { - private static CacheExpires _empty = new CacheExpires(); - - public CacheExpires() - { - Absolute = NoAbsoluteExpiration; - Sliding = NoSlidingExpiration; - } - - public CacheExpires(DateTime absolute) - { - Absolute = absolute; - Sliding = NoSlidingExpiration; - } - - public CacheExpires(TimeSpan sliding) - { - Absolute = NoAbsoluteExpiration; - Sliding = sliding; - } - - public CacheExpires(double sliding) - : this(TimeSpan.FromSeconds(sliding)) - { - } - - public DateTime Absolute { get; set; } - public TimeSpan Sliding { get; set; } - - public static DateTime NoAbsoluteExpiration { get { return System.Web.Caching.Cache.NoAbsoluteExpiration; } } - public static TimeSpan NoSlidingExpiration { get { return System.Web.Caching.Cache.NoSlidingExpiration; } } - public static CacheExpires Empty { get { return _empty; } } - } -} \ No newline at end of file diff --git a/src/Spark.Web/Caching/SpoolWriterOriginator.cs b/src/Spark.Web/Caching/SpoolWriterOriginator.cs deleted file mode 100644 index f2055cfb..00000000 --- a/src/Spark.Web/Caching/SpoolWriterOriginator.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Linq; -using Spark.Spool; - -namespace Spark.Caching -{ - public class SpoolWriterOriginator : TextWriterOriginator - { - private readonly SpoolWriter _writer; - private int _priorStringCount; - - public SpoolWriterOriginator(SpoolWriter writer) - { - _writer = writer; - } - - public override TextWriterMemento CreateMemento() - { - return new TextWriterMemento { Written = _writer.ToArray() }; - } - - public override void BeginMemento() - { - _priorStringCount = _writer.Count(); - } - - public override TextWriterMemento EndMemento() - { - return new TextWriterMemento { Written = _writer.Skip(_priorStringCount).ToArray() }; - } - - public override void DoMemento(TextWriterMemento memento) - { - foreach (var written in memento.Written) - _writer.Write(written); - } - } -} \ No newline at end of file diff --git a/src/Spark.Web/Caching/StringWriterOriginator.cs b/src/Spark.Web/Caching/StringWriterOriginator.cs deleted file mode 100644 index 421d0ce3..00000000 --- a/src/Spark.Web/Caching/StringWriterOriginator.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.IO; - -namespace Spark.Caching -{ - public class StringWriterOriginator : TextWriterOriginator - { - private readonly StringWriter _writer; - private int _priorLength; - - public StringWriterOriginator(StringWriter writer) - { - _writer = writer; - } - - public override TextWriterMemento CreateMemento() - { - return new TextWriterMemento {Written = new[] {_writer.ToString()}}; - } - - public override void BeginMemento() - { - _priorLength = _writer.GetStringBuilder().Length; - } - - public override TextWriterMemento EndMemento() - { - var currentLength = _writer.GetStringBuilder().Length; - var written = _writer.GetStringBuilder().ToString(_priorLength, currentLength - _priorLength); - return new TextWriterMemento { Written = new[] { written } }; - } - - public override void DoMemento(TextWriterMemento memento) - { - foreach(var written in memento.Written) - _writer.Write(written); - } - } -} diff --git a/src/Spark.Web/Spark.Web.csproj b/src/Spark.Web/Spark.Web.csproj index d76470b1..136e0f29 100644 --- a/src/Spark.Web/Spark.Web.csproj +++ b/src/Spark.Web/Spark.Web.csproj @@ -28,9 +28,6 @@
- - - diff --git a/src/Spark.Web/Caching/DefaultCacheService.cs b/src/Spark.Web/WebCacheService.cs similarity index 83% rename from src/Spark.Web/Caching/DefaultCacheService.cs rename to src/Spark.Web/WebCacheService.cs index 2a1a4785..bf31bc1f 100644 --- a/src/Spark.Web/Caching/DefaultCacheService.cs +++ b/src/Spark.Web/WebCacheService.cs @@ -1,25 +1,25 @@ using System; using System.Web.Caching; -namespace Spark.Caching +namespace Spark { - public class DefaultCacheService : ICacheService + public class WebCacheService : ICacheService { - private readonly Cache _cache; + private readonly Cache cache; - public DefaultCacheService(Cache cache) + public WebCacheService(Cache cache) { - _cache = cache; + this.cache = cache; } public object Get(string identifier) { - return _cache.Get(identifier); + return this.cache.Get(identifier); } public void Store(string identifier, CacheExpires expires, ICacheSignal signal, object item) { - _cache.Insert( + this.cache.Insert( identifier, item, SignalDependency.For(signal), diff --git a/src/Spark.Web/AbstractSparkView.cs b/src/Spark/AbstractSparkView.cs similarity index 100% rename from src/Spark.Web/AbstractSparkView.cs rename to src/Spark/AbstractSparkView.cs diff --git a/src/Spark/CacheExpires.cs b/src/Spark/CacheExpires.cs new file mode 100644 index 00000000..a9ed50cb --- /dev/null +++ b/src/Spark/CacheExpires.cs @@ -0,0 +1,60 @@ +using System; + +namespace Spark +{ + /// + /// Represents when a cached entry should expire. + /// + public class CacheExpires + { + /// + /// Constructor for a non expiring cached entry. + /// + public CacheExpires() + { + Absolute = NoAbsoluteExpiration; + Sliding = NoSlidingExpiration; + } + + /// + /// Constructor for a non cached entry expiring at a specified time. + /// + /// The time when to invalidate the cached entry. + public CacheExpires(DateTime absolute) + { + Absolute = absolute; + Sliding = NoSlidingExpiration; + } + + /// + /// Constructor for a cached entry that stays cached as long as it keeps being used. + /// + /// The timespan of sliding expirations. + public CacheExpires(TimeSpan sliding) + { + Absolute = NoAbsoluteExpiration; + Sliding = sliding; + } + + /// + /// Constructor for a cached entry that stays cached as long as it keeps being used. + /// + /// The number of seconds of sliding expirations. + public CacheExpires(double sliding) + : this(TimeSpan.FromSeconds(sliding)) + { + } + + public DateTime Absolute { get; set; } + public TimeSpan Sliding { get; set; } + + public static DateTime NoAbsoluteExpiration => DateTime.MaxValue; + + public static TimeSpan NoSlidingExpiration => TimeSpan.Zero; + + /// + /// Cached entry never to expire. + /// + public static CacheExpires Empty { get; } = new(); + } +} \ No newline at end of file diff --git a/src/Spark.Web/CacheSignal.cs b/src/Spark/CacheSignal.cs similarity index 92% rename from src/Spark.Web/CacheSignal.cs rename to src/Spark/CacheSignal.cs index 5e00d3ab..f4db2bd5 100644 --- a/src/Spark.Web/CacheSignal.cs +++ b/src/Spark/CacheSignal.cs @@ -19,7 +19,7 @@ public event EventHandler Changed lock (this) { _changed += value; - if (_enabled) + if (_enabled) return; Enable(); @@ -31,7 +31,7 @@ public event EventHandler Changed lock (this) { _changed -= value; - if (_enabled != true || ChangedIsEmpty() == false) + if (_enabled != true || ChangedIsEmpty() == false) return; Disable(); @@ -42,7 +42,7 @@ public event EventHandler Changed private bool ChangedIsEmpty() { - return _changed == null || + return _changed == null || _changed.GetInvocationList().Length == 0; } @@ -52,7 +52,7 @@ private bool ChangedIsEmpty() /// to call FireChanged. /// protected virtual void Enable() - { + { } /// @@ -60,7 +60,7 @@ protected virtual void Enable() /// when no cache dependencies remain listenning to the signal. /// protected virtual void Disable() - { + { } /// @@ -69,7 +69,7 @@ protected virtual void Disable() /// public void FireChanged() { - if (_changed != null) + if (_changed != null) _changed(this, EventArgs.Empty); } } diff --git a/src/Spark.Web/Caching/CacheMemento.cs b/src/Spark/Caching/CacheMemento.cs similarity index 60% rename from src/Spark.Web/Caching/CacheMemento.cs rename to src/Spark/Caching/CacheMemento.cs index 18c92a74..4e475e0f 100644 --- a/src/Spark.Web/Caching/CacheMemento.cs +++ b/src/Spark/Caching/CacheMemento.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Spark.Spool; @@ -6,14 +5,10 @@ namespace Spark.Caching { public class CacheMemento { - public CacheMemento() - { - Content = new Dictionary(); - OnceTable = new Dictionary(); - } - public SpoolWriter SpoolOutput { get; set; } - public Dictionary Content { get; set;} - public Dictionary OnceTable { get; set; } + + public Dictionary Content { get; set; } = new(); + + public Dictionary OnceTable { get; set; } = new(); } } diff --git a/src/Spark.Web/Caching/CacheOriginator.cs b/src/Spark/Caching/CacheOriginator.cs similarity index 71% rename from src/Spark.Web/Caching/CacheOriginator.cs rename to src/Spark/Caching/CacheOriginator.cs index c0b1511d..93aa3d26 100644 --- a/src/Spark.Web/Caching/CacheOriginator.cs +++ b/src/Spark/Caching/CacheOriginator.cs @@ -5,42 +5,35 @@ namespace Spark.Caching { - public class CacheOriginator + public class CacheOriginator(SparkViewContext state) { - private readonly SparkViewContext _state; - private TextWriter _priorOutput; private SpoolWriter _spoolOutput; - private readonly Dictionary _priorContent = new Dictionary(); + private readonly Dictionary _priorContent = new(); private Dictionary _priorOnceTable; - public CacheOriginator(SparkViewContext state) - { - _state = state; - } - /// /// Establishes original state for memento capturing purposes /// public void BeginMemento() { - foreach (var content in _state.Content) + foreach (var content in state.Content) { var writerOriginator = TextWriterOriginator.Create(content.Value); _priorContent.Add(content.Key, writerOriginator); writerOriginator.BeginMemento(); } - _priorOnceTable = _state.OnceTable.ToDictionary(kv=>kv.Key, kv=>kv.Value); + _priorOnceTable = state.OnceTable.ToDictionary(kv=>kv.Key, kv=>kv.Value); // capture current output also if it's not locked into a named output at the moment // this could be a case in view's output, direct to network, or various macro or content captures - if (_state.Content.Any(kv => ReferenceEquals(kv.Value, _state.Output)) == false) + if (state.Content.Any(kv => ReferenceEquals(kv.Value, state.Output)) == false) { - _priorOutput = _state.Output; + _priorOutput = state.Output; _spoolOutput = new SpoolWriter(); - _state.Output = _spoolOutput; + state.Output = _spoolOutput; } } @@ -57,7 +50,7 @@ public CacheMemento EndMemento() if (_priorOutput != null) { _spoolOutput.WriteTo(_priorOutput); - _state.Output = _priorOutput; + state.Output = _priorOutput; memento.SpoolOutput = _spoolOutput; } @@ -69,15 +62,15 @@ public CacheMemento EndMemento() memento.Content.Add(content.Key, textMemento); } - // also save any named content in it's entirety that added created after BeginMemento was called - foreach (var content in _state.Content.Where(kv => _priorContent.ContainsKey(kv.Key) == false)) + // also save any named content in its entirety that added created after BeginMemento was called + foreach (var content in state.Content.Where(kv => _priorContent.ContainsKey(kv.Key) == false)) { var originator = TextWriterOriginator.Create(content.Value); memento.Content.Add(content.Key, originator.CreateMemento()); } // capture anything from the oncetable that was added after BeginMemento was called - var newItems = _state.OnceTable.Where(once => _priorOnceTable.ContainsKey(once.Key) == false); + var newItems = state.OnceTable.Where(once => _priorOnceTable.ContainsKey(once.Key) == false); memento.OnceTable = newItems.ToDictionary(once => once.Key, once => once.Value); return memento; } @@ -88,16 +81,16 @@ public CacheMemento EndMemento() /// memento captured in previous begin/end calls public void DoMemento(CacheMemento memento) { - memento.SpoolOutput.WriteTo(_state.Output); + memento.SpoolOutput.WriteTo(state.Output); foreach (var content in memento.Content) { // create named content if it doesn't exist TextWriter writer; - if (_state.Content.TryGetValue(content.Key, out writer) == false) + if (state.Content.TryGetValue(content.Key, out writer) == false) { writer = new SpoolWriter(); - _state.Content.Add(content.Key, writer); + state.Content.Add(content.Key, writer); } // and in any case apply the delta @@ -106,10 +99,10 @@ public void DoMemento(CacheMemento memento) } // add recorded once deltas that were not yet in this subject's table - var newItems = memento.OnceTable.Where(once => _state.OnceTable.ContainsKey(once.Key) == false); + var newItems = memento.OnceTable.Where(once => state.OnceTable.ContainsKey(once.Key) == false); foreach (var once in newItems) { - _state.OnceTable.Add(once.Key, once.Value); + state.OnceTable.Add(once.Key, once.Value); } } } diff --git a/src/Spark/Caching/SpoolWriterOriginator.cs b/src/Spark/Caching/SpoolWriterOriginator.cs new file mode 100644 index 00000000..dd044591 --- /dev/null +++ b/src/Spark/Caching/SpoolWriterOriginator.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Spark.Spool; + +namespace Spark.Caching +{ + public class SpoolWriterOriginator(SpoolWriter writer) : TextWriterOriginator + { + private int _priorStringCount; + + public override TextWriterMemento CreateMemento() + { + return new TextWriterMemento { Written = writer.ToArray() }; + } + + public override void BeginMemento() + { + _priorStringCount = writer.Count(); + } + + public override TextWriterMemento EndMemento() + { + return new TextWriterMemento { Written = writer.Skip(_priorStringCount).ToArray() }; + } + + public override void DoMemento(TextWriterMemento memento) + { + foreach (var written in memento.Written) + writer.Write(written); + } + } +} \ No newline at end of file diff --git a/src/Spark/Caching/StringWriterOriginator.cs b/src/Spark/Caching/StringWriterOriginator.cs new file mode 100644 index 00000000..619a7ace --- /dev/null +++ b/src/Spark/Caching/StringWriterOriginator.cs @@ -0,0 +1,32 @@ +using System.IO; + +namespace Spark.Caching +{ + public class StringWriterOriginator(StringWriter writer) : TextWriterOriginator + { + private int _priorLength; + + public override TextWriterMemento CreateMemento() + { + return new TextWriterMemento {Written = new[] {writer.ToString()}}; + } + + public override void BeginMemento() + { + _priorLength = writer.GetStringBuilder().Length; + } + + public override TextWriterMemento EndMemento() + { + var currentLength = writer.GetStringBuilder().Length; + var written = writer.GetStringBuilder().ToString(_priorLength, currentLength - _priorLength); + return new TextWriterMemento { Written = new[] { written } }; + } + + public override void DoMemento(TextWriterMemento memento) + { + foreach(var written in memento.Written) + writer.Write(written); + } + } +} diff --git a/src/Spark.Web/Caching/TextWriterOriginator.cs b/src/Spark/Caching/TextWriterOriginator.cs similarity index 94% rename from src/Spark.Web/Caching/TextWriterOriginator.cs rename to src/Spark/Caching/TextWriterOriginator.cs index b9885705..9727df17 100644 --- a/src/Spark.Web/Caching/TextWriterOriginator.cs +++ b/src/Spark/Caching/TextWriterOriginator.cs @@ -10,9 +10,15 @@ public abstract class TextWriterOriginator public static TextWriterOriginator Create(TextWriter writer) { if (writer is SpoolWriter) + { return new SpoolWriterOriginator((SpoolWriter) writer); + } + if (writer is StringWriter) + { return new StringWriterOriginator((StringWriter)writer); + } + throw new InvalidCastException("writer is unknown type " + writer.GetType().FullName); } diff --git a/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs b/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs index bff1a342..207a1641 100644 --- a/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs +++ b/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs @@ -503,6 +503,7 @@ protected override void Visit(CacheChunk chunk) .RemoveIndent().WriteLine("}") .RemoveIndent().WriteLine("}"); } + protected override void Visit(MarkdownChunk chunk) { CodeIndent(chunk).WriteLine("using(MarkdownOutputScope())"); diff --git a/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs b/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs index 95307a8d..3a00578a 100644 --- a/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs +++ b/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; + using System.Collections.Generic; -using System.Linq; -using System.Text; -using Spark.Compiler.ChunkVisitors; using Spark.Parser.Code; namespace Spark.Compiler.ChunkVisitors @@ -62,7 +59,7 @@ void Examine(Snippets code) protected override void Visit(UseImportChunk chunk) { - + //no-op } protected override void Visit(ContentSetChunk chunk) diff --git a/src/Spark.Web/ICacheService.cs b/src/Spark/ICacheService.cs similarity index 93% rename from src/Spark.Web/ICacheService.cs rename to src/Spark/ICacheService.cs index e4937080..2c9816aa 100644 --- a/src/Spark.Web/ICacheService.cs +++ b/src/Spark/ICacheService.cs @@ -1,5 +1,3 @@ -using System; - namespace Spark { public interface ICacheService diff --git a/src/Spark/InMemoryCacheService.cs b/src/Spark/InMemoryCacheService.cs new file mode 100644 index 00000000..b60c8d9e --- /dev/null +++ b/src/Spark/InMemoryCacheService.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace Spark; + +public class InMemoryCacheService(IMemoryCache cache) : ICacheService +{ + public object Get(string identifier) + { + return cache.Get(identifier); + } + + public void Store(string identifier, CacheExpires expires, ICacheSignal signal, object item) + { + var option = new MemoryCacheEntryOptions(); + + if (expires != null) + { + if (expires.Sliding > CacheExpires.NoSlidingExpiration) + { + option.SlidingExpiration = expires.Sliding; + } + else + { + option.AbsoluteExpiration = expires.Absolute; + } + } + + if (signal != null) + { + option.AddExpirationToken(new SignalChangeToken(signal)); + } + + cache.Set(identifier, item, option); + } + + private class SignalChangeToken : IChangeToken + { + private readonly ICacheSignal signal; + private bool hasChanged; + + public SignalChangeToken(ICacheSignal signal) + { + this.signal = signal; + this.signal.Changed += this.SignalOnChanged; + } + + private void SignalOnChanged(object sender, EventArgs e) + { + this.hasChanged = true; + } + + public bool HasChanged => this.hasChanged; + + public bool ActiveChangeCallbacks => true; + + public IDisposable RegisterChangeCallback(Action callback, object state) + { + return new StopListeningToSignal(this); + } + + private class StopListeningToSignal(SignalChangeToken signalChangeToken) : IDisposable + { + public void Dispose() + { + signalChangeToken.signal.Changed -= signalChangeToken.SignalOnChanged; + } + } + } +} \ No newline at end of file diff --git a/src/Spark/Spark.csproj b/src/Spark/Spark.csproj index 81e988fe..f060c4e1 100644 --- a/src/Spark/Spark.csproj +++ b/src/Spark/Spark.csproj @@ -1,4 +1,4 @@ - + Library net48;net8.0 @@ -25,9 +25,11 @@ Spark is a view engine allowing the HTML to dominate the flow and any code to fit seamlessly. - + + + diff --git a/src/Spark.Web/SparkViewBase.cs b/src/Spark/SparkViewBase.cs similarity index 100% rename from src/Spark.Web/SparkViewBase.cs rename to src/Spark/SparkViewBase.cs diff --git a/src/Spark.Web/SparkViewDecorator.cs b/src/Spark/SparkViewDecorator.cs similarity index 100% rename from src/Spark.Web/SparkViewDecorator.cs rename to src/Spark/SparkViewDecorator.cs From 544fcb7e42c86b823cd5c1766439670c65df3cea Mon Sep 17 00:00:00 2001 From: bounav Date: Mon, 4 Mar 2024 10:25:49 +0000 Subject: [PATCH 12/14] Moved descriptor builder and filters to spark - Use of a new SparkRouteData class - Avoids having a dependency on Microsoft.AspNet.Mvc --- .../DescriptorBuildingTester.cs | 31 +- .../DescriptorFilterTester.cs | 13 +- .../SparkViewFactoryTester.cs | 10 +- .../Descriptors/AreaDescriptorFilter.cs | 61 -- .../Descriptors/DescriptorFilterBase.cs | 27 - .../Descriptors/DescriptorFilterExtensions.cs | 11 +- .../Descriptors/ThemeDescriptorFilter.cs | 41 -- .../Extensions/ServiceCollectionExtensions.cs | 4 +- src/Spark.Web.Mvc/JavascriptViewResult.cs | 14 +- src/Spark.Web.Mvc/LanguageKit.cs | 13 +- src/Spark.Web.Mvc/SparkViewFactory.cs | 10 +- .../Wrappers/ITextWriterContainer.cs | 10 +- .../BuildDescriptorParams.cs} | 222 +++---- src/Spark/Compiler/BatchCompiler.cs | 1 - src/Spark/Descriptors/AreaDescriptorFilter.cs | 37 ++ .../Descriptors/DescriptorBuilder.cs} | 546 +++++++++--------- src/Spark/Descriptors/DescriptorFilterBase.cs | 27 + src/Spark/Descriptors/IDescriptorBuilder.cs | 24 + .../Descriptors/IDescriptorFilter.cs | 57 +- .../Descriptors/LanguageDescriptorFilter.cs | 127 ++-- src/Spark/Descriptors/SparkRouteData.cs | 14 + .../Descriptors/ThemeDescriptorFilter.cs | 41 ++ 22 files changed, 672 insertions(+), 669 deletions(-) delete mode 100644 src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs delete mode 100644 src/Spark.Web.Mvc/Descriptors/DescriptorFilterBase.cs delete mode 100644 src/Spark.Web.Mvc/Descriptors/ThemeDescriptorFilter.cs rename src/{Spark.Web.Mvc/IDescriptorBuilder.cs => Spark/BuildDescriptorParams.cs} (52%) create mode 100644 src/Spark/Descriptors/AreaDescriptorFilter.cs rename src/{Spark.Web.Mvc/DefaultDescriptorBuilder.cs => Spark/Descriptors/DescriptorBuilder.cs} (82%) create mode 100644 src/Spark/Descriptors/DescriptorFilterBase.cs create mode 100644 src/Spark/Descriptors/IDescriptorBuilder.cs rename src/{Spark.Web.Mvc => Spark}/Descriptors/IDescriptorFilter.cs (82%) rename src/{Spark.Web.Mvc => Spark}/Descriptors/LanguageDescriptorFilter.cs (66%) create mode 100644 src/Spark/Descriptors/SparkRouteData.cs create mode 100644 src/Spark/Descriptors/ThemeDescriptorFilter.cs diff --git a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs index f160edcd..0d9d899f 100644 --- a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs +++ b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs @@ -18,15 +18,16 @@ using System.Linq; using System.Web; using System.Web.Mvc; -using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; +using Spark.Descriptors; using Spark.FileSystem; using Spark.Parser; using Spark.Web.Mvc.Descriptors; using Spark.Web.Mvc.Extensions; +using RouteData = System.Web.Routing.RouteData; namespace Spark.Web.Mvc.Tests { @@ -267,7 +268,7 @@ public void PartialViewIgnoresDefaultLayoutsWithShade() [Test] public void RouteAreaPresentDefaultsToNormalLocation() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.spark", ""); _viewFolder.Add(@"Layouts\Application.spark", ""); @@ -285,7 +286,7 @@ public void RouteAreaPresentDefaultsToNormalLocation() [Test] public void RouteAreaPresentDefaultsToNormalLocationWithShade() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.shade", ""); _viewFolder.Add(@"Layouts\Application.shade", ""); @@ -303,7 +304,7 @@ public void RouteAreaPresentDefaultsToNormalLocationWithShade() [Test] public void AreaFolderMayContainControllerFolder() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.spark", ""); _viewFolder.Add(@"Layouts\Application.spark", ""); @@ -322,7 +323,7 @@ public void AreaFolderMayContainControllerFolder() [Test] public void AreaFolderMayContainControllerFolderWithShade() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.shade", ""); _viewFolder.Add(@"Layouts\Application.shade", ""); @@ -377,7 +378,7 @@ public void AreaRouteValueAlsoRecognizedForBackCompatWithEarlierAssumptionsWithS [Test] public void AreaFolderMayContainLayoutsFolder() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.spark", ""); _viewFolder.Add(@"Layouts\Application.spark", ""); @@ -397,7 +398,7 @@ public void AreaFolderMayContainLayoutsFolder() [Test] public void AreaFolderMayContainLayoutsFolderWithShade() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.shade", ""); _viewFolder.Add(@"Layouts\Application.shade", ""); @@ -417,7 +418,7 @@ public void AreaFolderMayContainLayoutsFolderWithShade() [Test] public void AreaContainsNamedLayout() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.spark", ""); _viewFolder.Add(@"Layouts\Application.spark", ""); @@ -438,7 +439,7 @@ public void AreaContainsNamedLayout() [Test] public void AreaContainsNamedLayoutWithShade() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.shade", ""); _viewFolder.Add(@"Layouts\Application.shade", ""); @@ -459,7 +460,7 @@ public void AreaContainsNamedLayoutWithShade() [Test] public void PartialViewFromAreaIgnoresLayout() { - _routeData.DataTokens.Add("area", "Admin"); + _routeData.Values.Add("area", "Admin"); _routeData.Values.Add("controller", "Home"); _viewFolder.Add(@"Home\Index.spark", ""); _viewFolder.Add(@"Admin\Home\Index.spark", ""); @@ -626,18 +627,18 @@ public void CustomParameterAddedToViewSearchPath() @"Layouts\Application.en.spark"); } - public class ExtendingDescriptorBuilderWithInheritance : DefaultDescriptorBuilder + public class ExtendingDescriptorBuilderWithInheritance : DescriptorBuilder { public ExtendingDescriptorBuilderWithInheritance(ISparkSettings settings, IViewFolder viewFolder) : base(settings, viewFolder) { } - public override IDictionary GetExtraParameters(ControllerContext controllerContext) + public override IDictionary GetExtraParameters(SparkRouteData routeData) { return new Dictionary { - { "language", Convert.ToString(controllerContext.RouteData.Values["language"]) } + { "language", Convert.ToString(routeData.Values["language"]) } }; } @@ -702,7 +703,7 @@ public void SimplifiedUseMasterGrammarDetectsElementCorrectly() Prefix = null }; - var builder = new DefaultDescriptorBuilder(settings, null); + var builder = new DescriptorBuilder(settings, null); var a = builder.ParseUseMaster(new Position(new SourceContext(""))); var b = builder.ParseUseMaster(new Position(new SourceContext(""))); @@ -727,7 +728,7 @@ public void SimplifiedUseMasterGrammarWithPrefixDetectsElementCorrectly() Prefix = "s" }; - var builder = new DefaultDescriptorBuilder(settings, null); + var builder = new DescriptorBuilder(settings, null); var a = builder.ParseUseMaster(new Position(new SourceContext(""))); var b = builder.ParseUseMaster(new Position(new SourceContext(""))); diff --git a/src/Spark.Web.Mvc.Tests/DescriptorFilterTester.cs b/src/Spark.Web.Mvc.Tests/DescriptorFilterTester.cs index ee11ab35..c6407f00 100644 --- a/src/Spark.Web.Mvc.Tests/DescriptorFilterTester.cs +++ b/src/Spark.Web.Mvc.Tests/DescriptorFilterTester.cs @@ -4,9 +4,8 @@ using System.Web.Mvc; using System.Web.Routing; using NUnit.Framework; - using Rhino.Mocks; -using Spark.Web.Mvc.Descriptors; +using Spark.Descriptors; namespace Spark.Web.Mvc.Tests { @@ -32,8 +31,8 @@ public void AreaFilterUsesRouteDataTokensByDefault() { var filter = new AreaDescriptorFilter(); - _context.RouteData.DataTokens.Add("area", "foo"); - filter.ExtraParameters(_context, _extra); + _context.RouteData.Values.Add("area", "foo"); + filter.ExtraParameters(new SparkRouteData(_context.RouteData.Values), _extra); Assert.That(_extra["area"], Is.EqualTo("foo")); } @@ -44,7 +43,7 @@ public void AreaFilterUsesRouteValuesForBackCompat() { var filter = new AreaDescriptorFilter(); _context.RouteData.Values.Add("area", "foo"); - filter.ExtraParameters(_context, _extra); + filter.ExtraParameters(new SparkRouteData(_context.RouteData.Values), _extra); Assert.That(_extra["area"], Is.EqualTo("foo")); } @@ -76,7 +75,7 @@ public void AreaFilterAddsNameToStartOfPath() public void ThemeFilterDelegateCanExtractParameter() { var filter = ThemeDescriptorFilter.For(context => "foo"); - filter.ExtraParameters(_context, _extra); + filter.ExtraParameters(new SparkRouteData(_context.RouteData.Values), _extra); Assert.That(_extra["theme"], Is.EqualTo("foo")); } @@ -107,7 +106,7 @@ public void ThemeFilterAddsThemesAndNameToPath() public void LanguageFilterDelegateCanExtractParameter() { var filter = LanguageDescriptorFilter.For(context => "foo"); - filter.ExtraParameters(_context, _extra); + filter.ExtraParameters(new SparkRouteData(_context.RouteData.Values), _extra); Assert.That(_extra["language"], Is.EqualTo("foo")); } diff --git a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs index a712147d..3f17d096 100644 --- a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs @@ -24,10 +24,12 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Rhino.Mocks; +using Spark.Descriptors; using Spark.FileSystem; using Spark.Web.Mvc.Extensions; using Spark.Web.Mvc.Tests.Controllers; using Spark.Web.Mvc.Tests.Models; +using RouteData = System.Web.Routing.RouteData; namespace Spark.Web.Mvc.Tests { @@ -610,7 +612,7 @@ public void RenderPartialSharesState() [Test] public void CanLocateViewInArea() { - controllerContext.RouteData.DataTokens.Add("area", "admin"); + controllerContext.RouteData.Values.Add("area", "admin"); var result = factory.FindView(controllerContext, "index", null); @@ -624,7 +626,7 @@ public void CanLocateViewInArea() [Test] public void CanLocateViewInAreaWithLayout() { - controllerContext.RouteData.DataTokens.Add("area", "admin"); + controllerContext.RouteData.Values.Add("area", "admin"); var result = factory.FindView(controllerContext, "index", "layout"); @@ -642,7 +644,7 @@ public void CanLocateViewInAreaWithLayout() [Test] public void CanLocateViewInAreaWithLayoutInArea() { - controllerContext.RouteData.DataTokens.Add("area", "admin"); + controllerContext.RouteData.Values.Add("area", "admin"); var result = factory.FindView(controllerContext, "index", "speciallayout"); @@ -660,7 +662,7 @@ public void CanLocateViewInAreaWithLayoutInArea() [Test] public void CanLocatePartialViewInArea() { - controllerContext.RouteData.DataTokens.Add("area", "admin"); + controllerContext.RouteData.Values.Add("area", "admin"); var result = factory.FindPartialView(controllerContext, "index"); diff --git a/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs b/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs deleted file mode 100644 index 81557c10..00000000 --- a/src/Spark.Web.Mvc/Descriptors/AreaDescriptorFilter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Mvc; -using System.Web.Routing; - -namespace Spark.Web.Mvc.Descriptors -{ - public class AreaDescriptorFilter : DescriptorFilterBase - { - public override void ExtraParameters(ControllerContext context, IDictionary extra) - { - var areaName = GetAreaName(context.RouteData); - if (!string.IsNullOrEmpty(areaName)) - extra["area"] = areaName; - } - - public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) - { - string areaName; - - return TryGetString(extra, "area", out areaName) - ? locations.Select(x => Path.Combine(areaName, x)).Concat(locations) - : locations; - } - - - private static string GetAreaName(RouteBase route) - { - var routeWithArea = route as IRouteWithArea; - if (routeWithArea != null) - { - return routeWithArea.Area; - } - - var castRoute = route as Route; - if (castRoute != null && castRoute.DataTokens != null) - { - return castRoute.DataTokens["area"] as string; - } - - return null; - } - - private static string GetAreaName(RouteData routeData) - { - object area; - if (routeData.DataTokens.TryGetValue("area", out area)) - { - return area as string; - } - if (routeData.Values.TryGetValue("area", out area)) - { - return area as string; - } - - return GetAreaName(routeData.Route); - } - - } -} diff --git a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterBase.cs b/src/Spark.Web.Mvc/Descriptors/DescriptorFilterBase.cs deleted file mode 100644 index d08e3949..00000000 --- a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterBase.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Mvc; - -namespace Spark.Web.Mvc.Descriptors -{ - public abstract class DescriptorFilterBase : IDescriptorFilter - { - public abstract void ExtraParameters(ControllerContext context, IDictionary extra); - - public abstract IEnumerable PotentialLocations(IEnumerable locations, - IDictionary extra); - - protected static bool TryGetString(IDictionary extra, string name, out string value) - { - object obj; - if (extra.TryGetValue(name, out obj)) - { - value = Convert.ToString(obj); - return !string.IsNullOrEmpty(value); - } - - value = null; - return false; - } - } -} diff --git a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs b/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs index d8a10919..f9fa2a86 100644 --- a/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs +++ b/src/Spark.Web.Mvc/Descriptors/DescriptorFilterExtensions.cs @@ -1,4 +1,5 @@ using System; +using Spark.Descriptors; namespace Spark.Web.Mvc.Descriptors { @@ -11,13 +12,15 @@ public static void AddFilter(this SparkViewFactory target, IDescriptorFilter fil public static void AddFilter(this IDescriptorBuilder target, IDescriptorFilter filter) { - if (!(target is DefaultDescriptorBuilder)) - throw new InvalidCastException($"IDescriptorFilters may only be added to {nameof(DefaultDescriptorBuilder)}"); + if (!(target is DescriptorBuilder)) + { + throw new InvalidCastException($"IDescriptorFilters may only be added to {nameof(DescriptorBuilder)}"); + } - ((DefaultDescriptorBuilder) target).AddFilter(filter); + ((DescriptorBuilder)target).AddFilter(filter); } - public static void AddFilter(this DefaultDescriptorBuilder target, IDescriptorFilter filter) + public static void AddFilter(this DescriptorBuilder target, IDescriptorFilter filter) { target.Filters.Add(filter); } diff --git a/src/Spark.Web.Mvc/Descriptors/ThemeDescriptorFilter.cs b/src/Spark.Web.Mvc/Descriptors/ThemeDescriptorFilter.cs deleted file mode 100644 index d79cea86..00000000 --- a/src/Spark.Web.Mvc/Descriptors/ThemeDescriptorFilter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Mvc; - -namespace Spark.Web.Mvc.Descriptors -{ - public abstract class ThemeDescriptorFilter : DescriptorFilterBase - { - public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) - { - string themeName; - return TryGetString(extra, "theme", out themeName) - ? locations.Select(x => Path.Combine(string.Format("themes{0}", Path.DirectorySeparatorChar) + themeName, x)).Concat(locations) - : locations; - } - - public static ThemeDescriptorFilter For(Func selector) - { - return new Delegated(selector); - } - - class Delegated : ThemeDescriptorFilter - { - private readonly Func _selector; - - public Delegated(Func selector) - { - _selector = selector; - } - - public override void ExtraParameters(ControllerContext context, IDictionary extra) - { - var theme = Convert.ToString(_selector(context)); - if (!string.IsNullOrEmpty(theme)) - extra["theme"] = theme; - } - } - } -} \ No newline at end of file diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs index fd33e2cc..2bbc450c 100644 --- a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -3,9 +3,9 @@ using System.Web; using Microsoft.Extensions.DependencyInjection; using Spark.Bindings; -using Spark.Caching; using Spark.Compiler; using Spark.Compiler.Roslyn; +using Spark.Descriptors; using Spark.FileSystem; using Spark.Parser; using Spark.Parser.Syntax; @@ -64,7 +64,7 @@ public static IServiceCollection AddSpark(this IServiceCollection services, ISpa services.AddSingleton(c => null); services - .AddSingleton() + .AddSingleton() .AddTransient(sp => { if (HttpContext.Current != null && HttpContext.Current.Cache != null) diff --git a/src/Spark.Web.Mvc/JavascriptViewResult.cs b/src/Spark.Web.Mvc/JavascriptViewResult.cs index b5fadadc..a20c039c 100644 --- a/src/Spark.Web.Mvc/JavascriptViewResult.cs +++ b/src/Spark.Web.Mvc/JavascriptViewResult.cs @@ -15,9 +15,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Web.Mvc; using Spark.Compiler; +using Spark.Descriptors; namespace Spark.Web.Mvc { @@ -44,10 +44,20 @@ public override void ExecuteResult(ControllerContext context) if (!factories.Any()) throw new CompilerException("No SparkViewFactory instances are registered"); + var routeDataWrapper = new SparkRouteData(context.RouteData.Values); + foreach (var factory in factories) { var descriptor = factory.DescriptorBuilder.BuildDescriptor( - new BuildDescriptorParams("", controllerName, ViewName, MasterName, false, factory.DescriptorBuilder.GetExtraParameters(context)), searchedLocations); + new BuildDescriptorParams( + "", + controllerName, + ViewName, + MasterName, + false, + factory.DescriptorBuilder.GetExtraParameters(routeDataWrapper)), + searchedLocations); + descriptor.Language = LanguageType.Javascript; var entry = factory.Engine.CreateEntry(descriptor); context.HttpContext.Response.ContentType = "text/javascript"; diff --git a/src/Spark.Web.Mvc/LanguageKit.cs b/src/Spark.Web.Mvc/LanguageKit.cs index 5f7f2086..e1377caf 100644 --- a/src/Spark.Web.Mvc/LanguageKit.cs +++ b/src/Spark.Web.Mvc/LanguageKit.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Web.Mvc; +using Spark.Descriptors; using Spark.FileSystem; using Spark.Web.Mvc.Descriptors; @@ -10,14 +11,14 @@ namespace Spark.Web.Mvc { public static class LanguageKit { - public static void Install(SparkViewFactory factory, Func selector) + public static void Install(SparkViewFactory factory, Func selector) { factory.AddFilter(new Filter(selector)); factory.Engine.ViewFolder = new Folder(factory.Engine.ViewFolder); } - public static void Install(IEnumerable engines, Func selector) + public static void Install(IEnumerable engines, Func selector) { foreach (var factory in engines.OfType()) { @@ -27,16 +28,16 @@ public static void Install(IEnumerable engines, Func _selector; + private readonly Func _selector; - public Filter(Func selector) + public Filter(Func selector) { _selector = selector; } - public override void ExtraParameters(ControllerContext context, IDictionary extra) + public override void ExtraParameters(SparkRouteData routeData, IDictionary extra) { - var theme = Convert.ToString(_selector(context)); + var theme = Convert.ToString(_selector(routeData)); if (!string.IsNullOrEmpty(theme)) { extra["language"] = theme; diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 0d193a95..71da58bf 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -18,8 +18,8 @@ using System.Linq; using System.Reflection; using System.Web.Mvc; -using System.Web.Routing; using Spark.Compiler; +using Spark.Descriptors; using Spark.FileSystem; using Spark.Web.Mvc.Wrappers; @@ -95,13 +95,15 @@ private ViewEngineResult FindViewInternal(ControllerContext controllerContext, s var controllerName = controllerContext.RouteData.GetRequiredString("controller"); + var routeDataWrapper = new SparkRouteData(controllerContext.RouteData.Values); + var descriptorParams = new BuildDescriptorParams( targetNamespace, controllerName, viewName, masterName, findDefaultMaster, - DescriptorBuilder.GetExtraParameters(controllerContext)); + DescriptorBuilder.GetExtraParameters(routeDataWrapper)); ISparkViewEntry entry; if (useCache) @@ -164,6 +166,8 @@ public SparkViewDescriptor CreateDescriptor( var controllerName = controllerContext.RouteData.GetRequiredString("controller"); + var routeDataWrapper = new SparkRouteData(controllerContext.RouteData.Values); + return DescriptorBuilder.BuildDescriptor( new BuildDescriptorParams( targetNamespace, @@ -171,7 +175,7 @@ public SparkViewDescriptor CreateDescriptor( viewName, masterName, findDefaultMaster, - DescriptorBuilder.GetExtraParameters(controllerContext)), + DescriptorBuilder.GetExtraParameters(routeDataWrapper)), searchedLocations); } diff --git a/src/Spark.Web.Mvc/Wrappers/ITextWriterContainer.cs b/src/Spark.Web.Mvc/Wrappers/ITextWriterContainer.cs index 7bfbb694..af20832a 100644 --- a/src/Spark.Web.Mvc/Wrappers/ITextWriterContainer.cs +++ b/src/Spark.Web.Mvc/Wrappers/ITextWriterContainer.cs @@ -1,7 +1,9 @@ using System.IO; -namespace Spark.Web.Mvc.Wrappers { - public interface ITextWriterContainer { - TextWriter Output{ get; set;} +namespace Spark.Web.Mvc.Wrappers +{ + public interface ITextWriterContainer + { + TextWriter Output { get; set; } } -} +} \ No newline at end of file diff --git a/src/Spark.Web.Mvc/IDescriptorBuilder.cs b/src/Spark/BuildDescriptorParams.cs similarity index 52% rename from src/Spark.Web.Mvc/IDescriptorBuilder.cs rename to src/Spark/BuildDescriptorParams.cs index 817fd258..d0c2e5cf 100644 --- a/src/Spark.Web.Mvc/IDescriptorBuilder.cs +++ b/src/Spark/BuildDescriptorParams.cs @@ -1,129 +1,93 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; - -namespace Spark.Web.Mvc -{ - public interface IDescriptorBuilder - { - /// - /// Implemented by custom descriptor builder to quickly extract additional parameters needed - /// to locate templates, like the theme or language in effect for the request - /// - /// Context information for the current request - /// An in-order array of values which are meaningful to BuildDescriptor on the same implementation class - IDictionary GetExtraParameters(ControllerContext controllerContext); - - /// - /// Given a set of MVC-specific parameters, a descriptor for the target view is created. This can - /// be a bit more expensive because the existence of files is tested at various candidate locations. - /// - /// Contains all of the standard and extra parameters which contribute to a descriptor - /// Candidate locations are added to this collection so an information-rich error may be returned - /// The descriptor with all of the detected view locations in order - SparkViewDescriptor BuildDescriptor(BuildDescriptorParams buildDescriptorParams, ICollection searchedLocations); - } - - public class BuildDescriptorParams - { - private readonly string _targetNamespace; - private readonly string _controllerName; - private readonly string _viewName; - private readonly string _masterName; - private readonly bool _findDefaultMaster; - private readonly IDictionary _extra; - private static readonly IDictionary _extraEmpty = new Dictionary(); - private readonly int _hashCode; - - public BuildDescriptorParams(string targetNamespace, string controllerName, string viewName, string masterName, bool findDefaultMaster, IDictionary extra) - { - _targetNamespace = targetNamespace; - _controllerName = controllerName; - _viewName = viewName; - _masterName = masterName; - _findDefaultMaster = findDefaultMaster; - _extra = extra ?? _extraEmpty; - - // this object is meant to be immutable and used in a dictionary. - // the hash code will always be used so it isn't calculated just-in-time. - _hashCode = CalculateHashCode(); - } - - public string TargetNamespace - { - get { return _targetNamespace; } - } - - public string ControllerName - { - get { return _controllerName; } - } - - public string ViewName - { - get { return _viewName; } - } - - public string MasterName - { - get { return _masterName; } - } - - public bool FindDefaultMaster - { - get { return _findDefaultMaster; } - } - - public IDictionary Extra - { - get { return _extra; } - } - - private static int Hash(object str) - { - return str == null ? 0 : str.GetHashCode(); - } - - public override int GetHashCode() - { - return _hashCode; - } - - private int CalculateHashCode() - { - return Hash(_viewName) ^ - Hash(_controllerName) ^ - Hash(_targetNamespace) ^ - Hash(_masterName) ^ - _findDefaultMaster.GetHashCode() ^ - _extra.Aggregate(0, (hash, kv) => hash ^ Hash(kv.Key) ^ Hash(kv.Value)); - } - - public override bool Equals(object obj) - { - var that = obj as BuildDescriptorParams; - if (that == null || that.GetType() != GetType()) - return false; - - if (!string.Equals(_viewName, that._viewName) || - !string.Equals(_controllerName, that._controllerName) || - !string.Equals(_targetNamespace, that._targetNamespace) || - !string.Equals(_masterName, that._masterName) || - _findDefaultMaster != that._findDefaultMaster || - _extra.Count != that._extra.Count) - { - return false; - } - foreach (var kv in _extra) - { - object value; - if (!that._extra.TryGetValue(kv.Key, out value) || - !Equals(kv.Value, value)) - { - return false; - } - } - return true; - } - } -} +using System.Collections.Generic; +using System.Linq; + +namespace Spark +{ + public class BuildDescriptorParams + { + private readonly string _targetNamespace; + private readonly string _controllerName; + private readonly string _viewName; + private readonly string _masterName; + private readonly bool _findDefaultMaster; + private readonly IDictionary _extra; + private static readonly IDictionary _extraEmpty = new Dictionary(); + private readonly int _hashCode; + + public BuildDescriptorParams(string targetNamespace, string controllerName, string viewName, string masterName, bool findDefaultMaster, IDictionary extra) + { + _targetNamespace = targetNamespace; + _controllerName = controllerName; + _viewName = viewName; + _masterName = masterName; + _findDefaultMaster = findDefaultMaster; + _extra = extra ?? _extraEmpty; + + // this object is meant to be immutable and used in a dictionary. + // the hash code will always be used so it isn't calculated just-in-time. + _hashCode = CalculateHashCode(); + } + + public string TargetNamespace => _targetNamespace; + + public string ControllerName => _controllerName; + + public string ViewName => _viewName; + + public string MasterName => _masterName; + + public bool FindDefaultMaster => _findDefaultMaster; + + public IDictionary Extra => _extra; + + private static int Hash(object str) + { + return str?.GetHashCode() ?? 0; + } + + public override int GetHashCode() + { + return _hashCode; + } + + private int CalculateHashCode() + { + return Hash(_viewName) ^ + Hash(_controllerName) ^ + Hash(_targetNamespace) ^ + Hash(_masterName) ^ + _findDefaultMaster.GetHashCode() ^ + _extra.Aggregate(0, (hash, kv) => hash ^ Hash(kv.Key) ^ Hash(kv.Value)); + } + + public override bool Equals(object obj) + { + var that = obj as BuildDescriptorParams; + if (that == null || that.GetType() != GetType()) + { + return false; + } + + if (!string.Equals(_viewName, that._viewName) || + !string.Equals(_controllerName, that._controllerName) || + !string.Equals(_targetNamespace, that._targetNamespace) || + !string.Equals(_masterName, that._masterName) || + _findDefaultMaster != that._findDefaultMaster || + _extra.Count != that._extra.Count) + { + return false; + } + + foreach (var kv in _extra) + { + if (!that._extra.TryGetValue(kv.Key, out var value) || + !Equals(kv.Value, value)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/BatchCompiler.cs index 01ae0da7..0cef55a4 100644 --- a/src/Spark/Compiler/BatchCompiler.cs +++ b/src/Spark/Compiler/BatchCompiler.cs @@ -15,7 +15,6 @@ namespace Spark.Compiler; #if NETFRAMEWORK -[Obsolete("To be replaced with RoslynBatchCompiler (code dom cannot target .net 'core')")] public class CodeDomBatchCompiler : IBatchCompiler { /// diff --git a/src/Spark/Descriptors/AreaDescriptorFilter.cs b/src/Spark/Descriptors/AreaDescriptorFilter.cs new file mode 100644 index 00000000..4339fb67 --- /dev/null +++ b/src/Spark/Descriptors/AreaDescriptorFilter.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Spark.Descriptors +{ + public class AreaDescriptorFilter : DescriptorFilterBase + { + public override void ExtraParameters(SparkRouteData routeData, IDictionary extra) + { + var areaName = GetAreaName(routeData); + if (!string.IsNullOrEmpty(areaName)) + extra["area"] = areaName; + } + + public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) + { + return TryGetString(extra, "area", out var areaName) + ? locations.Select(x => Path.Combine(areaName, x)).Concat(locations) + : locations; + } + + private static string GetAreaName(SparkRouteData routeData) + { + if (routeData.Values.TryGetValue("area", out var area)) + { + return area as string; + } + if (routeData.Values.TryGetValue("area", out area)) + { + return area as string; + } + + return null; + } + } +} diff --git a/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs b/src/Spark/Descriptors/DescriptorBuilder.cs similarity index 82% rename from src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs rename to src/Spark/Descriptors/DescriptorBuilder.cs index 8ab0e37e..24666132 100644 --- a/src/Spark.Web.Mvc/DefaultDescriptorBuilder.cs +++ b/src/Spark/Descriptors/DescriptorBuilder.cs @@ -1,272 +1,276 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Mvc; -using Spark.FileSystem; -using Spark.Parser; -using Spark.Parser.Syntax; -using Spark.Web.Mvc.Descriptors; - -namespace Spark.Web.Mvc -{ - public class DefaultDescriptorBuilder : IDescriptorBuilder - { - private IViewFolder _viewFolder; - - public DefaultDescriptorBuilder(ISparkSettings settings, IViewFolder viewFolder) - { - this.Filters = new List - { - new AreaDescriptorFilter() - }; - - this._grammar = new UseMasterGrammar(settings.Prefix); - - this._viewFolder = viewFolder; - } - - public IList Filters { get; set; } - - public virtual IDictionary GetExtraParameters(ControllerContext controllerContext) - { - var extra = new Dictionary(); - foreach (var filter in Filters) - { - filter.ExtraParameters(controllerContext, extra); - } - - return extra; - } - - public virtual SparkViewDescriptor BuildDescriptor(BuildDescriptorParams buildDescriptorParams, ICollection searchedLocations) - { - var descriptor = new SparkViewDescriptor - { - TargetNamespace = buildDescriptorParams.TargetNamespace - }; - - if (!LocatePotentialTemplate( - PotentialViewLocations(buildDescriptorParams.ControllerName, - buildDescriptorParams.ViewName, - buildDescriptorParams.Extra), - descriptor.Templates, - searchedLocations)) - { - return null; - } - - if (!string.IsNullOrEmpty(buildDescriptorParams.MasterName)) - { - if (!LocatePotentialTemplate( - PotentialMasterLocations(buildDescriptorParams.MasterName, - buildDescriptorParams.Extra), - descriptor.Templates, - searchedLocations)) - { - return null; - } - } - else if (buildDescriptorParams.FindDefaultMaster && TrailingUseMasterName(descriptor) == null /*empty is a valid value*/) - { - LocatePotentialTemplate( - PotentialDefaultMasterLocations(buildDescriptorParams.ControllerName, - buildDescriptorParams.Extra), - descriptor.Templates, - null); - } - - var trailingUseMaster = TrailingUseMasterName(descriptor); - while (buildDescriptorParams.FindDefaultMaster && !string.IsNullOrEmpty(trailingUseMaster)) - { - if (!LocatePotentialTemplate( - PotentialMasterLocations(trailingUseMaster, - buildDescriptorParams.Extra), - descriptor.Templates, - searchedLocations)) - { - return null; - } - trailingUseMaster = TrailingUseMasterName(descriptor); - } - - return descriptor; - } - - /// - /// Simplified parser for <use master=""/> detection. - /// TODO: get rid of this. - /// switch to a cache of view-file to master location with iscurrent detection? - /// - class UseMasterGrammar : CharGrammar - { - public UseMasterGrammar(string _prefix) - { - var whiteSpace0 = Rep(Ch(char.IsWhiteSpace)); - var whiteSpace1 = Rep1(Ch(char.IsWhiteSpace)); - var startOfElement = !string.IsNullOrEmpty(_prefix) ? Ch("<" + _prefix + ":use") : Ch(""); - - var useMaster = startOfElement - .And(whiteSpace1) - .And(startOfAttribute) - .And(attrValue) - .And(whiteSpace0) - .And(endOfElement) - .Build(hit => new string(hit.Left.Left.Down.Left.Down.ToArray())); - - ParseUseMaster = - pos => - { - for (var scan = pos; scan.PotentialLength() != 0; scan = scan.Advance(1)) - { - var result = useMaster(scan); - if (result != null) - { - return result; - } - } - return null; - }; - } - - public ParseAction ParseUseMaster { get; set; } - } - - private UseMasterGrammar _grammar; - public ParseAction ParseUseMaster => _grammar.ParseUseMaster; - - public string TrailingUseMasterName(SparkViewDescriptor descriptor) - { - var lastTemplate = descriptor.Templates.Last(); - var sourceContext = AbstractSyntaxProvider.CreateSourceContext(lastTemplate, _viewFolder); - if (sourceContext == null) - { - return null; - } - - var result = ParseUseMaster(new Position(sourceContext)); - return result == null ? null : result.Value; - } - - private bool LocatePotentialTemplate( - IEnumerable potentialTemplates, - ICollection descriptorTemplates, - ICollection searchedLocations) - { - var template = potentialTemplates.FirstOrDefault(t => _viewFolder.HasView(t)); - if (template != null) - { - descriptorTemplates.Add(template); - return true; - } - if (searchedLocations != null) - { - foreach (var potentialTemplate in potentialTemplates) - { - searchedLocations.Add(potentialTemplate); - } - } - return false; - } - - protected IEnumerable ApplyFilters(IEnumerable locations, IDictionary extra) - { - // apply all of the filters PotentialLocations in order - return Filters.Aggregate( - locations, - (aggregate, filter) => filter.PotentialLocations(aggregate, extra)); - } - - protected virtual IEnumerable PotentialViewLocations(string controllerName, string viewName, IDictionary extra) - { - if (extra.ContainsKey("area") && !string.IsNullOrEmpty(extra["area"] as string)) - { - return ApplyFilters(new[] - { - string.Format("~{0}Areas{0}{1}{0}Views{0}{2}{0}{3}.spark", Path.DirectorySeparatorChar, extra["area"], controllerName, viewName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, extra["area"], viewName), - string.Format("{0}{1}{2}.spark", controllerName, Path.DirectorySeparatorChar, viewName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, viewName), - string.Format("~{0}Areas{0}{1}{0}Views{0}{2}{0}{3}.shade", Path.DirectorySeparatorChar, extra["area"], controllerName, viewName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, extra["area"], viewName), - string.Format("{0}{1}{2}.shade", controllerName, Path.DirectorySeparatorChar, viewName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, viewName) - }, extra); - } - return ApplyFilters(new[] - { - string.Format("{0}{1}{2}.spark", controllerName,Path.DirectorySeparatorChar, viewName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,viewName), - string.Format("{0}{1}{2}.shade", controllerName,Path.DirectorySeparatorChar, viewName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,viewName) - }, extra); - } - - protected virtual IEnumerable PotentialMasterLocations(string masterName, IDictionary extra) - { - if (extra.ContainsKey("area") && !string.IsNullOrEmpty(extra["area"] as string)) - { - return ApplyFilters(new[] - { - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.spark", Path.DirectorySeparatorChar, extra["area"], masterName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, extra["area"], masterName), - string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar,masterName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,masterName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.shade", Path.DirectorySeparatorChar, extra["area"], masterName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, extra["area"], masterName), - string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar,masterName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,masterName) - }, extra); - } - return ApplyFilters(new[] - { - string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar,masterName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,masterName), - string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar,masterName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,masterName) - }, extra); - } - - protected virtual IEnumerable PotentialDefaultMasterLocations(string controllerName, IDictionary extra) - { - if (extra.ContainsKey("area") && !string.IsNullOrEmpty(extra["area"] as string)) - { - return ApplyFilters(new[] - { - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.spark", Path.DirectorySeparatorChar, extra["area"], controllerName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, extra["area"], controllerName), - string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}Application.spark", Path.DirectorySeparatorChar, extra["area"]), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}Application.spark", Path.DirectorySeparatorChar, extra["area"]), - string.Format("Layouts{0}Application.spark", Path.DirectorySeparatorChar), - string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.shade", Path.DirectorySeparatorChar, extra["area"], controllerName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, extra["area"], controllerName), - string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), - string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}Application.shade", Path.DirectorySeparatorChar, extra["area"]), - string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}Application.shade", Path.DirectorySeparatorChar, extra["area"]), - string.Format("Layouts{0}Application.shade", Path.DirectorySeparatorChar), - string.Format("Shared{0}Application.shade", Path.DirectorySeparatorChar) - }, extra); - } - - return ApplyFilters(new[] - { - string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), - string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), - string.Format("Layouts{0}Application.spark", Path.DirectorySeparatorChar), - string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), - string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), - string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), - string.Format("Layouts{0}Application.shade", Path.DirectorySeparatorChar), - string.Format("Shared{0}Application.shade", Path.DirectorySeparatorChar) - }, extra); - } - } +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; + +namespace Spark.Descriptors +{ + public class DescriptorBuilder : IDescriptorBuilder + { + private IViewFolder _viewFolder; + + public DescriptorBuilder(ISparkSettings settings, IViewFolder viewFolder) + { + this.Filters = new List + { + new AreaDescriptorFilter() + }; + + this._grammar = new UseMasterGrammar(settings.Prefix); + + this._viewFolder = viewFolder; + } + + public IList Filters { get; set; } + + public virtual IDictionary GetExtraParameters(SparkRouteData routeData) + { + var extra = new Dictionary(); + foreach (var filter in this.Filters) + { + filter.ExtraParameters(routeData, extra); + } + + return extra; + } + + public virtual SparkViewDescriptor BuildDescriptor(BuildDescriptorParams buildDescriptorParams, ICollection searchedLocations) + { + var descriptor = new SparkViewDescriptor + { + TargetNamespace = buildDescriptorParams.TargetNamespace + }; + + if (!this.LocatePotentialTemplate( + this.PotentialViewLocations(buildDescriptorParams.ControllerName, + buildDescriptorParams.ViewName, + buildDescriptorParams.Extra), + descriptor.Templates, + searchedLocations)) + { + return null; + } + + if (!string.IsNullOrEmpty(buildDescriptorParams.MasterName)) + { + if (!this.LocatePotentialTemplate( + this.PotentialMasterLocations(buildDescriptorParams.MasterName, + buildDescriptorParams.Extra), + descriptor.Templates, + searchedLocations)) + { + return null; + } + } + else if (buildDescriptorParams.FindDefaultMaster && this.TrailingUseMasterName(descriptor) == null /*empty is a valid value*/) + { + this.LocatePotentialTemplate( + this.PotentialDefaultMasterLocations(buildDescriptorParams.ControllerName, + buildDescriptorParams.Extra), + descriptor.Templates, + null); + } + + var trailingUseMaster = this.TrailingUseMasterName(descriptor); + while (buildDescriptorParams.FindDefaultMaster && !string.IsNullOrEmpty(trailingUseMaster)) + { + if (!this.LocatePotentialTemplate( + this.PotentialMasterLocations(trailingUseMaster, + buildDescriptorParams.Extra), + descriptor.Templates, + searchedLocations)) + { + return null; + } + trailingUseMaster = this.TrailingUseMasterName(descriptor); + } + + return descriptor; + } + + /// + /// Simplified parser for <use master=""/> detection. + /// TODO: get rid of this. + /// switch to a cache of view-file to master location with iscurrent detection? + /// + class UseMasterGrammar : CharGrammar + { + public UseMasterGrammar(string _prefix) + { + var whiteSpace0 = Rep(Ch(char.IsWhiteSpace)); + var whiteSpace1 = Rep1(Ch(char.IsWhiteSpace)); + var startOfElement = !string.IsNullOrEmpty(_prefix) ? Ch("<" + _prefix + ":use") : Ch(""); + + var useMaster = startOfElement + .And(whiteSpace1) + .And(startOfAttribute) + .And(attrValue) + .And(whiteSpace0) + .And(endOfElement) + .Build(hit => new string(hit.Left.Left.Down.Left.Down.ToArray())); + + this.ParseUseMaster = + pos => + { + for (var scan = pos; scan.PotentialLength() != 0; scan = scan.Advance(1)) + { + var result = useMaster(scan); + if (result != null) + { + return result; + } + } + return null; + }; + } + + public ParseAction ParseUseMaster { get; set; } + } + + private UseMasterGrammar _grammar; + public ParseAction ParseUseMaster => this._grammar.ParseUseMaster; + + public string TrailingUseMasterName(SparkViewDescriptor descriptor) + { + var lastTemplate = descriptor.Templates.Last(); + var sourceContext = AbstractSyntaxProvider.CreateSourceContext(lastTemplate, this._viewFolder); + if (sourceContext == null) + { + return null; + } + + var result = this.ParseUseMaster(new Position(sourceContext)); + return result == null ? null : result.Value; + } + + private bool LocatePotentialTemplate( + IEnumerable potentialTemplates, + ICollection descriptorTemplates, + ICollection searchedLocations) + { + var template = potentialTemplates.FirstOrDefault(t => this._viewFolder.HasView(t)); + if (template != null) + { + descriptorTemplates.Add(template); + return true; + } + if (searchedLocations != null) + { + foreach (var potentialTemplate in potentialTemplates) + { + searchedLocations.Add(potentialTemplate); + } + } + return false; + } + + protected IEnumerable ApplyFilters(IEnumerable locations, IDictionary extra) + { + // apply all of the filters PotentialLocations in order + return this.Filters.Aggregate( + locations, + (aggregate, filter) => filter.PotentialLocations(aggregate, extra)); + } + + protected virtual IEnumerable PotentialViewLocations(string controllerName, string viewName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return this.ApplyFilters(new[] + { + string.Format("~{0}Areas{0}{1}{0}Views{0}{2}{0}{3}.spark", Path.DirectorySeparatorChar, area, controllerName, viewName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, area, viewName), + string.Format("{0}{1}{2}.spark", controllerName, Path.DirectorySeparatorChar, viewName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, viewName), + string.Format("~{0}Areas{0}{1}{0}Views{0}{2}{0}{3}.shade", Path.DirectorySeparatorChar, area, controllerName, viewName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, area, viewName), + string.Format("{0}{1}{2}.shade", controllerName, Path.DirectorySeparatorChar, viewName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, viewName) + }, extra); + } + return this.ApplyFilters(new[] + { + string.Format("{0}{1}{2}.spark", controllerName,Path.DirectorySeparatorChar, viewName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,viewName), + string.Format("{0}{1}{2}.shade", controllerName,Path.DirectorySeparatorChar, viewName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,viewName) + }, extra); + } + + protected virtual IEnumerable PotentialMasterLocations(string masterName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return this.ApplyFilters(new[] + { + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.spark", Path.DirectorySeparatorChar, area, masterName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, area, masterName), + string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar,masterName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,masterName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.shade", Path.DirectorySeparatorChar, area, masterName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, area, masterName), + string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar,masterName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,masterName) + }, extra); + } + return this.ApplyFilters(new[] + { + string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar,masterName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar,masterName), + string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar,masterName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar,masterName) + }, extra); + } + + protected virtual IEnumerable PotentialDefaultMasterLocations(string controllerName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return this.ApplyFilters(new[] + { + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.spark", Path.DirectorySeparatorChar, area, controllerName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.spark", Path.DirectorySeparatorChar, area, controllerName), + string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}Application.spark", Path.DirectorySeparatorChar, area), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}Application.spark", Path.DirectorySeparatorChar, area), + string.Format("Layouts{0}Application.spark", Path.DirectorySeparatorChar), + string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}{2}.shade", Path.DirectorySeparatorChar, area, controllerName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}{2}.shade", Path.DirectorySeparatorChar, area, controllerName), + string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), + string.Format("~{0}Areas{0}{1}{0}Views{0}Layouts{0}Application.shade", Path.DirectorySeparatorChar, area), + string.Format("~{0}Areas{0}{1}{0}Views{0}Shared{0}Application.shade", Path.DirectorySeparatorChar, area), + string.Format("Layouts{0}Application.shade", Path.DirectorySeparatorChar), + string.Format("Shared{0}Application.shade", Path.DirectorySeparatorChar) + }, extra); + } + + return this.ApplyFilters(new[] + { + string.Format("Layouts{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), + string.Format("Shared{0}{1}.spark", Path.DirectorySeparatorChar, controllerName), + string.Format("Layouts{0}Application.spark", Path.DirectorySeparatorChar), + string.Format("Shared{0}Application.spark", Path.DirectorySeparatorChar), + string.Format("Layouts{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), + string.Format("Shared{0}{1}.shade", Path.DirectorySeparatorChar, controllerName), + string.Format("Layouts{0}Application.shade", Path.DirectorySeparatorChar), + string.Format("Shared{0}Application.shade", Path.DirectorySeparatorChar) + }, extra); + } + } } \ No newline at end of file diff --git a/src/Spark/Descriptors/DescriptorFilterBase.cs b/src/Spark/Descriptors/DescriptorFilterBase.cs new file mode 100644 index 00000000..e75539ee --- /dev/null +++ b/src/Spark/Descriptors/DescriptorFilterBase.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Spark.Descriptors +{ + public abstract class DescriptorFilterBase : IDescriptorFilter + { + public abstract void ExtraParameters(SparkRouteData routeData, IDictionary extra); + + public abstract IEnumerable PotentialLocations( + IEnumerable locations, + IDictionary extra); + + protected static bool TryGetString(IDictionary extra, string name, out string value) + { + if (extra.TryGetValue(name, out var obj)) + { + value = Convert.ToString(obj); + return !string.IsNullOrEmpty(value); + } + + value = null; + + return false; + } + } +} diff --git a/src/Spark/Descriptors/IDescriptorBuilder.cs b/src/Spark/Descriptors/IDescriptorBuilder.cs new file mode 100644 index 00000000..0e94f736 --- /dev/null +++ b/src/Spark/Descriptors/IDescriptorBuilder.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Spark.Descriptors +{ + public interface IDescriptorBuilder + { + /// + /// Implemented by custom descriptor builder to quickly extract additional parameters needed + /// to locate templates, like the theme or language in effect for the request + /// + /// The current request's route data + /// An in-order array of values which are meaningful to BuildDescriptor on the same implementation class + IDictionary GetExtraParameters(SparkRouteData routeData); + + /// + /// Given a set of MVC-specific parameters, a descriptor for the target view is created. This can + /// be a bit more expensive because the existence of files is tested at various candidate locations. + /// + /// Contains all of the standard and extra parameters which contribute to a descriptor + /// Candidate locations are added to this collection so an information-rich error may be returned + /// The descriptor with all of the detected view locations in order + SparkViewDescriptor BuildDescriptor(BuildDescriptorParams buildDescriptorParams, ICollection searchedLocations); + } +} diff --git a/src/Spark.Web.Mvc/Descriptors/IDescriptorFilter.cs b/src/Spark/Descriptors/IDescriptorFilter.cs similarity index 82% rename from src/Spark.Web.Mvc/Descriptors/IDescriptorFilter.cs rename to src/Spark/Descriptors/IDescriptorFilter.cs index 879d2cfa..2c4c0766 100644 --- a/src/Spark.Web.Mvc/Descriptors/IDescriptorFilter.cs +++ b/src/Spark/Descriptors/IDescriptorFilter.cs @@ -1,29 +1,28 @@ -using System.Collections.Generic; -using System.Web.Mvc; - -namespace Spark.Web.Mvc.Descriptors -{ - /// - /// A descriptor filter may be added to the DefaultDescriptorBuilder to extend - /// - public interface IDescriptorFilter - { - /// - /// Called frequently to extract filter-specific parameters from a request context. This call - /// happens on every request so should be implemented as efficiently as possible. - /// - /// The current request's controller context - /// Dictionary where additional parameters should be added - void ExtraParameters(ControllerContext context, IDictionary extra); - - /// - /// The DefaultDescriptorBuider calls this method for the filter to return a modified enumerable - /// ordered list of potential template locations. This is called only when the unique combination of controller, - /// master, view, and extra have not been resolved previously. - /// - /// incoming ordered list of locations - /// extra parameters which have been extracted - /// either the original list or a new, augmented, enumerable list - IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra); - } -} +using System.Collections.Generic; + +namespace Spark.Descriptors +{ + /// + /// A descriptor filter may be added to the DefaultDescriptorBuilder to extend + /// + public interface IDescriptorFilter + { + /// + /// Called frequently to extract filter-specific parameters from a request context. This call + /// happens on every request so should be implemented as efficiently as possible. + /// + /// The current request's route data + /// Dictionary where additional parameters should be added + void ExtraParameters(SparkRouteData routeData, IDictionary extra); + + /// + /// The DefaultDescriptorBuider calls this method for the filter to return a modified enumerable + /// ordered list of potential template locations. This is called only when the unique combination of controller, + /// master, view, and extra have not been resolved previously. + /// + /// incoming ordered list of locations + /// extra parameters which have been extracted + /// either the original list or a new, augmented, enumerable list + IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra); + } +} diff --git a/src/Spark.Web.Mvc/Descriptors/LanguageDescriptorFilter.cs b/src/Spark/Descriptors/LanguageDescriptorFilter.cs similarity index 66% rename from src/Spark.Web.Mvc/Descriptors/LanguageDescriptorFilter.cs rename to src/Spark/Descriptors/LanguageDescriptorFilter.cs index fad520b8..76504eac 100644 --- a/src/Spark.Web.Mvc/Descriptors/LanguageDescriptorFilter.cs +++ b/src/Spark/Descriptors/LanguageDescriptorFilter.cs @@ -1,63 +1,64 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Mvc; - -namespace Spark.Web.Mvc.Descriptors -{ - public abstract class LanguageDescriptorFilter : DescriptorFilterBase - { - public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) - { - string languageName; - if (!TryGetString(extra, "language", out languageName)) - return locations; - - var extension = languageName + ".spark"; - - var slashPos = languageName.IndexOf('-'); - if (slashPos == -1) - { - return locations.SelectMany( - path => new[] - { - Path.ChangeExtension(path, extension), - path - }); - } - - var shortExtension = languageName.Substring(0, slashPos) + ".spark"; - return locations.SelectMany( - path => new[] - { - Path.ChangeExtension(path, extension), - Path.ChangeExtension(path, shortExtension), - path - }); - } - - public static LanguageDescriptorFilter For(Func selector) - { - return new Delegated(selector); - } - - class Delegated : LanguageDescriptorFilter - { - private readonly Func _selector; - - public Delegated(Func selector) - { - _selector = selector; - } - - public override void ExtraParameters(ControllerContext context, IDictionary extra) - { - var theme = Convert.ToString(_selector(context)); - if (!string.IsNullOrEmpty(theme)) - extra["language"] = theme; - } - - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Spark.Descriptors +{ + public abstract class LanguageDescriptorFilter : DescriptorFilterBase + { + public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) + { + if (!TryGetString(extra, "language", out var languageName)) + { + return locations; + } + + var extension = languageName + ".spark"; + + var slashPos = languageName.IndexOf('-'); + if (slashPos == -1) + { + return locations.SelectMany( + path => new[] + { + Path.ChangeExtension(path, extension), + path + }); + } + + var shortExtension = languageName.Substring(0, slashPos) + ".spark"; + return locations.SelectMany( + path => new[] + { + Path.ChangeExtension(path, extension), + Path.ChangeExtension(path, shortExtension), + path + }); + } + + public static LanguageDescriptorFilter For(Func selector) + { + return new Delegated(selector); + } + + class Delegated : LanguageDescriptorFilter + { + private readonly Func _selector; + + public Delegated(Func selector) + { + this._selector = selector; + } + + public override void ExtraParameters(SparkRouteData context, IDictionary extra) + { + var theme = Convert.ToString(this._selector(context)); + if (!string.IsNullOrEmpty(theme)) + { + extra["language"] = theme; + } + } + } + } +} diff --git a/src/Spark/Descriptors/SparkRouteData.cs b/src/Spark/Descriptors/SparkRouteData.cs new file mode 100644 index 00000000..ccc3b7fd --- /dev/null +++ b/src/Spark/Descriptors/SparkRouteData.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Spark.Descriptors; + +/// +/// Helper class to keep a strongly typed parameter where used but make it more explicit that we expect the route data. +/// +/// +/// Used to pass the route data whether it comes from ASP.NET or ASP.NET Core. +/// +public class SparkRouteData(IDictionary values) +{ + public IDictionary Values = values; +} \ No newline at end of file diff --git a/src/Spark/Descriptors/ThemeDescriptorFilter.cs b/src/Spark/Descriptors/ThemeDescriptorFilter.cs new file mode 100644 index 00000000..40eddbba --- /dev/null +++ b/src/Spark/Descriptors/ThemeDescriptorFilter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Spark.Descriptors +{ + public abstract class ThemeDescriptorFilter : DescriptorFilterBase + { + public override IEnumerable PotentialLocations(IEnumerable locations, IDictionary extra) + { + return TryGetString(extra, "theme", out var themeName) + ? locations + .Select(x => Path.Combine($"themes{Path.DirectorySeparatorChar}" + themeName, x)) + .Concat(locations) + : locations; + } + + public static ThemeDescriptorFilter For(Func selector) + { + return new Delegated(selector); + } + + class Delegated : ThemeDescriptorFilter + { + private readonly Func _selector; + + public Delegated(Func selector) + { + this._selector = selector; + } + + public override void ExtraParameters(SparkRouteData context, IDictionary extra) + { + var theme = Convert.ToString(this._selector(context)); + if (!string.IsNullOrEmpty(theme)) + extra["theme"] = theme; + } + } + } +} \ No newline at end of file From 948e34165a60cc129e797712efffeec8d1505561 Mon Sep 17 00:00:00 2001 From: bounav Date: Mon, 4 Mar 2024 11:44:51 +0000 Subject: [PATCH 13/14] New project to support AspNetCore.Mvc applications - New OutputValue(value, automaticEncoding) on SparkViewBase - Removed H() method (replaced by OutputValue - GeneratedCodeVisitor calls new OutputValue() method instead of Output.Write() - This method gives simplifies the HTML encoding logic - This method handles MvcHtmlString for MVC 5 - This method handles IHtmlContent for aspnetcore - HtmlHelperResultFilter to help get funnel the IHtmlHelper from the controllers to the view - Can now set base class for views with new SparkSettings() shortcut --- .../Home/TerseHtmlEncode.spark | 2 +- .../SparkViewFactoryTests.cs | 4 + .../Stubs/StubController.cs | 6 +- src/Castle.MonoRail.Views.Spark/SparkView.cs | 31 +- .../AspNetCoreDescriptorBuilder.cs | 77 +++++ src/Spark.AspNetCore.Mvc/EntryPoint.cs | 10 + .../Extensions/ServiceCollectionExtensions.cs | 72 +++++ .../Filters/HtmlHelperResultFilter.cs | 26 ++ .../Helpers/PathHelper.cs | 37 +++ .../Properties/launchSettings.json | 12 + .../Spark.AspNetCore.Mvc.csproj | 32 ++ .../SparkMvcOptionsSetup.cs | 13 + .../SparkMvcViewOptionsSetup.cs | 23 ++ src/Spark.AspNetCore.Mvc/SparkOptionsSetup.cs | 10 + src/Spark.AspNetCore.Mvc/SparkView.cs | 169 +++++++++++ src/Spark.AspNetCore.Mvc/SparkViewEngine.cs | 110 +++++++ src/Spark.AspNetCore.Mvc/SparkViewOfT.cs | 54 ++++ .../PythonViewCompilerTests.cs | 12 +- src/Spark.Ruby.Tests/RubyViewCompilerTests.cs | 9 +- src/Spark.Tests/SparkViewExtensions.cs | 3 +- .../Home/html-encode-function-h.spark | 5 - .../Compiler/CSharpViewCompilerTester.cs | 53 ++-- .../Compiler/VisualBasicViewCompilerTester.cs | 47 +-- .../DescriptorBuildingTester.cs | 60 ++-- .../Spark.Web.Mvc.Tests.csproj | 4 +- .../SparkBatchCompilerTester.cs | 27 +- .../SparkViewFactoryTester.cs | 11 - .../VisualBasicViewTester.cs | 5 +- .../Extensions/ServiceCollectionExtensions.cs | 3 +- src/Spark.Web.Mvc/SparkView.cs | 87 ++---- src/Spark.Web.Mvc/SparkViewFactory.cs | 275 +++++------------- src/Spark.Web.Mvc/SparkViewOfT.cs | 57 ++++ .../Compiler/SourceMappingTester.cs | 2 - .../Parser/AutomaticEncodingTester.cs | 9 - src/Spark.Web.Tests/Precompiled/View1.cs | 5 +- src/Spark.Web.Tests/Precompiled/View2.cs | 9 +- src/Spark.Web.Tests/Precompiled/View3.cs | 9 +- .../Home/LateBoundEvalResolvesViewData.spark | 1 - src/Spark.Web.Tests/SparkViewFactoryTester.cs | 1 - src/Spark.Web.Tests/Stubs/StubSparkView.cs | 11 +- src/Spark.Web.Tests/Stubs/StubSparkView2.cs | 5 - .../Configuration/SparkSectionHandler.cs | 6 +- src/Spark.sln | 11 +- src/Spark.sln.DotSettings | 4 +- .../ChunkVisitors/GeneratedCodeVisitor.cs | 12 +- .../Compiler/ChunkVisitors/ChunkVisitor.cs | 1 - .../DetectCodeExpressionVisitor.cs | 15 +- .../ChunkVisitors/GeneratedCodeVisitor.cs | 10 +- .../VisualBasic/VisualBasicViewCompiler.cs | 1 + src/Spark/ISparkPrecompiler.cs | 8 + src/Spark/ISparkSettings.cs | 2 +- src/Spark/SparkPrecompiler.cs | 184 ++++++++++++ src/Spark/SparkSettings.cs | 36 ++- src/Spark/SparkViewBase.cs | 31 +- src/Spark/SparkViewDecorator.cs | 10 +- 55 files changed, 1246 insertions(+), 483 deletions(-) create mode 100644 src/Spark.AspNetCore.Mvc/AspNetCoreDescriptorBuilder.cs create mode 100644 src/Spark.AspNetCore.Mvc/EntryPoint.cs create mode 100644 src/Spark.AspNetCore.Mvc/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Spark.AspNetCore.Mvc/Filters/HtmlHelperResultFilter.cs create mode 100644 src/Spark.AspNetCore.Mvc/Helpers/PathHelper.cs create mode 100644 src/Spark.AspNetCore.Mvc/Properties/launchSettings.json create mode 100644 src/Spark.AspNetCore.Mvc/Spark.AspNetCore.Mvc.csproj create mode 100644 src/Spark.AspNetCore.Mvc/SparkMvcOptionsSetup.cs create mode 100644 src/Spark.AspNetCore.Mvc/SparkMvcViewOptionsSetup.cs create mode 100644 src/Spark.AspNetCore.Mvc/SparkOptionsSetup.cs create mode 100644 src/Spark.AspNetCore.Mvc/SparkView.cs create mode 100644 src/Spark.AspNetCore.Mvc/SparkViewEngine.cs create mode 100644 src/Spark.AspNetCore.Mvc/SparkViewOfT.cs delete mode 100644 src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/html-encode-function-h.spark rename src/{Spark.Web.Tests => Spark.Web.Mvc.Tests}/Compiler/CSharpViewCompilerTester.cs (89%) rename src/{Spark.Web.Tests => Spark.Web.Mvc.Tests}/Compiler/VisualBasicViewCompilerTester.cs (90%) rename src/{Spark.Web.Tests => Spark.Web.Mvc.Tests}/VisualBasicViewTester.cs (98%) create mode 100644 src/Spark.Web.Mvc/SparkViewOfT.cs create mode 100644 src/Spark/ISparkPrecompiler.cs create mode 100644 src/Spark/SparkPrecompiler.cs diff --git a/src/Castle.MonoRail.Views.Spark.Tests/MonoRail.Tests.Views/Home/TerseHtmlEncode.spark b/src/Castle.MonoRail.Views.Spark.Tests/MonoRail.Tests.Views/Home/TerseHtmlEncode.spark index 758a7bd2..7fc43e5d 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/MonoRail.Tests.Views/Home/TerseHtmlEncode.spark +++ b/src/Castle.MonoRail.Views.Spark.Tests/MonoRail.Tests.Views/Home/TerseHtmlEncode.spark @@ -1 +1 @@ -

${H("This html")}

+

${"This html"}

diff --git a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs index be3168fa..043f055e 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/SparkViewFactoryTests.cs @@ -33,6 +33,8 @@ protected override void Configure() var settings = new SparkSettings() .SetBaseClassTypeName(typeof(SparkView)); + settings.AutomaticEncoding = true; + serviceProvider.AddService(typeof(ISparkSettings), settings); factory = new SparkViewFactory(); @@ -132,6 +134,8 @@ public void TerseHtmlEncode() { mocks.ReplayAll(); manager.Process(string.Format("Home{0}TerseHtmlEncode", Path.DirectorySeparatorChar), output, engineContext, controller, controllerContext); + + // See AutomaticEncoding = true in Configure() method ContainsInOrder(output.ToString(), "

This <contains/> html

"); } diff --git a/src/Castle.MonoRail.Views.Spark.Tests/Stubs/StubController.cs b/src/Castle.MonoRail.Views.Spark.Tests/Stubs/StubController.cs index 9956d546..9572c999 100644 --- a/src/Castle.MonoRail.Views.Spark.Tests/Stubs/StubController.cs +++ b/src/Castle.MonoRail.Views.Spark.Tests/Stubs/StubController.cs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; + using Castle.MonoRail.Framework; namespace Castle.MonoRail.Views.Spark.Tests.Stubs @@ -34,7 +31,6 @@ public void List() [Layout("ajax")] public void _Widget() { - } } } diff --git a/src/Castle.MonoRail.Views.Spark/SparkView.cs b/src/Castle.MonoRail.Views.Spark/SparkView.cs index 6dc6c525..e87ccdc4 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkView.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkView.cs @@ -15,7 +15,6 @@ namespace Castle.MonoRail.Views.Spark { using System; - using System.Linq; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -106,13 +105,33 @@ public virtual void Contextualize(IEngineContext context, IControllerContext con OnceTable = outerView.OnceTable; } - public string H(object value) + public override void OutputValue(object value, bool automaticEncoding) { - if (value is HtmlString) - return value.ToString(); - return Server.HtmlEncode(Convert.ToString(value)); + // Always encode when automatic encoding enabled or HtmlString (includes MvcHtmlString) + if (automaticEncoding || value is HtmlString) + { + OutputEncodedValue(value); + } + else + { + Output.Write(value); + } + } + + public void OutputEncodedValue(object value) + { + if (value is string stringValue) + { + var encoded = System.Web.HttpUtility.HtmlEncode(stringValue); + + Output.Write(encoded); + } + else + { + Output.Write(value.ToString()); + } } - + public object HTML(object value) { return new HtmlString(Convert.ToString(value)); diff --git a/src/Spark.AspNetCore.Mvc/AspNetCoreDescriptorBuilder.cs b/src/Spark.AspNetCore.Mvc/AspNetCoreDescriptorBuilder.cs new file mode 100644 index 00000000..ef8142d9 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/AspNetCoreDescriptorBuilder.cs @@ -0,0 +1,77 @@ +using Spark.Descriptors; +using Spark.FileSystem; + +namespace Spark.AspNetCore.Mvc; + +public class AspNetCoreDescriptorBuilder : DescriptorBuilder +{ + public AspNetCoreDescriptorBuilder(ISparkSettings settings, IViewFolder viewFolder) : base(settings, viewFolder) + { + } + + protected override IEnumerable PotentialViewLocations(string controllerName, string viewName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return ApplyFilters([ + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}{controllerName}{Path.DirectorySeparatorChar}{viewName}.spark", + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Shared{Path.DirectorySeparatorChar}{viewName}.spark", + $"{controllerName}{Path.DirectorySeparatorChar}{viewName}.spark", + $"Shared{Path.DirectorySeparatorChar}{viewName}.spark" + ], extra); + } + + return ApplyFilters([ + $"{controllerName}{Path.DirectorySeparatorChar}{viewName}.spark", + $"Shared{Path.DirectorySeparatorChar}{viewName}.spark" + ], extra); + } + + protected override IEnumerable PotentialMasterLocations(string masterName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return ApplyFilters([ + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Layouts{Path.DirectorySeparatorChar}{masterName}.spark", + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Shared{Path.DirectorySeparatorChar}{masterName}.spark", + $"Layouts{Path.DirectorySeparatorChar}{masterName}.spark", + $"Shared{Path.DirectorySeparatorChar}{masterName}.spark" + ], extra); + } + + return ApplyFilters([ + $"Layouts{Path.DirectorySeparatorChar}{masterName}.spark", + $"Shared{Path.DirectorySeparatorChar}{masterName}.spark" + ], extra); + } + + protected override IEnumerable PotentialDefaultMasterLocations(string controllerName, IDictionary extra) + { + if (extra.TryGetValue("area", out var value)) + { + var area = value as string; + + return ApplyFilters([ + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Layouts{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Shared{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Layouts{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Shared{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Layouts{Path.DirectorySeparatorChar}Application.spark", + $"Areas{Path.DirectorySeparatorChar}{area}{Path.DirectorySeparatorChar}Views{Path.DirectorySeparatorChar}Shared{Path.DirectorySeparatorChar}Application.spark", + $"Layouts{Path.DirectorySeparatorChar}Application.spark", + $"Shared{Path.DirectorySeparatorChar}Application.spark", + ], extra); + } + + return ApplyFilters([ + $"Layouts{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Shared{Path.DirectorySeparatorChar}{controllerName}.spark", + $"Layouts{Path.DirectorySeparatorChar}Application.spark", + $"Shared{Path.DirectorySeparatorChar}Application.spark" + ], extra); + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/EntryPoint.cs b/src/Spark.AspNetCore.Mvc/EntryPoint.cs new file mode 100644 index 00000000..09c85e9f --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/EntryPoint.cs @@ -0,0 +1,10 @@ +namespace Spark.AspNetCore.Mvc +{ + public static class EntryPoint + { + public static void Main(string[] arguments) + { + throw new NotImplementedException("This method should never be called. It is only here to satisfy the compiler (see in the .csproj file)"); + } + } +} diff --git a/src/Spark.AspNetCore.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.AspNetCore.Mvc/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..034d8aa0 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Spark.Bindings; +using Spark.Compiler; +using Spark.Compiler.Roslyn; +using Spark.Descriptors; +using Spark.FileSystem; +using Spark.Parser; +using Spark.Parser.Syntax; + +namespace Spark.AspNetCore.Mvc.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddSpark(this IServiceCollection services, Action? setupAction = null) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services + .AddOptions() + .AddSingleton, SparkOptionsSetup>() + .AddTransient(f => f.GetService>()?.Value) + .AddTransient(f => f.GetService>()?.Value); + + services + .AddMemoryCache() + .AddTransient(); + + if (setupAction != null) + { + services.Configure(setupAction); + } + + // TODO: Reduce memory consumption cause by loading the assembly of the views compiled dynamically + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(f => f.GetService().CreateDefaultViewFolder()) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + services.AddSingleton(c => null); + + services + .AddSingleton(); + + services + .AddSingleton(); + + services + .AddTransient, SparkMvcOptionsSetup>() + .AddTransient, SparkMvcViewOptionsSetup>() + .AddSingleton(); + + return services; + } + } +} diff --git a/src/Spark.AspNetCore.Mvc/Filters/HtmlHelperResultFilter.cs b/src/Spark.AspNetCore.Mvc/Filters/HtmlHelperResultFilter.cs new file mode 100644 index 00000000..534aafc6 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/Filters/HtmlHelperResultFilter.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Spark.AspNetCore.Mvc.Filters +{ + /// + /// Result filter to pass the HTML Helper down to the spark view. + /// + public class HtmlHelperResultFilter(IHtmlHelper htmlHelper) : IAlwaysRunResultFilter + { + public void OnResultExecuting(ResultExecutingContext context) + { + if (context.Controller is Controller controller) + { + // The HtmlHelper will have to contextualised before being used in the view + // See SparkView.RenderAsync() + controller.ViewData["Html"] = htmlHelper; + } + } + + public void OnResultExecuted(ResultExecutedContext context) + { + } + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/Helpers/PathHelper.cs b/src/Spark.AspNetCore.Mvc/Helpers/PathHelper.cs new file mode 100644 index 00000000..4a1dbc1f --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/Helpers/PathHelper.cs @@ -0,0 +1,37 @@ +namespace Spark.AspNetCore.Mvc.Helpers; + +public static class PathHelper +{ + public static string GetAbsolutePath(string executingFilePath, string pagePath) + { + // Path is not valid or a page name; no change required. + if (string.IsNullOrEmpty(pagePath) || !IsRelativePath(pagePath)) + { + return pagePath; + } + + if (IsAbsolutePath(pagePath)) + { + // An absolute path already; no change required. + return pagePath.Replace("~/", string.Empty); + } + + // Given a relative path i.e. not yet application-relative (starting with "~/" or "/"), interpret + // path relative to currently-executing view, if any. + if (string.IsNullOrEmpty(executingFilePath)) + { + // Not yet executing a view. Start in app root. + return $"/{pagePath}"; + } + + // Get directory name (including final slash) but do not use Path.GetDirectoryName() to preserve path + // normalization. + var index = executingFilePath.LastIndexOf('/'); + return executingFilePath.Substring(0, index + 1) + pagePath; + } + + public static bool IsAbsolutePath(string name) => name.StartsWith("~/") || name.StartsWith("/"); + + // Though ./ViewName looks like a relative path, framework searches for that view using view locations. + public static bool IsRelativePath(string name) => !IsAbsolutePath(name); +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/Properties/launchSettings.json b/src/Spark.AspNetCore.Mvc/Properties/launchSettings.json new file mode 100644 index 00000000..c1e85032 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Spark.AspNetCore.Mvc": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:49470;http://localhost:49471" + } + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/Spark.AspNetCore.Mvc.csproj b/src/Spark.AspNetCore.Mvc/Spark.AspNetCore.Mvc.csproj new file mode 100644 index 00000000..e541238c --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/Spark.AspNetCore.Mvc.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + True + \ + + + True + \ + + + SparkKey.snk + + + + diff --git a/src/Spark.AspNetCore.Mvc/SparkMvcOptionsSetup.cs b/src/Spark.AspNetCore.Mvc/SparkMvcOptionsSetup.cs new file mode 100644 index 00000000..94d60065 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkMvcOptionsSetup.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Spark.AspNetCore.Mvc.Filters; + +namespace Spark.AspNetCore.Mvc; + +public class SparkMvcOptionsSetup : IConfigureOptions +{ + public void Configure(MvcOptions options) + { + options.Filters.Add(); + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/SparkMvcViewOptionsSetup.cs b/src/Spark.AspNetCore.Mvc/SparkMvcViewOptionsSetup.cs new file mode 100644 index 00000000..7aa5c9f0 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkMvcViewOptionsSetup.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Spark.AspNetCore.Mvc; + +public class SparkMvcViewOptionsSetup(ISparkViewEngine sparkCoreViewEngine) : IConfigureOptions +{ + private readonly ISparkViewEngine SparkCoreViewEngine = sparkCoreViewEngine ?? throw new ArgumentNullException(nameof(sparkCoreViewEngine)); + + /// + /// Configures to use . + /// + /// The to configure. + public void Configure(MvcViewOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewEngines.Add(SparkCoreViewEngine); + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/SparkOptionsSetup.cs b/src/Spark.AspNetCore.Mvc/SparkOptionsSetup.cs new file mode 100644 index 00000000..21b06e1c --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkOptionsSetup.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Options; + +namespace Spark.AspNetCore.Mvc; + +public class SparkOptionsSetup : IConfigureOptions +{ + public void Configure(SparkSettings options) + { + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/SparkView.cs b/src/Spark.AspNetCore.Mvc/SparkView.cs new file mode 100644 index 00000000..ba50b383 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkView.cs @@ -0,0 +1,169 @@ +using System.Configuration; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Spark.AspNetCore.Mvc; + +public abstract class SparkView : SparkViewBase, IView +{ + /// Set by method. + protected ViewContext? ViewContext; + + public string Path { get; set; } + + #region Exposing properties + + /// + /// Html encoder used to encode content. + /// + protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default; + + /// + /// Url encoder used to encode content. + /// + protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default; + + /// + /// JavaScript encoder used to encode content. + /// + protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default; + + public ViewDataDictionary ViewData => this.ViewContext.ViewData; + + public dynamic ViewBag => this.ViewContext.ViewBag; + + public ITempDataDictionary TempData => this.ViewContext.TempData; + + public HttpContext Context => this.ViewContext.HttpContext; + + public HttpRequest Request => this.ViewContext.HttpContext.Request; + + public HttpResponse Response => this.ViewContext.HttpContext.Response; + + public IHtmlHelper Html + { + get + { + if (this.ViewContext.ViewData.TryGetValue("Html", out object value)) + { + return (IHtmlHelper) value; + } + + throw new ConfigurationErrorsException($"Html not set in ViewData, is {nameof(Spark.AspNetCore.Mvc.Filters.HtmlHelperResultFilter)} configured?"); + } + } + + #endregion + + public override void OutputValue(object value, bool automaticEncoding) + { + // Always encode when automatic encoding is enabled or value is IHtmlContent + if (automaticEncoding || value is IHtmlContent) + { + OutputEncodedValue(value); + } + else + { + Output.Write(value); + } + } + + public void OutputEncodedValue(object value) + { + if (value is string stringValue) + { + var encoded = HtmlEncoder.Default.Encode(stringValue); + + Output.Write(encoded); + } + else if (value is IHtmlContent htmlContent) + { + htmlContent.WriteTo(Output, HtmlEncoder.Default); + } + else + { + Output.Write(value.ToString()); + } + } + + public IHtmlContent HTML(object value) + { + return new HtmlString(Convert.ToString(value)); + } + + public object Eval(string expression) + { + return ViewData.Eval(expression); + } + public string Eval(string expression, string format) + { + return ViewData.Eval(expression, format); + } + + public Task RenderAsync(ViewContext context) + { + this.ViewContext = context; + + // Checks if HtmlHelperResultFilter set the HTML helper in the view data + if (this.ViewContext.ViewData.TryGetValue("Html", out var htmlHelper)) + { + if (htmlHelper is IViewContextAware viewContextAware) + { + viewContextAware.Contextualize(this.ViewContext); + } + } + + var outerView = this.ViewContext.View as SparkViewBase; + var isNestedView = outerView != null && ReferenceEquals(this, outerView) == false; + + var priorContent = this.Content; + var priorOnce = this.OnceTable; + TextWriter priorContentView = null; + + if (isNestedView) + { + // set aside the "view" content, to avoid modification + if (outerView.Content.TryGetValue("view", out priorContentView)) + { + outerView.Content.Remove("view"); + } + + // assume the values of the outer view collections + this.Content = outerView.Content; + this.OnceTable = outerView.OnceTable; + } + + this.RenderView(context.Writer); + + if (isNestedView) + { + this.Content = priorContent; + this.OnceTable = priorOnce; + + // restore previous state of "view" content + if (priorContentView != null) + { + outerView.Content["view"] = priorContentView; + } + else if (outerView.Content.ContainsKey("view")) + { + outerView.Content.Remove("view"); + } + } + else + { + // proactively dispose named content. pools spoolwriter pages. avoids finalizers. + foreach (var content in this.Content.Values) + { + content.Close(); + } + } + + this.Content.Clear(); + + return Task.CompletedTask; + } +} diff --git a/src/Spark.AspNetCore.Mvc/SparkViewEngine.cs b/src/Spark.AspNetCore.Mvc/SparkViewEngine.cs new file mode 100644 index 00000000..dbed9edc --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkViewEngine.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Spark.AspNetCore.Mvc.Helpers; +using Spark.Descriptors; + +namespace Spark.AspNetCore.Mvc; + +public interface ISparkViewEngine : IViewEngine; + +public class SparkViewEngine : ISparkViewEngine +{ + private readonly Spark.ISparkViewEngine ViewEngine; + private readonly IDescriptorBuilder DescriptorBuilder; + private readonly ICacheService CacheService; + + private readonly Dictionary _cache; + + public SparkViewEngine(Spark.ISparkViewEngine viewEngine, IDescriptorBuilder descriptorBuilder, ICacheService cacheService) + { + ViewEngine = viewEngine; + DescriptorBuilder = descriptorBuilder; + CacheService = cacheService; + + _cache = new Dictionary(); + } + + public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) + { + return FindViewInternal(context, viewName, null, true, false); + } + + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage) + { + var applicationRelativePath = PathHelper.GetAbsolutePath(executingFilePath, viewPath); + + return ViewEngineResult.NotFound(applicationRelativePath, Enumerable.Empty()); + } + + private ViewEngineResult FindViewInternal(ActionContext context, string viewName, string masterName, bool findDefaultMaster, bool useCache) + { + var searchedLocations = new List(); + + var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; + if (controllerActionDescriptor == null) + { + return ViewEngineResult.NotFound(viewName, searchedLocations); + } + + var targetNamespace = controllerActionDescriptor.ControllerTypeInfo.Namespace; + var controllerName = controllerActionDescriptor.ControllerName; + + var descriptorParams = new BuildDescriptorParams( + targetNamespace, + controllerName, + viewName, + masterName, + findDefaultMaster, + DescriptorBuilder.GetExtraParameters(new SparkRouteData(context.RouteData.Values))); + + ISparkViewEntry entry; + if (useCache) + { + if (TryGetCacheValue(descriptorParams, out entry) && entry.IsCurrent()) + { + return BuildResult(viewName, entry); + } + + return ViewEngineResult.NotFound(viewName, searchedLocations); + } + + var descriptor = DescriptorBuilder.BuildDescriptor( + descriptorParams, + searchedLocations); + + if (descriptor == null) + { + return ViewEngineResult.NotFound(viewName, searchedLocations); + } + + entry = ViewEngine.CreateEntry(descriptor); + + SetCacheValue(descriptorParams, entry); + + return BuildResult(viewName, entry); + } + + private bool TryGetCacheValue(BuildDescriptorParams descriptorParams, out ISparkViewEntry entry) + { + lock (_cache) return _cache.TryGetValue(descriptorParams, out entry); + } + + private void SetCacheValue(BuildDescriptorParams descriptorParams, ISparkViewEntry entry) + { + lock (_cache) _cache[descriptorParams] = entry; + } + + private ViewEngineResult BuildResult(string viewName, ISparkViewEntry entry) + { + var view = (IView) entry.CreateInstance(); + + if (view is SparkView sparkView) + { + sparkView.Path = entry.Descriptor.Templates[0]; + sparkView.CacheService = this.CacheService; + } + + return ViewEngineResult.Found(viewName, view); + } +} \ No newline at end of file diff --git a/src/Spark.AspNetCore.Mvc/SparkViewOfT.cs b/src/Spark.AspNetCore.Mvc/SparkViewOfT.cs new file mode 100644 index 00000000..c4109bf2 --- /dev/null +++ b/src/Spark.AspNetCore.Mvc/SparkViewOfT.cs @@ -0,0 +1,54 @@ +using System.Configuration; +using System.Reflection; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; + +namespace Spark.AspNetCore.Mvc; + +public abstract class SparkView : SparkView +{ + private ViewDataDictionary? viewData; + private IHtmlHelper? htmlHelper; + + public TModel? Model => this.ViewData.Model; + + public new ViewDataDictionary ViewData => this.viewData ??= new ViewDataDictionary(this.ViewContext.ViewData); + + public new IHtmlHelper Html + { + get + { + if (this.htmlHelper == null) + { + if (this.ViewContext.ViewData.TryGetValue("Html", out object value)) + { + var plainHelper = (IHtmlHelper)value; + + // TODO: Improve this as using reflection will fail if Microsoft's HtmlHelper implementation fails + var htmlGenerator = (IHtmlGenerator) typeof(HtmlHelper).GetField("_htmlGenerator", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(value); + var compositeViewEngine = (ICompositeViewEngine) typeof(HtmlHelper).GetField("_viewEngine", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(value); + var viewBufferScope = (IViewBufferScope) typeof(HtmlHelper).GetField("_bufferScope", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(value); + var htmlEncoder = (HtmlEncoder) typeof(HtmlEncoder).GetField("_htmlEncoder", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(value); + + this.htmlHelper = new HtmlHelper( + htmlGenerator, + compositeViewEngine, + plainHelper.MetadataProvider, + viewBufferScope, + htmlEncoder, + plainHelper.UrlEncoder, + new ModelExpressionProvider(plainHelper.MetadataProvider)); + } + else + { + throw new ConfigurationErrorsException($"Html not set in ViewData, is {nameof(Filters.HtmlHelperResultFilter)} configured?"); + } + } + + return this.htmlHelper; + } + } +} \ No newline at end of file diff --git a/src/Spark.Python.Tests/PythonViewCompilerTests.cs b/src/Spark.Python.Tests/PythonViewCompilerTests.cs index 7e90de21..644e580a 100644 --- a/src/Spark.Python.Tests/PythonViewCompilerTests.cs +++ b/src/Spark.Python.Tests/PythonViewCompilerTests.cs @@ -17,7 +17,6 @@ using System.IO; using NUnit.Framework; using Spark.Compiler; -using Spark.Compiler.Roslyn; using Spark.Parser; using Spark.Python.Compiler; using Spark.Tests.Models; @@ -35,10 +34,7 @@ public class PythonViewCompilerTests [SetUp] public void Init() { - _settings = new SparkSettings - { - BaseClassTypeName = typeof(StubSparkView).FullName - }; + _settings = new SparkSettings(); _compiler = new PythonViewCompiler(_settings); @@ -90,10 +86,9 @@ public void CodeInheritsBaseClass() { var chunks = Chunks(); - this._settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); - Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); + Assert.That(_compiler.SourceCode.Contains(": Spark.Tests.Stubs.StubSparkView")); } [Test] @@ -101,10 +96,9 @@ public void CodeInheritsBaseClassWithTModel() { var chunks = Chunks(new ViewDataModelChunk { TModel = "ThisIsTheModelClass" }); - this._settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); - Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); + Assert.That(_compiler.SourceCode.Contains(": Spark.Tests.Stubs.StubSparkView")); } [Test] diff --git a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs index 366260aa..b63b24d3 100644 --- a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs +++ b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs @@ -33,9 +33,8 @@ public class RubyViewCompilerTests [SetUp] public void Init() { - _settings = new SparkSettings + _settings = new SparkSettings { - BaseClassTypeName = typeof(StubSparkView).FullName, Debug = true }; _compiler = new RubyViewCompiler(this._settings); @@ -88,10 +87,9 @@ public void CodeInheritsBaseClass() { var chunks = Chunks(); - _settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); - Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); + Assert.That(_compiler.SourceCode.Contains(": Spark.Tests.Stubs.StubSparkView")); } [Test] @@ -99,10 +97,9 @@ public void CodeInheritsBaseClassWithTModel() { var chunks = Chunks(new ViewDataModelChunk { TModel = "ThisIsTheModelClass" }); - _settings.BaseClassTypeName = "ThisIsTheBaseClass"; _compiler.GenerateSourceCode(chunks, chunks); - Assert.That(_compiler.SourceCode.Contains(": ThisIsTheBaseClass")); + Assert.That(_compiler.SourceCode.Contains(": Spark.Tests.Stubs.StubSparkView")); } [Test] diff --git a/src/Spark.Tests/SparkViewExtensions.cs b/src/Spark.Tests/SparkViewExtensions.cs index 194a1a04..dfab0c8c 100644 --- a/src/Spark.Tests/SparkViewExtensions.cs +++ b/src/Spark.Tests/SparkViewExtensions.cs @@ -21,9 +21,10 @@ public static class SparkViewExtensions public static string RenderView(this ISparkView view) { var writer = new StringWriter(); + view.RenderView(writer); + return writer.ToString(); } - } } diff --git a/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/html-encode-function-h.spark b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/html-encode-function-h.spark deleted file mode 100644 index ba084f1e..00000000 --- a/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/html-encode-function-h.spark +++ /dev/null @@ -1,5 +0,0 @@ -

${H(SomeHtml().ToString())}

- - -

<>

-
diff --git a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs similarity index 89% rename from src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs rename to src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs index 375a55b7..3b7f66a1 100644 --- a/src/Spark.Web.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs @@ -19,6 +19,7 @@ using Spark.Tests; using Spark.Tests.Models; using Spark.Tests.Stubs; +using Spark.Web.Mvc; namespace Spark.Compiler { @@ -41,7 +42,7 @@ private static void DoCompileView(ViewCompiler compiler, IList chunks) [Test] public void MakeAndCompile() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendLiteralChunk { Text = "hello world" } }); @@ -56,7 +57,7 @@ public void MakeAndCompile() [Test] public void UnsafeLiteralCharacters() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var text = "hello\t\r\n\"world"; var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendLiteralChunk { Text = text } }); @@ -72,7 +73,7 @@ public void UnsafeLiteralCharacters() [Test] public void SimpleOutput() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); @@ -84,7 +85,7 @@ public void SimpleOutput() [Test] public void LocalVariableDecl() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -100,7 +101,7 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -127,7 +128,7 @@ public void ForEachLoop() [Test] public void GlobalVariables() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -147,7 +148,7 @@ public void GlobalVariables() [Test] public void TargetNamespace() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings) { Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } @@ -163,7 +164,7 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); Assert.That( @@ -180,7 +181,7 @@ public void ProvideFullException() [Test] public void IfTrueCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -204,7 +205,7 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -228,7 +229,7 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -254,7 +255,7 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -278,7 +279,7 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -302,11 +303,11 @@ public void UnlessFalseCondition() [Test] public void LenientSilentNullDoesNotCauseWarningCS0168() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Lenient - }; + }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly.FullName); + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] @@ -323,11 +324,11 @@ public void LenientSilentNullDoesNotCauseWarningCS0168() [Test] public void LenientOutputNullDoesNotCauseWarningCS0168() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Lenient - }; + }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly.FullName); + var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] { @@ -343,9 +344,8 @@ public void LenientOutputNullDoesNotCauseWarningCS0168() [Test] public void StrictNullUsesException() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new CSharpViewCompiler(this.batchCompiler, settings); @@ -366,9 +366,8 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new CSharpViewCompiler(this.batchCompiler, settings); @@ -388,9 +387,8 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new CSharpViewCompiler(this.batchCompiler, settings); @@ -413,9 +411,8 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new CSharpViewCompiler(this.batchCompiler, settings); @@ -436,7 +433,7 @@ public void PageBaseTypeWorksWithGenericParametersIncluded() [Test] public void Markdown() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.SparkViewBase" }; + var settings = new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var innerChunks = new Chunk[] { new SendLiteralChunk { Text = "*test*" } }; diff --git a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs b/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs similarity index 90% rename from src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs rename to src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs index 58133a4b..d44bdb72 100644 --- a/src/Spark.Web.Tests/Compiler/VisualBasicViewCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs @@ -20,6 +20,7 @@ using Spark.Tests; using Spark.Tests.Models; using Spark.Tests.Stubs; +using Spark.Web.Mvc; namespace Spark.Compiler { @@ -56,7 +57,7 @@ public void MakeAndCompile() [Test] public void StronglyTypedBase() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] @@ -90,7 +91,7 @@ private VisualBasicViewCompiler CreateCompiler(ISparkSettings settings = null) { if (settings == null) { - settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" } + settings = new SparkSettings() .AddAssembly("Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") .AddNamespace("Microsoft.VisualBasic"); } @@ -101,7 +102,7 @@ private VisualBasicViewCompiler CreateCompiler(ISparkSettings settings = null) [Test] public void SimpleOutput() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "3 + 4" } }); var instance = compiler.CreateInstance(); @@ -137,7 +138,11 @@ public void SilentNullBehavior() [Test] public void RethrowNullBehavior() { - var settings = new SparkSettings { NullBehaviour = NullBehaviour.Strict }; + var settings = new SparkSettings + { + NullBehaviour = NullBehaviour.Strict + }; + var compiler = CreateCompiler(settings); DoCompileView(compiler, new[] { new SendExpressionChunk { Code = "CType(Nothing, String).Length" } }); @@ -149,7 +154,7 @@ public void RethrowNullBehavior() [Test] public void LocalVariableDecl() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] @@ -166,7 +171,7 @@ public void LocalVariableDecl() [Test] public void ForEachLoop() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -193,7 +198,7 @@ public void ForEachLoop() [Test] public void ForEachAutoVariables() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -224,7 +229,7 @@ public void ForEachAutoVariables() [Test] public void GlobalVariables() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); DoCompileView(compiler, new Chunk[] { @@ -245,7 +250,7 @@ public void GlobalVariables() [Platform(Exclude = "Mono", Reason = "Problems with Mono-2.10+/Linux and the VB compiler prevent this from running.")] public void TargetNamespace() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings) { Descriptor = new SparkViewDescriptor { TargetNamespace = "Testing.Target.Namespace" } @@ -260,7 +265,7 @@ public void TargetNamespace() [Test] public void ProvideFullException() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); Assert.That(() => @@ -274,7 +279,7 @@ public void ProvideFullException() [Test] public void IfTrueCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -295,7 +300,7 @@ public void IfTrueCondition() [Test] public void IfFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -316,7 +321,7 @@ public void IfFalseCondition() [Test] public void IfElseFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -339,7 +344,7 @@ public void IfElseFalseCondition() [Test] public void UnlessTrueCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -360,7 +365,7 @@ public void UnlessTrueCondition() [Test] public void UnlessFalseCondition() { - var settings = new SparkSettings { BaseClassTypeName = "Spark.AbstractSparkView" }; + var settings = new SparkSettings(); var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); var trueChunks = new Chunk[] { new SendLiteralChunk { Text = "wastrue" } }; @@ -381,9 +386,8 @@ public void UnlessFalseCondition() [Test] public void StrictNullUsesException() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); @@ -402,9 +406,8 @@ public void StrictNullUsesException() [Test] public void PageBaseTypeOverridesBaseClass() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); @@ -422,9 +425,8 @@ public void PageBaseTypeOverridesBaseClass() [Test] public void PageBaseTypeWorksWithOptionalModel() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); @@ -443,9 +445,8 @@ public void PageBaseTypeWorksWithOptionalModel() [Test] public void PageBaseTypeWorksWithGenericParametersIncluded() { - var settings = new SparkSettings + var settings = new SparkSettings { - BaseClassTypeName = "Spark.Tests.Stubs.StubSparkView", NullBehaviour = NullBehaviour.Strict }; var compiler = new VisualBasicViewCompiler(this.batchCompiler, settings); diff --git a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs index 0d9d899f..916042b7 100644 --- a/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs +++ b/src/Spark.Web.Mvc.Tests/DescriptorBuildingTester.cs @@ -34,7 +34,7 @@ namespace Spark.Web.Mvc.Tests [TestFixture] public class DescriptorBuildingTester { - private SparkViewFactory _factory; + private SparkWebPrecompiler _precompiler; private InMemoryViewFolder _viewFolder; private RouteData _routeData; private ControllerContext _controllerContext; @@ -64,9 +64,10 @@ public void Init() services => { services.AddSingleton(this._viewFolder); + services.AddSingleton(); }); - _factory = sp.GetService(); + this._precompiler = sp.GetService(); var httpContext = MockRepository.GenerateStub(); _routeData = new RouteData(); @@ -96,7 +97,7 @@ public void NormalViewAndNoDefaultLayout() _viewFolder.Add(@"Home\Index.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -111,7 +112,7 @@ public void NormalViewAndNoDefaultLayoutWithShade() _viewFolder.Add(@"Home\Index.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -127,7 +128,7 @@ public void NormalViewAndDefaultLayoutPresent() _viewFolder.Add(@"Layouts\Application.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -144,7 +145,7 @@ public void NormalViewAndDefaultLayoutPresentWithShade() _viewFolder.Add(@"Layouts\Application.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -162,7 +163,7 @@ public void NormalViewAndControllerLayoutOverrides() _viewFolder.Add(@"Layouts\Home.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -180,7 +181,7 @@ public void NormalViewAndControllerLayoutOverridesWithShade() _viewFolder.Add(@"Layouts\Home.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -199,7 +200,7 @@ public void NormalViewAndNamedMaster() _viewFolder.Add(@"Layouts\Site.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); AssertDescriptorTemplates( result, @@ -218,7 +219,7 @@ public void NormalViewAndNamedMasterWithShade() _viewFolder.Add(@"Layouts\Site.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); AssertDescriptorTemplates( result, @@ -238,7 +239,7 @@ public void PartialViewIgnoresDefaultLayouts() _viewFolder.Add(@"Shared\Home.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); AssertDescriptorTemplates( result, @@ -257,7 +258,7 @@ public void PartialViewIgnoresDefaultLayoutsWithShade() _viewFolder.Add(@"Shared\Home.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); AssertDescriptorTemplates( result, @@ -274,7 +275,7 @@ public void RouteAreaPresentDefaultsToNormalLocation() _viewFolder.Add(@"Layouts\Application.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -292,7 +293,7 @@ public void RouteAreaPresentDefaultsToNormalLocationWithShade() _viewFolder.Add(@"Layouts\Application.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -311,7 +312,7 @@ public void AreaFolderMayContainControllerFolder() _viewFolder.Add(@"Admin\Home\Index.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -330,7 +331,7 @@ public void AreaFolderMayContainControllerFolderWithShade() _viewFolder.Add(@"Admin\Home\Index.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -348,7 +349,7 @@ public void AreaRouteValueAlsoRecognizedForBackCompatWithEarlierAssumptions() { _viewFolder.Add(@"Admin\Home\Index.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -366,7 +367,7 @@ public void AreaRouteValueAlsoRecognizedForBackCompatWithEarlierAssumptionsWithS _viewFolder.Add(@"Admin\Home\Index.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -386,7 +387,7 @@ public void AreaFolderMayContainLayoutsFolder() _viewFolder.Add(@"Admin\Layouts\Application.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -406,7 +407,7 @@ public void AreaFolderMayContainLayoutsFolderWithShade() _viewFolder.Add(@"Admin\Layouts\Application.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -427,7 +428,7 @@ public void AreaContainsNamedLayout() _viewFolder.Add(@"Admin\Layouts\Site.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); AssertDescriptorTemplates( result, @@ -448,7 +449,7 @@ public void AreaContainsNamedLayoutWithShade() _viewFolder.Add(@"Admin\Layouts\Site.shade", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", "Site", true, searchedLocations); AssertDescriptorTemplates( result, @@ -471,7 +472,7 @@ public void PartialViewFromAreaIgnoresLayout() _viewFolder.Add(@"Admin\Layouts\Site.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); AssertDescriptorTemplates( result, @@ -491,7 +492,7 @@ public void UseMasterCreatesTemplateChain() _viewFolder.Add(@"Layouts\Home.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, true, searchedLocations); AssertDescriptorTemplates( result, @@ -514,7 +515,7 @@ public void NamedMasterOverridesViewMaster() _viewFolder.Add(@"Layouts\Home.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", "Red", true, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", "Red", true, searchedLocations); AssertDescriptorTemplates( result, @@ -536,7 +537,7 @@ public void PartialViewIgnoresUseMasterAndDefault() _viewFolder.Add(@"Layouts\Home.spark", ""); var searchedLocations = new List(); - var result = _factory.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); + var result = this._precompiler.CreateDescriptor(_controllerContext, "Index", null, false, searchedLocations); AssertDescriptorTemplates( result, @@ -682,12 +683,15 @@ public void CustomDescriptorBuildersCantUseDescriptorFilters() { var settings = new SparkSettings(); + var descriptorBuilders = MockRepository.GenerateStub(); + var factory = new SparkViewFactory( settings, null, - MockRepository.GenerateStub(), + descriptorBuilders, + null, null, - null); + new SparkWebPrecompiler(null, descriptorBuilders)); Assert.That( () => diff --git a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj index 7988669e..2b91d1c8 100644 --- a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj +++ b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj @@ -18,6 +18,7 @@ + @@ -77,9 +78,6 @@ Always - - Always - Always diff --git a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs index 6c9472a9..6981f5e1 100644 --- a/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkBatchCompilerTester.cs @@ -27,7 +27,7 @@ namespace Spark.Web.Mvc.Tests { [TestFixture] - public class SparkBatchCompilerTester + public class SparkPrecompilerTester { #region Setup/Teardown @@ -35,13 +35,15 @@ public static IServiceProvider SetupServiceProvider(Action se { var services = new ServiceCollection(); - services.AddSpark(new SparkSettings()); + services.AddSpark(new SparkSettings().SetBaseClassTypeName(typeof(SparkView).FullName)); if (serviceOverrides != null) { serviceOverrides.Invoke(services); } + services.AddSingleton(); + return services.BuildServiceProvider(); } @@ -52,14 +54,15 @@ public void Init() s => { s.AddSingleton(new FileSystemViewFolder("AspNetMvc.Tests.Views")); + s.AddSingleton(); }); - _factory = serviceProvider.GetService(); + this._precompiler = serviceProvider.GetService(); } #endregion - private SparkViewFactory _factory; + private SparkPrecompiler _precompiler; [Test] public void CompileBatchDescriptor() @@ -70,7 +73,7 @@ public void CompileBatchDescriptor() .For().Layout("layout").Include("Index").Include("List.spark") .For().Layout("ajax").Include("_Widget"); - var assembly = _factory.Precompile(batch); + var assembly = this._precompiler.Precompile(batch); Assert.IsNotNull(assembly); Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); @@ -134,7 +137,7 @@ public void DefaultMatchingRules() batch.For(); - var descriptors = _factory.CreateDescriptors(batch); + var descriptors = this._precompiler.CreateDescriptors(batch); Assert.AreEqual(2, descriptors.Count); Assert.AreEqual(1, descriptors[0].Templates.Count); @@ -150,7 +153,7 @@ public void ExcludeRules() batch.For().Include("*").Include("_*").Exclude("In*"); - var descriptors = _factory.CreateDescriptors(batch); + var descriptors = this._precompiler.CreateDescriptors(batch); Assert.AreEqual(2, descriptors.Count); Assert.AreEqual(1, descriptors[0].Templates.Count); @@ -171,7 +174,7 @@ public void MultipleLayoutFiles() .Include("Index") .Include("List.spark"); - var assembly = _factory.Precompile(batch); + var assembly = this._precompiler.Precompile(batch); Assert.IsNotNull(assembly); Assert.AreEqual(4, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); @@ -186,7 +189,7 @@ public void WildcardIncludeRules() .For().Layout("layout").Include("*") .For().Layout("ajax").Include("_*"); - var descriptors = _factory.CreateDescriptors(batch); + var descriptors = this._precompiler.CreateDescriptors(batch); Assert.AreEqual(3, descriptors.Count); Assert.That( descriptors.Any( @@ -198,7 +201,7 @@ public void WildcardIncludeRules() descriptors.Any( d => d.Templates.Contains(string.Format("Stub{0}_Widget.spark", Path.DirectorySeparatorChar)) && d.Templates.Contains(string.Format("Shared{0}ajax.spark", Path.DirectorySeparatorChar)))); - var assembly = _factory.Precompile(batch); + var assembly = this._precompiler.Precompile(batch); Assert.IsNotNull(assembly); Assert.AreEqual(3, assembly.GetTypes().Count(x => x.BaseType == typeof(SparkView))); @@ -220,12 +223,12 @@ public void FileWithoutSparkExtensionAreIgnored() s.AddSingleton(viewFolder); }); - _factory = sp.GetService(); + this._precompiler = sp.GetService(); var batch = new SparkBatchDescriptor(); batch.For(); - var descriptors = _factory.CreateDescriptors(batch); + var descriptors = this._precompiler.CreateDescriptors(batch); Assert.AreEqual(1, descriptors.Count); Assert.AreEqual(2, descriptors[0].Templates.Count); diff --git a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs index 3f17d096..efdc880d 100644 --- a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs @@ -241,17 +241,6 @@ public void GlobalSetTest() Assert.That(content.Contains("

7==7

")); } - [Test] - public void HtmlEncodeFunctionH() - { - FindViewAndRender("html-encode-function-h"); - //mocks.VerifyAll(); - - var content = output.ToString().Replace(" ", "").Replace("\r", "").Replace("\n", ""); - - Assert.AreEqual("

<p>&lt;&gt;</p>

", content); - } - [Test] public void HtmlHelperWorksOnItsOwn() { diff --git a/src/Spark.Web.Tests/VisualBasicViewTester.cs b/src/Spark.Web.Mvc.Tests/VisualBasicViewTester.cs similarity index 98% rename from src/Spark.Web.Tests/VisualBasicViewTester.cs rename to src/Spark.Web.Mvc.Tests/VisualBasicViewTester.cs index 1094652c..e9f45635 100644 --- a/src/Spark.Web.Tests/VisualBasicViewTester.cs +++ b/src/Spark.Web.Mvc.Tests/VisualBasicViewTester.cs @@ -1,7 +1,6 @@ using System.Text; using NUnit.Framework; using Spark.FileSystem; -using Spark.Tests.Models; using Spark.Tests.Stubs; using System.IO; using Microsoft.Extensions.DependencyInjection; @@ -257,8 +256,8 @@ public void ViewDataModelChunk() ${Comment.Text} "); - var comment = new Comment { Text = "hello world" }; - var contents = Render("index", new StubViewData { Model = comment }); + var comment = new Spark.Tests.Models.Comment { Text = "hello world" }; + var contents = Render("index", new StubViewData { Model = comment }); Assert.That(contents.Trim(), Is.EqualTo("hello world")); } diff --git a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs index 2bbc450c..935fab40 100644 --- a/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs +++ b/src/Spark.Web.Mvc/Extensions/ServiceCollectionExtensions.cs @@ -59,7 +59,8 @@ public static IServiceCollection AddSpark(this IServiceCollection services, ISpa .AddSingleton(settings.CreateDefaultViewFolder()) .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); services.AddSingleton(c => null); diff --git a/src/Spark.Web.Mvc/SparkView.cs b/src/Spark.Web.Mvc/SparkView.cs index b075dedc..7a676d0f 100644 --- a/src/Spark.Web.Mvc/SparkView.cs +++ b/src/Spark.Web.Mvc/SparkView.cs @@ -182,20 +182,36 @@ public void Render(ViewContext viewContext, TextWriter writer) Content.Clear(); } - - public string H(object value) + + public override void OutputValue(object value, bool automaticEncoding) { - if (value is MvcHtmlString htmlString) + // Always encode when automatic encoding enabled or HtmlString (includes MvcHtmlString) + if (automaticEncoding || value is HtmlString) { - return H(htmlString); + OutputEncodedValue(value); + } + else + { + Output.Write(value); } - - return Html.Encode(value); } - public string H(MvcHtmlString value) + public void OutputEncodedValue(object value) { - return value == null ? null : value.ToString(); + if (value is string stringValue) + { + var encoded = Html.Encode(stringValue); + + Output.Write(encoded); + } + else if (value is MvcHtmlString mvcHtmlString) + { + Output.Write(mvcHtmlString.ToString()); + } + else + { + Output.Write(value.ToString()); + } } public MvcHtmlString HTML(object value) @@ -212,57 +228,4 @@ public string Eval(string expression, string format) return ViewData.Eval(expression, format); } } - - public abstract class SparkView : SparkView - { - private ViewDataDictionary _viewData; - private HtmlHelper _htmlHelper; - private AjaxHelper _ajaxHelper; - - public TModel Model => ViewData.Model; - - public new ViewDataDictionary ViewData - { - get - { - if (_viewData == null) - SetViewData(new ViewDataDictionary()); - return _viewData; - } - set { SetViewData(value); } - } - - public new HtmlHelper Html - { - get => _htmlHelper; - set - { - _htmlHelper = value; - base.Html = value; - } - } - - public new AjaxHelper Ajax - { - get => _ajaxHelper; - set - { - _ajaxHelper = value; - base.Ajax = value; - } - } - - protected override void SetViewData(ViewDataDictionary viewData) - { - _viewData = new ViewDataDictionary(viewData); - base.SetViewData(_viewData); - } - - protected override void CreateHelpers() - { - Html = new HtmlHelper(ViewContext, this); - Url = new UrlHelper(ViewContext.RequestContext); - Ajax = new AjaxHelper(ViewContext, this); - } - } -} +} \ No newline at end of file diff --git a/src/Spark.Web.Mvc/SparkViewFactory.cs b/src/Spark.Web.Mvc/SparkViewFactory.cs index 71da58bf..0496e561 100644 --- a/src/Spark.Web.Mvc/SparkViewFactory.cs +++ b/src/Spark.Web.Mvc/SparkViewFactory.cs @@ -14,17 +14,49 @@ // using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Web.Mvc; -using Spark.Compiler; using Spark.Descriptors; using Spark.FileSystem; using Spark.Web.Mvc.Wrappers; namespace Spark.Web.Mvc { + public class SparkWebPrecompiler : SparkPrecompiler + { + private readonly IDescriptorBuilder DescriptorBuilder; + + public SparkWebPrecompiler(ISparkViewEngine engine, IDescriptorBuilder descriptorBuilder) : base(engine, descriptorBuilder) + { + this.DescriptorBuilder = descriptorBuilder; + } + + [Obsolete("Is this used apart from in the unit tests?")] + public SparkViewDescriptor CreateDescriptor( + ControllerContext controllerContext, + string viewName, + string masterName, + bool findDefaultMaster, + ICollection searchedLocations) + { + var targetNamespace = controllerContext.Controller.GetType().Namespace; + + var controllerName = controllerContext.RouteData.GetRequiredString("controller"); + + var routeDataWrapper = new SparkRouteData(controllerContext.RouteData.Values); + + return DescriptorBuilder.BuildDescriptor( + new BuildDescriptorParams( + targetNamespace, + controllerName, + viewName, + masterName, + findDefaultMaster, + DescriptorBuilder.GetExtraParameters(routeDataWrapper)), + searchedLocations); + } + } + public class SparkViewFactory : IViewEngine, IViewFolderContainer { public ISparkSettings Settings { get; protected set; } @@ -32,59 +64,63 @@ public class SparkViewFactory : IViewEngine, IViewFolderContainer public IDescriptorBuilder DescriptorBuilder { get; protected set; } public IResourcePathManager ResourcePathManager { get; protected set; } public ICacheService CacheService { get; protected set; } + public ISparkPrecompiler Precompiler { get; protected set; } - private readonly Dictionary _cache; - private readonly ViewEngineResult _cacheMissResult; + private readonly Dictionary cache; + private readonly ViewEngineResult cacheMissResult; - public SparkViewFactory(ISparkSettings settings, + public SparkViewFactory( + ISparkSettings settings, ISparkViewEngine viewEngine, IDescriptorBuilder descriptorBuilder, IResourcePathManager resourcePathManager, - ICacheService cacheService) + ICacheService cacheService, + ISparkPrecompiler precompiler) { - Settings = settings; + this.Settings = settings; - if (string.IsNullOrEmpty(settings.BaseClassTypeName)) + if (string.IsNullOrEmpty(settings.BaseClassTypeName) && settings is SparkSettings sparkSettings) { - settings.BaseClassTypeName = typeof(SparkView).FullName; + sparkSettings.SetBaseClass(); } - Engine = viewEngine; - DescriptorBuilder = descriptorBuilder; - ResourcePathManager = resourcePathManager; - CacheService = cacheService; + this.Engine = viewEngine; + this.DescriptorBuilder = descriptorBuilder; + this.ResourcePathManager = resourcePathManager; + this.CacheService = cacheService; + this.Precompiler = precompiler; - _cache = new Dictionary(); - _cacheMissResult = new ViewEngineResult(Array.Empty()); + this.cache = new Dictionary(); + this.cacheMissResult = new ViewEngineResult(Array.Empty()); } - public IViewActivatorFactory ViewActivatorFactory => Engine.ViewActivatorFactory; + public IViewActivatorFactory ViewActivatorFactory => this.Engine.ViewActivatorFactory; public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName) { - return FindViewInternal(controllerContext, viewName, masterName, true, false); + return this.FindViewInternal(controllerContext, viewName, masterName, true, false); } public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { - return FindViewInternal(controllerContext, viewName, masterName, true, useCache); + return this.FindViewInternal(controllerContext, viewName, masterName, true, useCache); } public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName) { - return FindViewInternal(controllerContext, partialViewName, null /*masterName*/, false, false); + return this.FindViewInternal(controllerContext, partialViewName, null /*masterName*/, false, false); } public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { - return FindViewInternal(controllerContext, partialViewName, null /*masterName*/, false, useCache); + return this.FindViewInternal(controllerContext, partialViewName, null /*masterName*/, false, useCache); } public virtual void ReleaseView(ControllerContext controllerContext, IView view) { if (view is ISparkView sparkView) { - Engine.ReleaseInstance(sparkView); + this.Engine.ReleaseInstance(sparkView); } } @@ -103,20 +139,20 @@ private ViewEngineResult FindViewInternal(ControllerContext controllerContext, s viewName, masterName, findDefaultMaster, - DescriptorBuilder.GetExtraParameters(routeDataWrapper)); + this.DescriptorBuilder.GetExtraParameters(routeDataWrapper)); ISparkViewEntry entry; if (useCache) { - if (TryGetCacheValue(descriptorParams, out entry) && entry.IsCurrent()) + if (this.TryGetCacheValue(descriptorParams, out entry) && entry.IsCurrent()) { - return BuildResult(entry); + return this.BuildResult(entry); } - return _cacheMissResult; + return this.cacheMissResult; } - var descriptor = DescriptorBuilder.BuildDescriptor( + var descriptor = this.DescriptorBuilder.BuildDescriptor( descriptorParams, searchedLocations); @@ -125,21 +161,21 @@ private ViewEngineResult FindViewInternal(ControllerContext controllerContext, s return new ViewEngineResult(searchedLocations); } - entry = Engine.CreateEntry(descriptor); - - SetCacheValue(descriptorParams, entry); + entry = this.Engine.CreateEntry(descriptor); + + this.SetCacheValue(descriptorParams, entry); - return BuildResult(entry); + return this.BuildResult(entry); } private bool TryGetCacheValue(BuildDescriptorParams descriptorParams, out ISparkViewEntry entry) { - lock (_cache) return _cache.TryGetValue(descriptorParams, out entry); + lock (this.cache) return this.cache.TryGetValue(descriptorParams, out entry); } private void SetCacheValue(BuildDescriptorParams descriptorParams, ISparkViewEntry entry) { - lock (_cache) _cache[descriptorParams] = entry; + lock (this.cache) this.cache[descriptorParams] = entry; } private ViewEngineResult BuildResult(ISparkViewEntry entry) @@ -148,13 +184,14 @@ private ViewEngineResult BuildResult(ISparkViewEntry entry) if (view is SparkView sparkView) { - sparkView.ResourcePathManager = ResourcePathManager; - sparkView.CacheService = CacheService; + sparkView.ResourcePathManager = this.ResourcePathManager; + sparkView.CacheService = this.CacheService; } return new ViewEngineResult(view, this); } + [Obsolete("Does not seem to be used?")] public SparkViewDescriptor CreateDescriptor( ControllerContext controllerContext, string viewName, @@ -178,174 +215,10 @@ public SparkViewDescriptor CreateDescriptor( DescriptorBuilder.GetExtraParameters(routeDataWrapper)), searchedLocations); } - - public SparkViewDescriptor CreateDescriptor( - string targetNamespace, - string controllerName, - string viewName, - string masterName, - bool findDefaultMaster) - { - var searchedLocations = new List(); - - var descriptor = DescriptorBuilder.BuildDescriptor( - new BuildDescriptorParams( - targetNamespace /*areaName*/, - controllerName, - viewName, - masterName, - findDefaultMaster, - null), - searchedLocations); - - if (descriptor == null) - { - throw new CompilerException($"Unable to find templates at {string.Join(", ", searchedLocations.ToArray())}"); - } - - return descriptor; - } - + public Assembly Precompile(SparkBatchDescriptor batch) { - return Engine.BatchCompilation(batch.OutputAssembly, CreateDescriptors(batch)); - } - - public List CreateDescriptors(SparkBatchDescriptor batch) - { - var descriptors = new List(); - - foreach (var entry in batch.Entries) - { - descriptors.AddRange(CreateDescriptors(entry)); - } - - return descriptors; - } - - public IList CreateDescriptors(SparkBatchEntry entry) - { - var descriptors = new List(); - - string controllerName = null; - - if (entry.ControllerType.ContainsGenericParameters) - { - // generic controller have a backtick suffix in their (name e.g. "SomeController`2") - var indexOfBacktick = entry.ControllerType.Name.IndexOf("Controller`", StringComparison.Ordinal); - if (indexOfBacktick > -1) - { - // removing it otherwise locating the view templates will fail - controllerName = entry.ControllerType.Name.Substring(0, indexOfBacktick); - } - } - else - { - controllerName = RemoveSuffix(entry.ControllerType.Name, "Controller"); - } - - var viewNames = new List(); - - var includeViews = entry.IncludeViews; - - if (includeViews.Count == 0) - { - includeViews = new[] { "*" }; - } - - foreach (var include in includeViews) - { - if (include.EndsWith("*")) - { - foreach (var fileName in Engine.ViewFolder.ListViews(controllerName)) - { - if (!string.Equals(Path.GetExtension(fileName), ".spark", StringComparison.InvariantCultureIgnoreCase)) - { - continue; - } - - var potentialMatch = Path.GetFileNameWithoutExtension(fileName); - if (!TestMatch(potentialMatch, include)) - { - continue; - } - - var isExcluded = false; - foreach (var exclude in entry.ExcludeViews) - { - if (!TestMatch(potentialMatch, RemoveSuffix(exclude, ".spark"))) - { - continue; - } - - isExcluded = true; - break; - } - if (!isExcluded) - { - viewNames.Add(potentialMatch); - } - } - } - else - { - // explicitly included views don't test for exclusion - viewNames.Add(RemoveSuffix(include, ".spark")); - } - } - - foreach (var viewName in viewNames) - { - if (entry.LayoutNames.Count == 0) - { - descriptors.Add( - CreateDescriptor( - entry.ControllerType.Namespace, - controllerName, - viewName, - null /*masterName*/, - true)); - } - else - { - foreach (var masterName in entry.LayoutNames) - { - descriptors.Add( - CreateDescriptor( - entry.ControllerType.Namespace, - controllerName, - viewName, - string.Join(" ", masterName.ToArray()), - false)); - } - } - } - - return descriptors; - } - - private static bool TestMatch(string potentialMatch, string pattern) - { - if (!pattern.EndsWith("*")) - { - return string.Equals(potentialMatch, pattern, StringComparison.InvariantCultureIgnoreCase); - } - - // raw wildcard matches anything that's not a partial - if (pattern == "*") - { - return !potentialMatch.StartsWith("_"); - } - - // otherwise the only thing that's supported is "starts with" - return potentialMatch.StartsWith(pattern.Substring(0, pattern.Length - 1), StringComparison.InvariantCultureIgnoreCase); - } - - private static string RemoveSuffix(string value, string suffix) - { - return value.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase) - ? value.Substring(0, value.Length - suffix.Length) - : value; + return this.Precompiler.Precompile(batch); } #region IViewEngine Members diff --git a/src/Spark.Web.Mvc/SparkViewOfT.cs b/src/Spark.Web.Mvc/SparkViewOfT.cs new file mode 100644 index 00000000..63b266d6 --- /dev/null +++ b/src/Spark.Web.Mvc/SparkViewOfT.cs @@ -0,0 +1,57 @@ +using System.Web.Mvc; + +namespace Spark.Web.Mvc +{ + public abstract class SparkView : SparkView + { + private ViewDataDictionary _viewData; + private HtmlHelper _htmlHelper; + private AjaxHelper _ajaxHelper; + + public TModel Model => ViewData.Model; + + public new ViewDataDictionary ViewData + { + get + { + if (_viewData == null) + SetViewData(new ViewDataDictionary()); + return _viewData; + } + set { SetViewData(value); } + } + + public new HtmlHelper Html + { + get => _htmlHelper; + set + { + _htmlHelper = value; + base.Html = value; + } + } + + public new AjaxHelper Ajax + { + get => _ajaxHelper; + set + { + _ajaxHelper = value; + base.Ajax = value; + } + } + + protected override void SetViewData(ViewDataDictionary viewData) + { + _viewData = new ViewDataDictionary(viewData); + base.SetViewData(_viewData); + } + + protected override void CreateHelpers() + { + Html = new HtmlHelper(ViewContext, this); + Url = new UrlHelper(ViewContext.RequestContext); + Ajax = new AjaxHelper(ViewContext, this); + } + } +} diff --git a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs index d70274b8..0b7eb390 100644 --- a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs +++ b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs @@ -18,8 +18,6 @@ using Spark.Tests.Stubs; using System.IO; using Spark.Bindings; -using Spark.Compiler.Roslyn; -using Spark.Parser; using Spark.Parser.Syntax; using Spark.Tests; diff --git a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs index c853cee8..969e0a84 100644 --- a/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs +++ b/src/Spark.Web.Tests/Parser/AutomaticEncodingTester.cs @@ -135,15 +135,6 @@ public void AutomaticEncodingTrueEncodesDollarSyntax() Assert.AreEqual("<span>hello</span> world", content); } - [Test] - public void AutomaticEncodingTrueOmitsRedundantEncoding() - { - this.Init(true); - this._viewFolder.Add(Path.Combine("home", "index.spark"), "${H('hello')} !{H('world')}"); - var content = this.RenderView(new SparkViewDescriptor().AddTemplate(Path.Combine("home", "index.spark"))); - Assert.AreEqual("<span>hello</span> <span>world</span>", content); - } - [Test] public void HashSyntaxForStatementsByDefault() { diff --git a/src/Spark.Web.Tests/Precompiled/View1.cs b/src/Spark.Web.Tests/Precompiled/View1.cs index b4a2b96b..abd7b93c 100644 --- a/src/Spark.Web.Tests/Precompiled/View1.cs +++ b/src/Spark.Web.Tests/Precompiled/View1.cs @@ -27,10 +27,7 @@ public override void Render() Output.Write("

Hello world

"); } - public override Guid GeneratedViewId - { - get { return new Guid("11111111123412341234123456123456"); } - } + public override Guid GeneratedViewId => new Guid("11111111123412341234123456123456"); public override bool TryGetViewData(string name, out object value) { diff --git a/src/Spark.Web.Tests/Precompiled/View2.cs b/src/Spark.Web.Tests/Precompiled/View2.cs index 4f648b1e..7adb64a4 100644 --- a/src/Spark.Web.Tests/Precompiled/View2.cs +++ b/src/Spark.Web.Tests/Precompiled/View2.cs @@ -13,11 +13,7 @@ // limitations under the License. // using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using Spark.Tests.Stubs; namespace Spark.Tests.Precompiled { @@ -31,10 +27,7 @@ public void RenderView(TextWriter writer) writer.Write("

Hello world

"); } - public Guid GeneratedViewId - { - get { return new Guid("22222222123412341234123456123456"); } - } + public Guid GeneratedViewId => new Guid("22222222123412341234123456123456"); public bool TryGetViewData(string name, out object value) { diff --git a/src/Spark.Web.Tests/Precompiled/View3.cs b/src/Spark.Web.Tests/Precompiled/View3.cs index f5f1b9cb..0b2e08b3 100644 --- a/src/Spark.Web.Tests/Precompiled/View3.cs +++ b/src/Spark.Web.Tests/Precompiled/View3.cs @@ -13,11 +13,7 @@ // limitations under the License. // using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using Spark.Tests.Stubs; namespace Spark.Tests.Precompiled { @@ -31,9 +27,6 @@ public void RenderView(TextWriter writer) writer.Write("

Hello world

"); } - public Guid GeneratedViewId - { - get { return new Guid("33333333123412341234123456123456"); } - } + public Guid GeneratedViewId => new Guid("33333333123412341234123456123456"); } } diff --git a/src/Spark.Web.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark b/src/Spark.Web.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark index 226eadbb..cceb6c51 100644 --- a/src/Spark.Web.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark +++ b/src/Spark.Web.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark @@ -1,5 +1,4 @@

${#alpha}

-

${H(#alpha)}

# Output.Write(#beta);

4${#nosuchthing}2

diff --git a/src/Spark.Web.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Tests/SparkViewFactoryTester.cs index 50bcb07b..0c23669d 100644 --- a/src/Spark.Web.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Tests/SparkViewFactoryTester.cs @@ -1158,7 +1158,6 @@ public void LateBoundEvalResolvesViewData() content, Contains.InOrder( "

hi

", - "

<strong>hi</strong>

", "yadda", "

42

")); } diff --git a/src/Spark.Web.Tests/Stubs/StubSparkView.cs b/src/Spark.Web.Tests/Stubs/StubSparkView.cs index cc729d98..d9ae1657 100644 --- a/src/Spark.Web.Tests/Stubs/StubSparkView.cs +++ b/src/Spark.Web.Tests/Stubs/StubSparkView.cs @@ -41,9 +41,16 @@ public override bool TryGetViewData(string name, out object value) return ViewData.TryGetValue(name, out value); } - public string H(object content) + public override void OutputValue(object value, bool automaticEncoding) { - return HttpUtility.HtmlEncode(Convert.ToString(content)); + if (automaticEncoding) + { + Output.Write(HttpUtility.HtmlEncode(Convert.ToString(value))); + } + else + { + Output.Write(Convert.ToString(value)); + } } public object HTML(object value) diff --git a/src/Spark.Web.Tests/Stubs/StubSparkView2.cs b/src/Spark.Web.Tests/Stubs/StubSparkView2.cs index d0c1e644..a16de67a 100644 --- a/src/Spark.Web.Tests/Stubs/StubSparkView2.cs +++ b/src/Spark.Web.Tests/Stubs/StubSparkView2.cs @@ -27,11 +27,6 @@ public override bool TryGetViewData(string name, out object value) return ViewData.TryGetValue(name, out value); } - public string H(object content) - { - return HttpUtility.HtmlEncode(Convert.ToString(content)); - } - public object Eval(string expression) { return ViewData.Eval(expression); diff --git a/src/Spark.Web/Configuration/SparkSectionHandler.cs b/src/Spark.Web/Configuration/SparkSectionHandler.cs index d610daf7..495223e5 100644 --- a/src/Spark.Web/Configuration/SparkSectionHandler.cs +++ b/src/Spark.Web/Configuration/SparkSectionHandler.cs @@ -105,11 +105,7 @@ public SparkSectionHandler AddNamespace(string ns) bool ISparkSettings.ParseSectionTagAsSegment => Pages.ParseSectionTagAsSegment; - string ISparkSettings.BaseClassTypeName - { - get => Pages.BaseClassTypeName; - set => Pages.BaseClassTypeName = value; - } + string ISparkSettings.BaseClassTypeName => Pages.BaseClassTypeName; LanguageType ISparkSettings.DefaultLanguage => Compilation.DefaultLanguage; diff --git a/src/Spark.sln b/src/Spark.sln index d0444fab..e894d034 100644 --- a/src/Spark.sln +++ b/src/Spark.sln @@ -4,7 +4,7 @@ VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Spark Core", "Spark Core", "{F606A062-1BD5-40E2-8E8A-BCC4F374BFBF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet Mvc", "AspNet Mvc", "{F4E1F2B2-2BA6-4EFC-8082-ADBC14ECC179}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet", "AspNet", "{F4E1F2B2-2BA6-4EFC-8082-ADBC14ECC179}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Castle MonoRail", "Castle MonoRail", "{CE293579-4889-4D6D-A1A0-BBBE8CBB9343}" EndProject @@ -60,6 +60,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.Web", "Spark.Web\Spar EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.Web.Tests", "Spark.Web.Tests\Spark.Web.Tests.csproj", "{58EE303C-D806-4917-9CA8-C2B10594A1AA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{50B212B0-9638-4392-9B99-38278C1AC9BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.AspNetCore.Mvc", "Spark.AspNetCore.Mvc\Spark.AspNetCore.Mvc.csproj", "{483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +154,10 @@ Global {58EE303C-D806-4917-9CA8-C2B10594A1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU {58EE303C-D806-4917-9CA8-C2B10594A1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {58EE303C-D806-4917-9CA8-C2B10594A1AA}.Release|Any CPU.Build.0 = Release|Any CPU + {483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -176,6 +184,7 @@ Global {0FEC123E-DFD8-4DB0-9FC7-6C2EF2897FAD} = {CE293579-4889-4D6D-A1A0-BBBE8CBB9343} {4A68431B-ADD3-4F99-9121-2DF0C5C93BC3} = {F606A062-1BD5-40E2-8E8A-BCC4F374BFBF} {58EE303C-D806-4917-9CA8-C2B10594A1AA} = {F606A062-1BD5-40E2-8E8A-BCC4F374BFBF} + {483CEEC7-C14C-4D6F-BB46-3DCA553E4CAB} = {50B212B0-9638-4392-9B99-38278C1AC9BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B9FB0A1C-BC73-4CE6-95F0-9D33AE572BED} diff --git a/src/Spark.sln.DotSettings b/src/Spark.sln.DotSettings index acf0b017..e921accd 100644 --- a/src/Spark.sln.DotSettings +++ b/src/Spark.sln.DotSettings @@ -1,3 +1,5 @@  DO_NOT_SHOW - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> \ No newline at end of file + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + True \ No newline at end of file diff --git a/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs b/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs index 207a1641..c97dccae 100644 --- a/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs +++ b/src/Spark/Compiler/CSharp/ChunkVisitors/GeneratedCodeVisitor.cs @@ -15,7 +15,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Spark.Compiler.ChunkVisitors; using Spark.Parser.Code; @@ -129,17 +128,15 @@ protected override void Visit(SendLiteralChunk chunk) protected override void Visit(SendExpressionChunk chunk) { var automaticallyEncode = chunk.AutomaticallyEncode; - if (chunk.Code.ToString().StartsWith("H(")) - automaticallyEncode = false; - + _source .WriteLine("try") .WriteLine("{"); CodeIndent(chunk) - .Write("Output.Write(") - .Write(automaticallyEncode ? "H(" : "") + .Write("OutputValue(") .WriteCode(chunk.Code) - .Write(automaticallyEncode ? ")" : "") + .Write(", ") + .Write(automaticallyEncode ? "true" : "false") .WriteLine(");"); CodeDefault(); _source @@ -175,7 +172,6 @@ static string EscapeStringContents(string text) protected override void Visit(MacroChunk chunk) { - } protected override void Visit(CodeStatementChunk chunk) diff --git a/src/Spark/Compiler/ChunkVisitors/ChunkVisitor.cs b/src/Spark/Compiler/ChunkVisitors/ChunkVisitor.cs index f3d1d867..9936a6d4 100644 --- a/src/Spark/Compiler/ChunkVisitors/ChunkVisitor.cs +++ b/src/Spark/Compiler/ChunkVisitors/ChunkVisitor.cs @@ -105,7 +105,6 @@ protected override void Visit(ConditionalChunk chunk) protected override void Visit(ViewDataModelChunk chunk) { - } protected override void Visit(ExtensionChunk chunk) diff --git a/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs b/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs index 3a00578a..e3e8c2c3 100644 --- a/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs +++ b/src/Spark/Compiler/ChunkVisitors/DetectCodeExpressionVisitor.cs @@ -36,24 +36,32 @@ public DetectCodeExpressionVisitor(RenderPartialChunk currentPartial) public Entry Add(string expression) { - var entry = new Entry {Expression = expression}; + var entry = new Entry { Expression = expression }; + _entries.Add(entry); + return entry; } void Examine(Snippets code) { if (Snippets.IsNullOrEmpty(code)) + { return; + } var codeString = code.ToString(); foreach(var entry in _entries) { if (entry.Detected) + { continue; + } if (codeString.Contains(entry.Expression)) + { entry.Detected = true; + } } } @@ -118,7 +126,10 @@ protected override void Visit(ExtensionChunk chunk) protected override void Visit(ConditionalChunk chunk) { if (!Snippets.IsNullOrEmpty(chunk.Condition)) - Examine(chunk.Condition); + { + this.Examine(chunk.Condition); + } + Accept(chunk.Body); } diff --git a/src/Spark/Compiler/VisualBasic/ChunkVisitors/GeneratedCodeVisitor.cs b/src/Spark/Compiler/VisualBasic/ChunkVisitors/GeneratedCodeVisitor.cs index a809c40a..e60f2a6f 100644 --- a/src/Spark/Compiler/VisualBasic/ChunkVisitors/GeneratedCodeVisitor.cs +++ b/src/Spark/Compiler/VisualBasic/ChunkVisitors/GeneratedCodeVisitor.cs @@ -128,15 +128,13 @@ protected override void Visit(SendLiteralChunk chunk) protected override void Visit(SendExpressionChunk chunk) { var automaticallyEncode = chunk.AutomaticallyEncode; - if (chunk.Code.ToString().StartsWith("H(")) - automaticallyEncode = false; - + _source.WriteLine("Try").AddIndent(); CodeIndent(chunk) - .Write("Output.Write(") - .Write(automaticallyEncode ? "H(" : "") + .Write("OutputValue(") .WriteCode(chunk.Code) - .Write(automaticallyEncode ? ")" : "") + .Write(", ") + .Write(automaticallyEncode ? "true" : "false") .WriteLine(")") .RemoveIndent(); CodeDefault(); diff --git a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs index 43c41c7e..f1ff4a19 100644 --- a/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs +++ b/src/Spark/Compiler/VisualBasic/VisualBasicViewCompiler.cs @@ -48,6 +48,7 @@ public override void GenerateSourceCode(IEnumerable> viewTemplates, source.WriteLine("Option Infer On"); usingGenerator.UsingNamespace("Microsoft.VisualBasic"); + foreach (var ns in settings.UseNamespaces ?? Array.Empty()) { usingGenerator.UsingNamespace(ns); diff --git a/src/Spark/ISparkPrecompiler.cs b/src/Spark/ISparkPrecompiler.cs new file mode 100644 index 00000000..17218a38 --- /dev/null +++ b/src/Spark/ISparkPrecompiler.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Spark; + +public interface ISparkPrecompiler +{ + Assembly Precompile(SparkBatchDescriptor batch); +} \ No newline at end of file diff --git a/src/Spark/ISparkSettings.cs b/src/Spark/ISparkSettings.cs index 864dd533..eb553314 100644 --- a/src/Spark/ISparkSettings.cs +++ b/src/Spark/ISparkSettings.cs @@ -52,7 +52,7 @@ public interface ISparkSettings : IParserSettings bool Debug { get; } NullBehaviour NullBehaviour { get; } string Prefix { get; } - string BaseClassTypeName { get; set; } + string BaseClassTypeName { get; } LanguageType DefaultLanguage { get; } IEnumerable UseNamespaces { get; } diff --git a/src/Spark/SparkPrecompiler.cs b/src/Spark/SparkPrecompiler.cs new file mode 100644 index 00000000..956f3d11 --- /dev/null +++ b/src/Spark/SparkPrecompiler.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Spark.Compiler; +using Spark.Descriptors; + +namespace Spark +{ + public class SparkPrecompiler(Spark.ISparkViewEngine engine, IDescriptorBuilder descriptorBuilder) : ISparkPrecompiler + { + public Assembly Precompile(SparkBatchDescriptor batch) + { + var descriptors = CreateDescriptors(batch); + + return engine.BatchCompilation(batch.OutputAssembly, descriptors); + } + + private static bool TestMatch(string potentialMatch, string pattern) + { + if (!pattern.EndsWith("*")) + { + return string.Equals(potentialMatch, pattern, StringComparison.InvariantCultureIgnoreCase); + } + + // raw wildcard matches anything that's not a partial + if (pattern == "*") + { + return !potentialMatch.StartsWith("_"); + } + + // otherwise the only thing that's supported is "starts with" + return potentialMatch.StartsWith(pattern.Substring(0, pattern.Length - 1), StringComparison.InvariantCultureIgnoreCase); + } + + private static string RemoveSuffix(string value, string suffix) + { + return value.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase) + ? value.Substring(0, value.Length - suffix.Length) + : value; + } + + public List CreateDescriptors(SparkBatchDescriptor batch) + { + var descriptors = new List(); + + foreach (var entry in batch.Entries) + { + descriptors.AddRange(CreateDescriptors(entry)); + } + + return descriptors; + } + + public IList CreateDescriptors(SparkBatchEntry entry) + { + var descriptors = new List(); + + string controllerName = null; + + if (entry.ControllerType.ContainsGenericParameters) + { + // generic controller have a backtick suffix in their (name e.g. "SomeController`2") + var indexOfBacktick = entry.ControllerType.Name.IndexOf("Controller`", StringComparison.Ordinal); + if (indexOfBacktick > -1) + { + // removing it otherwise locating the view templates will fail + controllerName = entry.ControllerType.Name.Substring(0, indexOfBacktick); + } + } + else + { + controllerName = RemoveSuffix(entry.ControllerType.Name, "Controller"); + } + + var viewNames = new List(); + + var includeViews = entry.IncludeViews; + + if (includeViews.Count == 0) + { + includeViews = ["*"]; + } + + foreach (var include in includeViews) + { + if (include.EndsWith("*")) + { + foreach (var fileName in engine.ViewFolder.ListViews(controllerName)) + { + if (!string.Equals(Path.GetExtension(fileName), ".spark", StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + var potentialMatch = Path.GetFileNameWithoutExtension(fileName); + if (!TestMatch(potentialMatch, include)) + { + continue; + } + + var isExcluded = false; + foreach (var exclude in entry.ExcludeViews) + { + if (!TestMatch(potentialMatch, RemoveSuffix(exclude, ".spark"))) + { + continue; + } + + isExcluded = true; + break; + } + if (!isExcluded) + { + viewNames.Add(potentialMatch); + } + } + } + else + { + // explicitly included views don't test for exclusion + viewNames.Add(RemoveSuffix(include, ".spark")); + } + } + + foreach (var viewName in viewNames) + { + if (entry.LayoutNames.Count == 0) + { + descriptors.Add( + CreateDescriptor( + entry.ControllerType.Namespace, + controllerName, + viewName, + null /*masterName*/, + true)); + } + else + { + foreach (var masterName in entry.LayoutNames) + { + descriptors.Add( + CreateDescriptor( + entry.ControllerType.Namespace, + controllerName, + viewName, + string.Join(" ", masterName.ToArray()), + false)); + } + } + } + + return descriptors; + } + + public SparkViewDescriptor CreateDescriptor( + string targetNamespace, + string controllerName, + string viewName, + string masterName, + bool findDefaultMaster) + { + var searchedLocations = new List(); + + var descriptor = descriptorBuilder.BuildDescriptor( + new BuildDescriptorParams( + targetNamespace /*areaName*/, + controllerName, + viewName, + masterName, + findDefaultMaster, + null), + searchedLocations); + + if (descriptor == null) + { + throw new CompilerException($"Unable to find templates at {string.Join(", ", searchedLocations.ToArray())}"); + } + + return descriptor; + } + } +} diff --git a/src/Spark/SparkSettings.cs b/src/Spark/SparkSettings.cs index 02ee65dd..8a57c7dd 100644 --- a/src/Spark/SparkSettings.cs +++ b/src/Spark/SparkSettings.cs @@ -19,6 +19,18 @@ namespace Spark { + /// + /// Generic version of the spark setting to set the BaseClassTypeName more conveniently. + /// + /// + public class SparkSettings : SparkSettings, ISparkSettings + where TBaseClassOfView : SparkViewBase + { + private string baseClassTypeName; + + public override string BaseClassTypeName => this.baseClassTypeName ??= typeof(TBaseClassOfView).FullName; + } + public class SparkSettings : ISparkSettings { /// @@ -58,7 +70,7 @@ public SparkSettings(string rootPath) public bool AutomaticEncoding { get; set; } public string StatementMarker { get; set; } public string Prefix { get; set; } - public string BaseClassTypeName { get; set; } + public virtual string BaseClassTypeName { get; private set; } public LanguageType DefaultLanguage { get; set; } public bool ParseSectionTagAsSegment { get; set; } @@ -108,29 +120,39 @@ public SparkSettings SetNullBehaviour(NullBehaviour nullBehaviour) } /// - /// Sets the fully full name of the type each spark page should inherit from. + /// Sets the type each spark view will inherit from. /// - /// The full name of the type. + /// The full name of the type. /// - public SparkSettings SetBaseClassTypeName(string typeName) + public SparkSettings SetBaseClassTypeName(string typeFullName) { - BaseClassTypeName = typeName; + this.BaseClassTypeName = typeFullName; return this; } /// - /// Sets the type each spark page should inherit from. + /// Sets the type each spark view will inherit from. /// /// The type. /// public SparkSettings SetBaseClassTypeName(Type type) { - BaseClassTypeName = type.FullName; + this.BaseClassTypeName = type.FullName; return this; } + /// + /// Sets the type each spark view will inherit from. + /// + /// + /// + public SparkSettings SetBaseClass() + { + return this.SetBaseClassTypeName(typeof(T)); + } + /// /// Specifies the the spark view should be compiled to. /// diff --git a/src/Spark/SparkViewBase.cs b/src/Spark/SparkViewBase.cs index 159b4534..1e89a34c 100644 --- a/src/Spark/SparkViewBase.cs +++ b/src/Spark/SparkViewBase.cs @@ -60,11 +60,32 @@ public virtual SparkViewContext SparkViewContext } set { _sparkViewContext = value; } } - - public TextWriter Output { get { return SparkViewContext.Output; } set { SparkViewContext.Output = value; } } - public Dictionary Content { get { return SparkViewContext.Content; } set { SparkViewContext.Content = value; } } - public Dictionary Globals { get { return SparkViewContext.Globals; } set { SparkViewContext.Globals = value; } } - public Dictionary OnceTable { get { return SparkViewContext.OnceTable; } set { SparkViewContext.OnceTable = value; } } + + public abstract void OutputValue(object value, bool automaticEncoding); + + public TextWriter Output + { + get => SparkViewContext.Output; + set => SparkViewContext.Output = value; + } + + public Dictionary Content + { + get => SparkViewContext.Content; + set => SparkViewContext.Content = value; + } + + public Dictionary Globals + { + get => SparkViewContext.Globals; + set => SparkViewContext.Globals = value; + } + + public Dictionary OnceTable + { + get => SparkViewContext.OnceTable; + set => SparkViewContext.OnceTable = value; + } public IDisposable OutputScope(string name) { diff --git a/src/Spark/SparkViewDecorator.cs b/src/Spark/SparkViewDecorator.cs index 1f2fb1a1..6a7f6a83 100644 --- a/src/Spark/SparkViewDecorator.cs +++ b/src/Spark/SparkViewDecorator.cs @@ -1,4 +1,6 @@ -using Spark.Spool; +using System; +using System.Web; +using Spark.Spool; namespace Spark { @@ -26,6 +28,12 @@ public override SparkViewContext SparkViewContext } } + public override void OutputValue(object value, bool automaticEncoding) + { + // Ignore encoding for test purposes + Output.Write(Convert.ToString(value)); + } + public override void RenderView(System.IO.TextWriter writer) { if (_decorated != null) From 3d8701afc8465afcf8372f1497377d62082719fb Mon Sep 17 00:00:00 2001 From: bounav Date: Wed, 24 Apr 2024 13:58:33 +0100 Subject: [PATCH 14/14] BatchCompiler now loads the assemblies defined in spark settings before compilation - Can now use name, full name or absolute path to an assembly --- .../SparkViewFactory.cs | 2 +- src/Spark.JsTests/Generate.ashx.cs | 2 +- .../PythonViewCompilerTests.cs | 2 +- .../Compiler/PythonViewCompiler.cs | 2 +- src/Spark.Ruby.Tests/RubyViewCompilerTests.cs | 2 +- src/Spark.Ruby/Compiler/RubyViewCompiler.cs | 2 +- .../HtmlHelperExtensionsTester.cs | 2 +- .../Compiler/CSharpViewCompilerTester.cs | 6 +++--- .../Compiler/VisualBasicViewCompilerTester.cs | 2 +- .../Compiler/SourceMappingTester.cs | 2 +- src/Spark/Compiler/BatchCompiler.cs | 20 ++++++++++++++----- .../ChunkVisitors/UsingNamespaceVisitor.cs | 7 ++++++- src/Spark/Compiler/Roslyn/CSharpLink.cs | 2 +- src/Spark/ISparkSettings.cs | 4 ++++ src/Spark/SparkSettings.cs | 8 ++++++-- src/Xpark/Program.cs | 2 +- 16 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs index 2dd13be8..9565d90d 100644 --- a/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs +++ b/src/Castle.MonoRail.Views.Spark/SparkViewFactory.cs @@ -68,7 +68,7 @@ public ISparkViewEngine Engine var viewFolder = new ViewSourceLoaderWrapper(this); - var batchCompiler = new RoslynBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(new SparkSettings()); this._engine = new SparkViewEngine( diff --git a/src/Spark.JsTests/Generate.ashx.cs b/src/Spark.JsTests/Generate.ashx.cs index 586f0736..5e9b5ec8 100644 --- a/src/Spark.JsTests/Generate.ashx.cs +++ b/src/Spark.JsTests/Generate.ashx.cs @@ -40,7 +40,7 @@ public void ProcessRequest(HttpContext context) var partialProvider = new DefaultPartialProvider(); - var batchCompiler = new RoslynBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(new SparkSettings()); var engine = new SparkViewEngine( settings, diff --git a/src/Spark.Python.Tests/PythonViewCompilerTests.cs b/src/Spark.Python.Tests/PythonViewCompilerTests.cs index 644e580a..259d984f 100644 --- a/src/Spark.Python.Tests/PythonViewCompilerTests.cs +++ b/src/Spark.Python.Tests/PythonViewCompilerTests.cs @@ -38,7 +38,7 @@ public void Init() _compiler = new PythonViewCompiler(_settings); - _languageFactory = new PythonLanguageFactory(new RoslynBatchCompiler(), _settings); + _languageFactory = new PythonLanguageFactory(new RoslynBatchCompiler(this._settings), _settings); // Load up assemblies IronPython.Hosting.Python.CreateEngine(); diff --git a/src/Spark.Python/Compiler/PythonViewCompiler.cs b/src/Spark.Python/Compiler/PythonViewCompiler.cs index e4936fbe..f182a61f 100644 --- a/src/Spark.Python/Compiler/PythonViewCompiler.cs +++ b/src/Spark.Python/Compiler/PythonViewCompiler.cs @@ -29,7 +29,7 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new RoslynBatchCompiler(); + var compiler = new RoslynBatchCompiler(new SparkSettings()); var assembly = compiler.Compile(settings.Debug, "csharp", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs index b63b24d3..ad38a50a 100644 --- a/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs +++ b/src/Spark.Ruby.Tests/RubyViewCompilerTests.cs @@ -38,7 +38,7 @@ public void Init() Debug = true }; _compiler = new RubyViewCompiler(this._settings); - _languageFactory = new RubyLanguageFactory(new RoslynBatchCompiler(), this._settings); + _languageFactory = new RubyLanguageFactory(new RoslynBatchCompiler(this._settings), this._settings); //load assemblies global::IronRuby.Ruby.CreateEngine(); diff --git a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs index 7ebee1d2..06f8501e 100644 --- a/src/Spark.Ruby/Compiler/RubyViewCompiler.cs +++ b/src/Spark.Ruby/Compiler/RubyViewCompiler.cs @@ -30,7 +30,7 @@ public override void CompileView(IEnumerable> viewTemplates, IEnume { GenerateSourceCode(viewTemplates, allResources); - var compiler = new RoslynBatchCompiler(); + var compiler = new RoslynBatchCompiler(new SparkSettings()); var assembly = compiler.Compile(settings.Debug, "csharp", null, new[] { SourceCode }, settings.ExcludeAssemblies); CompiledType = assembly.GetType(ViewClassFullName); } diff --git a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs index 60701dee..971badaa 100644 --- a/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs +++ b/src/Spark.Web.Mvc.Ruby.Tests/HtmlHelperExtensionsTester.cs @@ -43,7 +43,7 @@ public void BuildingScriptHeader() { var settings = new SparkSettings(); - var batchCompiler = new RoslynBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(settings); var languageFactory = new RubyLanguageFactoryWithExtensions(batchCompiler, settings); diff --git a/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs b/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs index 3b7f66a1..bd38c81a 100644 --- a/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/Compiler/CSharpViewCompilerTester.cs @@ -31,7 +31,7 @@ public class CSharpViewCompilerTester [SetUp] public void Init() { - this.batchCompiler = new RoslynBatchCompiler(); + this.batchCompiler = new RoslynBatchCompiler(new SparkSettings()); } private static void DoCompileView(ViewCompiler compiler, IList chunks) @@ -306,7 +306,7 @@ public void LenientSilentNullDoesNotCauseWarningCS0168() var settings = new SparkSettings { NullBehaviour = NullBehaviour.Lenient - }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly.FullName); + }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); @@ -327,7 +327,7 @@ public void LenientOutputNullDoesNotCauseWarningCS0168() var settings = new SparkSettings { NullBehaviour = NullBehaviour.Lenient - }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly.FullName); + }.AddAssembly(typeof(Spark.Tests.Models.Comment).Assembly); var compiler = new CSharpViewCompiler(this.batchCompiler, settings); var chunks = new Chunk[] diff --git a/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs b/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs index d44bdb72..e111632d 100644 --- a/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs +++ b/src/Spark.Web.Mvc.Tests/Compiler/VisualBasicViewCompilerTester.cs @@ -33,7 +33,7 @@ public class VisualBasicViewCompilerTester public void Init() { this.batchCompiler = - new RoslynBatchCompiler(); + new RoslynBatchCompiler(new SparkSettings()); } private static void DoCompileView(ViewCompiler compiler, IList chunks) diff --git a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs index 0b7eb390..ff439361 100644 --- a/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs +++ b/src/Spark.Web.Tests/Compiler/SourceMappingTester.cs @@ -40,7 +40,7 @@ public void Init() _viewFolder = new InMemoryViewFolder(); - var batchCompiler = new RoslynBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(new SparkSettings()); _engine = new SparkViewEngine( settings, diff --git a/src/Spark/Compiler/BatchCompiler.cs b/src/Spark/Compiler/BatchCompiler.cs index 0cef55a4..b004cc74 100644 --- a/src/Spark/Compiler/BatchCompiler.cs +++ b/src/Spark/Compiler/BatchCompiler.cs @@ -185,16 +185,18 @@ public class CodeDomCompilerException(string message, CompilerResults results) : public class RoslynBatchCompiler : IBatchCompiler { private readonly IEnumerable links; + private readonly ISparkSettings settings; private readonly IList References; - public RoslynBatchCompiler() : this(new IRoslynCompilationLink[] { new CSharpLink(), new VisualBasicLink() }) + public RoslynBatchCompiler(ISparkSettings settings) : this(new IRoslynCompilationLink[] { new CSharpLink(), new VisualBasicLink() }, settings) { } - public RoslynBatchCompiler(IEnumerable links) + public RoslynBatchCompiler(IEnumerable links, ISparkSettings settings) { this.links = links; + this.settings = settings; this.References = new List(); } @@ -217,7 +219,7 @@ public bool AddAssembly(string assemblyDll) if (!File.Exists(file)) { - // check framework or dedicated runtime app folder + // Check framework or dedicated runtime app folder var path = Path.GetDirectoryName(typeof(object).Assembly.Location); file = Path.Combine(path, assemblyDll); if (!File.Exists(file)) @@ -337,8 +339,16 @@ public Assembly Compile(bool debug, string languageOrExtension, string outputAss #endif // TODO: Is this needed? - this.AddAssembly(typeof(System.Drawing.Color)); - + //this.AddAssembly(typeof(System.Drawing.Color)); + foreach(var assemblyLocation in settings.UseAssemblies) + { + // Assumes full path to assemblies + if (assemblyLocation.EndsWith(".dll")) + { + this.AddAssembly(assemblyLocation); + } + } + foreach (var currentAssembly in AppDomain.CurrentDomain.GetAssemblies()) { if (currentAssembly.IsDynamic()) diff --git a/src/Spark/Compiler/CSharp/ChunkVisitors/UsingNamespaceVisitor.cs b/src/Spark/Compiler/CSharp/ChunkVisitors/UsingNamespaceVisitor.cs index 0d6252c6..19300311 100644 --- a/src/Spark/Compiler/CSharp/ChunkVisitors/UsingNamespaceVisitor.cs +++ b/src/Spark/Compiler/CSharp/ChunkVisitors/UsingNamespaceVisitor.cs @@ -13,6 +13,7 @@ // limitations under the License. // using System.Collections.Generic; +using System.Globalization; using System.Reflection; using Spark.Compiler.ChunkVisitors; using Spark.Parser.Code; @@ -75,7 +76,11 @@ public void UsingAssembly(string assemblyString) if (_assemblyAdded.ContainsKey(assemblyString)) return; - var assembly = Assembly.Load(assemblyString); + // Works with absolute path to .dll or assembly name (including fullname) + var assembly = assemblyString.EndsWith(".dll", ignoreCase: true, CultureInfo.InvariantCulture) + ? Assembly.LoadFile(assemblyString) + : Assembly.Load(assemblyString); + _assemblyAdded.Add(assemblyString, assembly); } } diff --git a/src/Spark/Compiler/Roslyn/CSharpLink.cs b/src/Spark/Compiler/Roslyn/CSharpLink.cs index a86791d3..4b817a51 100644 --- a/src/Spark/Compiler/Roslyn/CSharpLink.cs +++ b/src/Spark/Compiler/Roslyn/CSharpLink.cs @@ -28,7 +28,7 @@ public static void ThrowIfCompilationNotSuccessful(EmitResult result) foreach (var diagnostic in failures) { - sb.Append(diagnostic.Id).Append(":").AppendLine(diagnostic.GetMessage()); + sb.AppendLine(diagnostic.ToString()); } throw new RoslynCompilerException(sb.ToString(), result); diff --git a/src/Spark/ISparkSettings.cs b/src/Spark/ISparkSettings.cs index eb553314..0f1392d3 100644 --- a/src/Spark/ISparkSettings.cs +++ b/src/Spark/ISparkSettings.cs @@ -56,6 +56,10 @@ public interface ISparkSettings : IParserSettings LanguageType DefaultLanguage { get; } IEnumerable UseNamespaces { get; } + + /// + /// A list of name, fullname or absolute paths to .dll for assemblies to load before compiling views. + /// IEnumerable UseAssemblies { get; } /// diff --git a/src/Spark/SparkSettings.cs b/src/Spark/SparkSettings.cs index 8a57c7dd..5dcc767a 100644 --- a/src/Spark/SparkSettings.cs +++ b/src/Spark/SparkSettings.cs @@ -80,6 +80,10 @@ public SparkSettings(string rootPath) public IEnumerable UseNamespaces => _useNamespaces; private readonly IList _useAssemblies; + + /// + /// A list of names, full names or absolute paths to .dll for assemblies to load before compiling views. + /// public IEnumerable UseAssemblies => _useAssemblies; private readonly IList _excludeAssemblies; @@ -166,7 +170,7 @@ public SparkSettings SetDefaultLanguage(LanguageType language) } /// - /// Adds the full name of an assembly for the view compiler to be aware of. + /// Adds the name, full name or absolute path of an assembly for the view compiler to be aware of. /// /// The full name of an assembly. /// @@ -182,7 +186,7 @@ public SparkSettings AddAssembly(string assembly) /// public SparkSettings AddAssembly(Assembly assembly) { - _useAssemblies.Add(assembly.FullName); + _useAssemblies.Add(assembly.Location); return this; } diff --git a/src/Xpark/Program.cs b/src/Xpark/Program.cs index 95e32504..5d341bf1 100644 --- a/src/Xpark/Program.cs +++ b/src/Xpark/Program.cs @@ -51,7 +51,7 @@ The Model in the template is an XDocument loaded from the source. var partialProvider = new DefaultPartialProvider(); - var batchCompiler = new RoslynBatchCompiler(); + var batchCompiler = new RoslynBatchCompiler(new SparkSettings()); var engine = new SparkViewEngine( settings,