diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 327bb239..42a0f88d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,23 +23,17 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Setup .NET 2.1 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 2.1.* - - name: Setup .NET 3.1 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.* - - name: Setup .NET 5.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.* - - name: Setup .NET 6.0 + + - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.* + dotnet-version: | + 3.1.x + 5.0.x + 6.0.x + - name: Test run: dotnet test --configuration Release + - name: Test - Compiled run: dotnet test --configuration Release /p:Compiled=true diff --git a/Fluid.MvcViewEngine/FluidMvcViewOptions.cs b/Fluid.MvcViewEngine/FluidMvcViewOptions.cs index 7c1fe152..2dde5478 100644 --- a/Fluid.MvcViewEngine/FluidMvcViewOptions.cs +++ b/Fluid.MvcViewEngine/FluidMvcViewOptions.cs @@ -1,9 +1,16 @@ using Fluid.ViewEngine; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Threading.Tasks; namespace Fluid.MvcViewEngine { public class FluidMvcViewOptions : FluidViewEngineOptions { - // Placeholder for future options specific to the MVC view engine + public delegate ValueTask RenderingMvcViewDelegate(string path, ViewContext viewContext, TemplateContext context); + + /// + /// Gets or sets the delegate to execute when a view is rendered. + /// + public new RenderingMvcViewDelegate RenderingViewAsync { get; set; } } } diff --git a/Fluid.MvcViewEngine/FluidRendering.cs b/Fluid.MvcViewEngine/FluidRendering.cs index f969a6c2..29e97341 100644 --- a/Fluid.MvcViewEngine/FluidRendering.cs +++ b/Fluid.MvcViewEngine/FluidRendering.cs @@ -1,6 +1,7 @@ using Fluid.ViewEngine; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Options; using System.IO; @@ -32,14 +33,19 @@ public FluidRendering( } private readonly IWebHostEnvironment _hostingEnvironment; - private readonly FluidViewEngineOptions _options; + private readonly FluidMvcViewOptions _options; - public async Task RenderAsync(TextWriter writer, string path, object model, ViewDataDictionary viewData, ModelStateDictionary modelState) + public async Task RenderAsync(TextWriter writer, string path, ViewContext viewContext) { var context = new TemplateContext(_options.TemplateOptions); - context.SetValue("ViewData", viewData); - context.SetValue("ModelState", modelState); - context.SetValue("Model", model); + context.SetValue("ViewData", viewContext.ViewData); + context.SetValue("ModelState", viewContext.ModelState); + context.SetValue("Model", viewContext.ViewData.Model); + + if (_options.RenderingViewAsync != null) + { + await _options.RenderingViewAsync.Invoke(path, viewContext, context); + } await _fluidViewRenderer.RenderViewAsync(writer, path, context); } diff --git a/Fluid.MvcViewEngine/FluidTagHelper.cs b/Fluid.MvcViewEngine/FluidTagHelper.cs index 0c89cd91..170a84b0 100644 --- a/Fluid.MvcViewEngine/FluidTagHelper.cs +++ b/Fluid.MvcViewEngine/FluidTagHelper.cs @@ -1,5 +1,7 @@ using System.IO; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Fluid.MvcViewEngine @@ -14,6 +16,10 @@ public FluidTagHelper(FluidRendering fluidRendering) _fluidRendering = fluidRendering; } + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + [HtmlAttributeName("model")] public object Model { get; set; } @@ -31,7 +37,7 @@ static async Task Awaited(TagHelperOutput o, StringWriter sw, Task t) using (var sw = new StringWriter()) { - var task = _fluidRendering.RenderAsync(sw, View, Model, null, null); + var task = _fluidRendering.RenderAsync(sw, View, ViewContext); if (task.IsCompletedSuccessfully) { diff --git a/Fluid.MvcViewEngine/FluidView.cs b/Fluid.MvcViewEngine/FluidView.cs index 7345016c..d4290ed8 100644 --- a/Fluid.MvcViewEngine/FluidView.cs +++ b/Fluid.MvcViewEngine/FluidView.cs @@ -25,7 +25,7 @@ public string Path public async Task RenderAsync(ViewContext context) { - await _fluidRendering.RenderAsync(context.Writer, Path, context.ViewData.Model, context.ViewData, context.ModelState); + await _fluidRendering.RenderAsync(context.Writer, Path, context); } } } diff --git a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs index aa141d4e..e47d4267 100644 --- a/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs +++ b/Fluid.Tests/MvcViewEngine/ViewEngineTests.cs @@ -225,10 +225,25 @@ public async Task ShouldNotIncludeViewStartInLayout() var sw = new StringWriter(); await _renderer.RenderViewAsync(sw, "Index.liquid", new TemplateContext()); await sw.FlushAsync(); - Assert.Equal("[Layout][ViewStart][View]", sw.ToString()); } + [Fact] + public async Task ShouldInvokeRenderingViewAsync() + { + _options.RenderingViewAsync = (view, context) => { context.SetValue("custom", "hello"); return default; }; + + _mockFileProvider.Add("Views/Index.liquid", "{{ custom }}"); + + var sw = new StringWriter(); + await _renderer.RenderViewAsync(sw, "Index.liquid", new TemplateContext()); + await sw.FlushAsync(); + + _options.RenderingViewAsync = null; + + Assert.Equal("hello", sw.ToString()); + } + [Fact] public async Task ShouldApplyViewStartLayoutsRecursively() { diff --git a/Fluid.ViewEngine/FluidViewEngineOptions.cs b/Fluid.ViewEngine/FluidViewEngineOptions.cs index 4cd62ac8..2a2122b4 100644 --- a/Fluid.ViewEngine/FluidViewEngineOptions.cs +++ b/Fluid.ViewEngine/FluidViewEngineOptions.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.FileProviders; +using System; using System.Collections.Generic; using System.Text.Encodings.Web; +using System.Threading.Tasks; namespace Fluid.ViewEngine { @@ -62,5 +64,15 @@ public class FluidViewEngineOptions /// Gets or sets whether files should be reloaded automatically when changed. Default is true; /// public bool TrackFileChanges { get; set; } = true; + + /// + /// Represents the method that will handle the view rendering event. + /// + public delegate ValueTask RenderingViewDelegate(string path, TemplateContext context); + + /// + /// Gets or sets the delegate to execute when a view is rendered. + /// + public RenderingViewDelegate RenderingViewAsync { get; set; } } } diff --git a/Fluid.ViewEngine/FluidViewRenderer.cs b/Fluid.ViewEngine/FluidViewRenderer.cs index 8123712a..e2e87c39 100644 --- a/Fluid.ViewEngine/FluidViewRenderer.cs +++ b/Fluid.ViewEngine/FluidViewRenderer.cs @@ -43,6 +43,11 @@ public virtual async Task RenderViewAsync(TextWriter writer, string relativePath var template = await GetFluidTemplateAsync(relativePath, _fluidViewEngineOptions.ViewsFileProvider, true); + if (_fluidViewEngineOptions.RenderingViewAsync != null) + { + await _fluidViewEngineOptions.RenderingViewAsync.Invoke(relativePath, context); + } + // The body is rendered and buffered before the Layout since it can contain fragments // that need to be rendered as part of the Layout. // Also the body or its _ViewStarts might contain a Layout tag. @@ -73,6 +78,11 @@ public virtual async Task RenderPartialAsync(TextWriter writer, string relativeP // Substitute View Path context.AmbientValues[Constants.ViewPathIndex] = relativePath; + if (_fluidViewEngineOptions.RenderingViewAsync != null) + { + await _fluidViewEngineOptions.RenderingViewAsync.Invoke(relativePath, context); + } + var template = await GetFluidTemplateAsync(relativePath, _fluidViewEngineOptions.PartialsFileProvider, false); await template.RenderAsync(writer, _fluidViewEngineOptions.TextEncoder, context);