Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compilation page: fix controls with IDotvvmRequestContext injection #1767

Merged
merged 2 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using DotVVM.Framework.Controls.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using DotVVM.Framework.Utils;
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Testing;

namespace DotVVM.Framework.Compilation
{
Expand Down Expand Up @@ -160,8 +162,9 @@ public bool BuildView(DotHtmlFileInfo file, bool forceRecompile, out DotHtmlFile

var pageBuilder = controlBuilderFactory.GetControlBuilder(file.VirtualPath);

using var scopedServiceProvider = dotvvmConfiguration.ServiceProvider.CreateScope(); // dependencies that are configured as scoped cannot be resolved from root service provider
var compiledControl = pageBuilder.builder.Value.BuildControl(controlBuilderFactory, scopedServiceProvider.ServiceProvider);
using var scopedServices = dotvvmConfiguration.ServiceProvider.CreateScope(); // dependencies that are configured as scoped cannot be resolved from root service provider
scopedServices.ServiceProvider.GetRequiredService<DotvvmRequestContextStorage>().Context = new ViewCompilationFakeRequestContext(scopedServices.ServiceProvider);
var compiledControl = pageBuilder.builder.Value.BuildControl(controlBuilderFactory, scopedServices.ServiceProvider);

if (pageBuilder.descriptor.MasterPage is { FileName: {} masterPagePath })
{
Expand Down
10 changes: 10 additions & 0 deletions src/Framework/Framework/Testing/TestDotvvmRequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using DotVVM.Framework.ResourceManagement;
using DotVVM.Framework.Routing;
using DotVVM.Framework.Runtime.Tracing;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;

namespace DotVVM.Framework.Testing
Expand Down Expand Up @@ -53,5 +54,14 @@ public IServiceProvider Services
}

public CustomResponsePropertiesManager CustomResponseProperties { get; } = new CustomResponsePropertiesManager();


public TestDotvvmRequestContext() { }
public TestDotvvmRequestContext(IServiceProvider services)
{
this.Services = services;
this.Configuration = services.GetService<DotvvmConfiguration>();
this.ResourceManager = services.GetService<ResourceManager>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace DotVVM.Framework.Testing
{
class ViewCompilationFakeRequestContext : TestDotvvmRequestContext
{
public ViewCompilationFakeRequestContext(IServiceProvider services): base(services)
{
}
}
}
140 changes: 140 additions & 0 deletions src/Tests/Runtime/ViewCompilationServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using System.Linq;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DotVVM.Framework.Tests.Runtime
{
[TestClass]
public class ViewCompilationServiceTests
{
static readonly FakeMarkupFileLoader fileLoader;
static readonly DotvvmConfiguration config;
static readonly DotvvmViewCompilationService service;
static ViewCompilationServiceTests()
{
fileLoader = new FakeMarkupFileLoader();
config = DotvvmTestHelper.CreateConfiguration(s => {
s.AddSingleton<IMarkupFileLoader>(fileLoader);
});
config.Markup.AddCodeControls("test", exampleControl: typeof(ControlWithContextInjection));

config.RouteTable.Add("WithContextInjection", "WithContextInjection", "WithContextInjection.dothtml", null);
fileLoader.MarkupFiles["WithContextInjection.dothtml"] = """
@viewModel object
<test:ControlWithContextInjection />
""";

config.RouteTable.Add("WithUnresolvableDependency", "WithUnresolvableDependency", "WithUnresolvableDependency.dothtml", null);
fileLoader.MarkupFiles["WithUnresolvableDependency.dothtml"] = """
@viewModel object
<test:ControlWithUnresolvableDependency />
""";


config.RouteTable.Add("WithError", "WithError", "WithError.dothtml", null);
fileLoader.MarkupFiles["WithError.dothtml"] = """
@viewModel object
<test:ThisControlDoesNotExist />
""";

config.RouteTable.Add("WithMasterPage", "WithMasterPage", "WithMasterPage.dothtml", null);
fileLoader.MarkupFiles["WithMasterPage.dothtml"] = """
@viewModel object
@masterPage MasterPage.dothtml
<dot:Content ContentPlaceHolderID=Content>test</dot:Content>
""";
fileLoader.MarkupFiles["MasterPage.dothtml"] = """
@viewModel object
<head></head>

<body>
<dot:ContentPlaceHolder ID=Content />
</body>
""";

config.RouteTable.Add("NonCompilable", "NonCompilable", null, presenterFactory: _ => throw null);

config.Freeze();

service = (DotvvmViewCompilationService)config.ServiceProvider.GetRequiredService<IDotvvmViewCompilationService>();
}
[TestMethod]
public void RequestContextInjection()
{
var route = service.GetRoutes().First(r => r.RouteName == "WithContextInjection");
service.BuildView(route, out _);
Assert.IsNull(route.Exception);
Assert.AreEqual(CompilationState.CompletedSuccessfully, route.Status);
}
[TestMethod]
public void InjectionUnresolvableDependency()
{
var route = service.GetRoutes().First(r => r.RouteName == "WithUnresolvableDependency");
service.BuildView(route, out _);
Assert.AreEqual(CompilationState.CompilationFailed, route.Status);
Assert.AreEqual("Unable to resolve service for type 'DotVVM.Framework.Tests.Runtime.ControlWithUnresolvableDependency+ThisServiceIsntRegistered' while attempting to activate 'DotVVM.Framework.Tests.Runtime.ControlWithUnresolvableDependency'.", route.Exception);
Assert.IsNotNull(route.Exception);
}
[TestMethod]
public void ErrorInMarkup()
{
var route = service.GetRoutes().First(r => r.RouteName == "WithError");
service.BuildView(route, out _);
Assert.AreEqual(CompilationState.CompilationFailed, route.Status);
Assert.IsNotNull(route.Exception);
Assert.AreEqual("The control <test:ThisControlDoesNotExist> could not be resolved! Make sure that the tagPrefix is registered in DotvvmConfiguration.Markup.Controls collection!", route.Exception);
}

[TestMethod]
public void MasterPage()
{
var route = service.GetRoutes().First(r => r.RouteName == "WithMasterPage");
service.BuildView(route, out var masterPage);
Assert.IsNull(route.Exception);
Assert.AreEqual(CompilationState.CompletedSuccessfully, route.Status);
Assert.IsNotNull(masterPage);
Assert.AreEqual(masterPage, service.GetMasterPages().FirstOrDefault(m => m.VirtualPath == "MasterPage.dothtml"));
// Assert.AreEqual(CompilationState.None, masterPage.Status); // it's not deterministic, because the master page is built asynchronously after the view asks for its viewmodel type
service.BuildView(masterPage, out _);
Assert.AreEqual(CompilationState.CompletedSuccessfully, masterPage.Status);
}

[TestMethod]
public void NonCompilable()
{
var route = service.GetRoutes().First(r => r.RouteName == "NonCompilable");
Assert.AreEqual(CompilationState.NonCompilable, route.Status);
service.BuildView(route, out _);
Assert.AreEqual(CompilationState.NonCompilable, route.Status);
Assert.IsNull(route.Exception);
}
}

public class ControlWithContextInjection: DotvvmControl
{
public ControlWithContextInjection(IDotvvmRequestContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
}
}

public class ControlWithUnresolvableDependency: DotvvmControl
{
public ControlWithUnresolvableDependency(ThisServiceIsntRegistered dependency)
{
if (dependency == null)
throw new ArgumentNullException(nameof(dependency));
}

public class ThisServiceIsntRegistered
{
}
}
}
Loading