Closed twenzel closed 3 years ago
@twenzel There is not enough information here to reproduce the issue you're explaining. There is a ServiceCollectionExtensionsTest which covers using RazorLight with Microsoft DI. I'm not clear what code you wrote to get an InvalidOperationException - you gave me all the code except for the call site that triggers this exception. I'm also not sure why you're registering all these things as singletons after you call Build. The whole point of calling UseMemoryCachingProvider is so that it sets up the cache for when you call Build.
@jzabroski this is happening for me also on aspnet core 3.1, you shouldn't really need much to reproduce it:
services.AddRazorLight(() => new RazorLightEngineBuilder()
.UseFileSystemProject($"{environment.ContentRootPath}/Templates")
.UseMemoryCachingProvider()
.Build());
Trace:
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: RazorLight.IEngineHandler Lifetime: Singleton ImplementationType: RazorLight.EngineHandler': Unable to resolve service for type 'RazorLight.RazorLightOptions' while attempting to activate 'RazorLight.EngineHandler'.)
---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: RazorLight.IEngineHandler Lifetime: Singleton ImplementationType: RazorLight.EngineHandler': Unable to resolve service for type 'RazorLight.RazorLightOptions' while attempting to activate 'RazorLight.EngineHandler'.
---> System.InvalidOperationException: Unable to resolve service for type 'RazorLight.RazorLightOptions' while attempting to activate 'RazorLight.EngineHandler'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
--- End of inner exception stack trace ---
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
--- End of inner exception stack trace ---
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
@twenzel I digged into an older project that started in core 2.1 before migrating to 3.1, where Razorlight works!
The difference is, which I hadn't noticed, that the working project uses WebHost
(default in 2.1) instead of Host
(default in 3.1)
I started cleaning this up.
@georgiosd So, I would appreciate a repro.
I spent an hour today figuring out if I could repro @twenzel or your problem, and I cannot. Below is an example of what I tried.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
using RazorLight.Extensions;
using System;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
namespace RazorLight.Tests.Extensions
{
public class ServiceCollectionExtensionsTest
{
public class EmbeddedEngineStartup
{
public void Configure(IApplicationBuilder app)
{
}
public void ConfigureServices(IServiceCollection services)
{
var embeddedEngine = new RazorLightEngineBuilder()
.UseEmbeddedResourcesProject(typeof(EmbeddedEngineStartup)) // exception without this (or another project type)
.UseMemoryCachingProvider()
.Build();
//services.AddSingleton(embeddedEngine.Options);
//services.AddSingleton(embeddedEngine.Handler.Compiler);
//services.AddSingleton(embeddedEngine.Handler.FactoryProvider);
//services.AddSingleton(embeddedEngine.Handler.Cache);
services.AddRazorLight(() => embeddedEngine);
}
}
#if !(NETCOREAPP2_0)
[Fact]
public void Ensure_Works_With_Generic_Host()
{
static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<EmbeddedEngineStartup>();
});
}
var hostBuilder = CreateHostBuilder(null);
Assert.NotNull(hostBuilder);
var host = hostBuilder.Build();
Assert.NotNull(host);
host.Services.GetService<IRazorLightEngine>();
}
#endif
}
}
Workaround Register missing dependencies separately
public void ConfigureServices(IServiceCollection services) { var engine = new RazorLightEngineBuilder() .UseEmbeddedResourcesProject(typeof(Startup)) // exception without this (or another project type) .UseMemoryCachingProvider() .Build(); services.AddRazorLight(() => engine); services.AddSingleton(engine.Options); services.AddSingleton(engine.Handler.Compiler); services.AddSingleton(engine.Handler.FactoryProvider); services.AddSingleton(engine.Handler.Cache); }
This workaround fixes it for me too.
The workaround proposed by @twenzel only works for 2.0.0-beta4. The 2.0.0-rc.2 version throws:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: RazorLight.IEngineHandler Lifetime: Singleton ImplementationType: RazorLight.EngineHandler': Unable to activate type 'RazorLight.EngineHandler'. The following constructors are ambiguous: Void .ctor(RazorLight.RazorLightOptions, RazorLight.Compilation.IRazorTemplateCompiler, RazorLight.Compilation.ITemplateFactoryProvider, RazorLight.Caching.ICachingProvider) Void .ctor(Microsoft.Extensions.Options.IOptions`1[RazorLight.RazorLightOptions], RazorLight.Compilation.IRazorTemplateCompiler, RazorLight.Compilation.ITemplateFactoryProvider, RazorLight.Caching.ICachingProvider))'
There is a sample project: https://github.com/knuxbbs/RazorLightSample
@jzabroski You need to setup the service provider to validate on build like this:
static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args).UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = true;
}).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<EmbeddedEngineStartup>(); });
}
For 2.0.0-rc.2, what worked for me was registering IEngineHandler
before call AddRazorLight
.
public void ConfigureServices(IServiceCollection services)
{
var engine = new RazorLightEngineBuilder()
.UseEmbeddedResourcesProject(typeof(Startup))
.UseMemoryCachingProvider()
.Build();
services.AddSingleton(engine.Handler);
services.AddRazorLight(() => engine);
}
@jzabroski You need to setup the service provider to validate on build like this:
static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args).UseDefaultServiceProvider((context, options) => { options.ValidateOnBuild = true; }).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<EmbeddedEngineStartup>(); }); }
@edvinklaebo - so, you're saying the following test is incorrect?
I am not sure why I would need to call options.ValidateOnBuild = true;
in the test, as I am verifying host.Services.GetService<IRazorLightEngine>();
?
Or, did you mean to reply to @knuxbbs ?
@edvinklaebo Hmm... I just updated the test and it looks like you're right. Why would host.Services.GetService<IRazorLightEngine>();
succeed?
@edvinklaebo I played around with your sample code and ValidateOnBuild = true
does cause the test to fail. You can see the above commit.
~One thing I do not understand is why changing IEngineHandler to be resolved using a transient makes the ValidateOnBuild error go away. It actually makes whatever problem theoretically worse. So there is something ValidateOnBuild does that doesn't make sense to me.~ (Ignore this comment. I ran the wrong test.)
ValidateOnBuild was introduced in .netcoreapp3.0 TFM, so it makes sense this would only have caused issues starting with .NET Core 3.x.
I think I figured this out.
The issue is that since we resolve the IEngineHandler from the RazorLightEngineBuilder call, the ValidateOnBuild cannot statically verify the contents of the lambda. The only way it can "know" that IEngineHandler cannot be resolved is because we told it about the dependency in the line before. TryAdd does what it's supposed to do, which is only register the engine handler if it hasn't already been provided. But, the static verification doesn't know that we've provided a concrete instance in the next registration.
tl;dr: By eliminating this line, all tests pass:
It appears I broke this, although I can't remember why I did this (it was almost certainly to handle a scenario where people do silly things like register RazorLight using the RazorLightEngineBuilder, but then call the EngineHandler interface directly), and I didn't submit a issue number along with the check-in.
Is there any alternative to removing this line?
I think the workaround is to fail hard if people try to call the engine handler directly after calling AddRazorLight:
services.TryAddSingleton<IEngineHandler>(p =>
throw new InvalidOperationException($"This exception can only occur if you inject {nameof(IEngineHandler)} directly using {nameof(ServiceCollectionExtensions)}.{nameof(AddRazorLight)}"));
This might break some people, but at least the reason is fairly clear.
Feedback? Thoughts? Questions? Concerns?
One last thought. I guess there could be an AddRazorLightEngineHandler() extension method for those that don't want RazorLightEngine. The exception message could then be updated to suggest a call to action to "Consider using AddRazorLightEngineHandler() instead." But that still has to deal with things RazorLightEngine bundles, like support for @inject
, dynamic templates, and any other callbacks callers wish to make to enhance RazorLight.
Describe the bug InvalidOperationException: Unable to resolve service for type 'RazorLight.RazorLightOptions' while attempting to activate 'RazorLight.EngineHandler'.
To Reproduce Steps to reproduce the behavior: Create a new ASP.NET Core 3.1 MVC application with the new generic host template
Add RazorLight to the services:
Exception:
InvalidOperationException: Unable to resolve service for type 'RazorLight.RazorLightOptions' while attempting to activate 'RazorLight.EngineHandler'.
Expected behavior Runs smoothly.
The problem is that the
RazorLightOptions
are not registered which are required by theEngineHandler
.Workaround Register missing dependencies separately
Information (please complete the following information):