Closed phil-scott-78 closed 3 years ago
To make sure it wasn't anything weird I was doing, because Lord knows I do weird things with Statiq, I built up the simplest reproduction I could. Sure enough ,same error. Reproduction is here - https://github.com/phil-scott-78/statiq-dotnet6
Got a bit less noise on this exception with the singular file when running dotnet run -l Debug
[ERRO] Content/PostProcess » RenderContentPostProcessTemplates » ExecuteIf » ExecuteIf » RenderRazor » [/Users/philscott/RiderProjects/StatiqNet6/input/index.cshtml => index.html] Object does not match target type.
[DBUG] Exception while executing pipeline Content/PostProcess: System.Reflection.TargetException: Object does not match target type.
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Statiq.Razor.StatiqViewCompiler.CreateCompilation(String generatedCode, String assemblyName)
at Statiq.Razor.StatiqViewCompiler.CompileAndEmit(RazorCodeDocument codeDocument, String generatedCode)
at Statiq.Razor.RazorCompiler.GetCompilation(RazorProjectItem projectItem)
at Statiq.Razor.RazorCompiler.<>c__DisplayClass11_0.<CompilePage>b__0(CompilerCacheKey _)
at Statiq.Common.ConcurrentCache`2.<>c__DisplayClass2_1.<GetOrAdd>b__1()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at System.Lazy`1.get_Value()
at Statiq.Common.ConcurrentCache`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Statiq.Razor.CachingCompiler.GetOrAddCachedCompilation(CompilerCacheKey cacheKey, Func`2 valueFactory)
at Statiq.Razor.RazorCompiler.CompilePage(RenderRequest request, Int32 contentCacheCode, RazorProjectItem projectItem)
at Statiq.Razor.RazorCompiler.GetPageFromStreamAsync(IServiceProvider serviceProvider, RenderRequest request)
at Statiq.Razor.RazorCompiler.RenderPageAsync(RenderRequest request)
at Statiq.Razor.RazorService.RenderAsync(RenderRequest request)
at Statiq.Razor.RenderRazor.<>c__DisplayClass16_0.<<ExecuteContextAsync>g__RenderDocumentAsync|1>d.MoveNext()
Progress! On macOS I'm getting a Microsoft.AspNetCore.Mvc.Razor.Compilation.DefaultViewCompiler
rather than the expected Microsoft.AspNetCore.Mvc.Razor.Compilation.RuntimeViewCompiler
when calling InnerViewCompilerProvider.GetCompiler()
.
Not sure why or how to move forward, but that's the scoop so far.
Sorry for the train of thought here, but I'm in the weeds and I wanted to dump what I found so hopefully someone smarter than me can pick up the trail, or at least tell me I'm on a wild goose chase here.
This change is sus, especially considering this comment "This name is hardcoded in RazorRuntimeCompilationMvcCoreBuilderExtensions. Make sure it's updated if this is ever renamed." Looks like with this commit they merged the DefaultViewCompiler
and DefaultViewCompilerProvider
into one class.
But tbh I'm struggling wrapping my head around whether this would cause my failure. With Statiq being compiled against 3.1 it feels like this conditional might not trigger with the new name causing the TryAddSingleton
to skip the add because a previous instance never got removed. But that doesn't quite explain why it works in Windows and not macOS or Ubuntu. And I'm not sure why a different runtime would even cause this either.
Fantastic work getting to something we can investigate further. At first glance I think you’re definitely on to something and going down the right track. I’ll try to jump in and take a closer look tomorrow or in the next couple days. I do love a good debugging mystery :)
Going to move this issue to Framework since it's almost certainly with the Framework Razor extension.
Also for anyone else who ends up here with the same problem, a global.json
locking .NET to 5 until this is resolved should help work around it (https://docs.microsoft.com/en-us/dotnet/core/versions/selection#the-sdk-uses-the-latest-installed-version):
{
"sdk": {
"version": "5.0.0"
}
}
Ok, had some more time to look at this tonight.
Because I was rushing I missed a key bit of detail - I had a global.json on my windows box that was forcing the beta version of dotnet-6. So this issue isn't OS specific which makes things SO much easier to wrap my head around. I stepped into the ASP.NET code and sure enough that change I pointed to seems to be at fault. I'm gonna try and get an easier reproduction going and maybe submit a PR to get this in before .NET 6 goes live...maybe
Created an issue in the aspnetcore project - https://github.com/dotnet/aspnetcore/issues/37049
This is a rough one.
Fantastic work on the root cause here. I'm curious what the ASP.NET team says and if they'll fix this upstream now that it's at RC. If not, we might still have a couple options. We could consider registering our own DefaultViewCompiler
- though I haven't groked the issue enough to know if that would help?
Going to try and find some time later today to jump in and understand this issue while it's fresh for you. Hopefully together we can figure out a path forward.
This is definitely a nasty bug on their part. But the best I can tell it requires the Razor compilation to be referenced via a netcore 3.1 class library that is being used by a net6 application.
That enough for them to permanently require that DefaultViewCompileProvider class to be permanently the same name forever to keep that compatibility though?
Unfortunately for me to do the magic with generating the social cards I need net6. But you all might need to pin net5 rc long term until you can bring everything to net6. Net6 might have some very interesting hot reload options though...
Forgot to add - I could be totally wrong on the severity because I still have no idea why the runtime is registering that class to begin with instead of the netcore3.1. Bit over my head when it comes to runtime magic
In RazorRuntimeCompilationMvcCoreBuilderExtensions.AddServices()
(which is called by AddRazorRuntimeCompilation
), it does this:
services.TryAddSingleton<IViewCompilerProvider, RuntimeViewCompilerProvider>();
That sets the RuntimeViewCompilerProvider
as the servicec for IViewCompilerProvider
, but there's a problem. It's using TryAdd...
which will only bind the interface type to the implementation type if no other implementation type is already registered for that interface. The RazorRuntimeCompilationMvcCoreBuilderExtensions.AddServices()
method tries to account for this by calling this just before the TryAdd...
:
ServiceDescriptor serviceDescriptor = services.FirstOrDefault((ServiceDescriptor f) => f.ServiceType == typeof(IViewCompilerProvider) && f.ImplementationType?.Assembly == typeof(IViewCompilerProvider).Assembly && f.ImplementationType.FullName == "Microsoft.AspNetCore.Mvc.Razor.Compilation.DefaultViewCompilerProvider");
if (serviceDescriptor != null)
{
services.Remove(serviceDescriptor);
}
In an ideal world, that would remove the existing implementation type for IViewCompilerProvider
before adding the new runtime one. But as you noted, .NET 6 no longer registers DefaultViewCompilerProvider
as the implementation of IViewCompilerProvider
- instead it registers a newly combined DefaultViewCompiler
which also implements IViewCompilerProvider
. Therefore, the code above doesn't find the existing IViewCompilerProvider
to remove it and following that, because there's still a registered implementation, doesn't register the RuntimeViewCompilerProvider
we were expecting to see. And because Statiq assumes we'll get a RuntimeViewCompilerProvider
when we ask for a IViewCompilerProvider
, some other reflection code breaks due to mismatched types.
All of this is likely due to different library versions. Statiq currently uses the 3.1.x version of the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
libraries. I don't think I'm prepared to update Razor entirely yet (unless lots more problems on .NET 6 become evident), so let's take that as a given. On the other hand, some more foundational ASP.NET Core libraries ride with the SDK, so that's where the disconnect is. The RuntimeCompilation
library thinks it's 3.1.x ASP.NET Core code so that check above should work. But .NET 6 moved the cheese and now they're incompatible.
So...the solution! It comes down to removing that IViewCompilerProvider
from the service collection before registering (or rather letting it register) the RuntimeViewCompilerProvider
one we're expecting. We can be pretty indiscrimanant here since we're not playing is as big a sandbox - removing any other IViewCompilerProvider
besides the one we know we're about to register should be sufficient:
ServiceDescriptor serviceDescriptor = serviceCollection.FirstOrDefault((ServiceDescriptor f) => f.ServiceType == typeof(IViewCompilerProvider));
if (serviceDescriptor is object)
{
serviceCollection.Remove(serviceDescriptor);
}
That allows the AddRazorRuntimeCompilation()
call to do it's thing and we're good across .NET Core 3, .NET 5, and .NET 6. I'll push a commit shortly.
Very clever! I like it.
Once a build is out there I'll pull it down and give it a go
Okay - fix is in, just wanted to make sure it wouldn't break on older .NET targets (though it's a pretty low-risk change given the existing behavior). I'll get a release out later this week - have a couple more things in flight so not quite ready to publish one yet.
FWIW, I didn't notice any changes to this code in the dotnet repo, but just to be sure I checked RC2. No magic - still not working.
Meanwhile I’ve still got to get a release with the fix out. Somewhere along the way I broke an unrelated test and am still trying to track that down 😭
Saw the latest release and I wanted to confirm it fixes this issue for anyone else that ends up here googling the error.
Thanks!
Seeing this error compiling razor on dotnet 6 rc1 but only with Linux and macOS -
Object does not match target type
Preview releases of dotnet 6 worked fine, but with the latest version this is failing on cshtml.
You can see a full build output here but here is the full stack trace.