.NET 7/8 SDK breaks .NET Framework compilation
Description
When compiling a .NET Framework 4.8 web application that uses ASP.NET Core 2.1 via the .NET 7/8 SDK, the web app fails to run under IIS Express.
This scenario is triggered only when there is a reference to Microsoft.NETFramework.ReferenceAssemblies. See:
- https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/reference-assemblies
Note that ASP.NET Core 2.1 is currently supported for .NET Framework. See here:
- https://dotnet.microsoft.com/en-us/platform/support/policy/aspnet/2.1-packages
Reproduction Steps
- Create .NET Framework 4.8 MVC web application which uses ASP.Net Core 2.1
- Add a reference to Microsoft.NETFramework.ReferenceAssemblies pursuant to MS documentation
- Attempt running it from Visual Studio 2022 with IIS Express
Here is a sample repo: https://github.com/Shane32/SampleNet48Mvc
Sample project's csproj file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<DebugType>full</DebugType>
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />
<AssemblyName>SampleNet48Mvc</AssemblyName>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release</Configurations>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<!-- the following reference is fine with .NET 6 SDK, but breaks when using .NET 7 / 8 SDK -->
<!-- when commenting this out, must delete bin/obj folders or problem will persist -->
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
</ItemGroup>
</Project>
Expected behavior
The project should run properly.
Actual behavior
Throws exception:
InvalidOperationException: Cannot find reference assembly 'Microsoft.CSharp.dll' file for package Microsoft.CSharp.Reference
Exception details:
System.InvalidOperationException: Cannot find reference assembly 'Microsoft.CSharp.dll' file for package Microsoft.CSharp.Reference
at Microsoft.Extensions.DependencyModel.Resolution.ReferenceAssemblyPathResolver.TryResolveAssemblyPaths(CompilationLibrary library, List`1 assemblies)
at Microsoft.Extensions.DependencyModel.Resolution.CompositeCompilationAssemblyResolver.TryResolveAssemblyPaths(CompilationLibrary library, List`1 assemblies)
at Microsoft.Extensions.DependencyModel.CompilationLibrary.ResolveReferencePaths(ICompilationAssemblyResolver resolver, List`1 assemblies)
at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
at Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeatureProvider.PopulateFeature(IEnumerable`1 parts, MetadataReferenceFeature feature)
at Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager.PopulateFeature[TFeature](TFeature feature)
at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorReferenceManager.GetCompilationReferences()
at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorReferenceManager.get_CompilationReferences()
at Microsoft.CodeAnalysis.Razor.CompilationTagHelperFeature.GetDescriptors()
at Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase.ExecuteCore(RazorCodeDocument codeDocument)
at Microsoft.AspNetCore.Razor.Language.DefaultRazorEngine.Process(RazorCodeDocument document)
at Microsoft.AspNetCore.Razor.Language.RazorProjectEngine.Process(RazorProjectItem projectItem)
at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.CompileAndEmit(String relativePath)
at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.OnCacheMiss(String normalizedPath)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorPageFactoryProvider.CreateFactory(String relativePath)
at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.CreateCacheResult(HashSet`1 expirationTokens, String relativePath, Boolean isMainPage)
at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.OnCacheMiss(ViewLocationExpanderContext expanderContext, ViewLocationCacheKey cacheKey)
at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.LocatePageFromViewLocations(ActionContext actionContext, String pageName, Boolean isMainPage)
at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.FindView(ActionContext context, String viewName, Boolean isMainPage)
at Microsoft.AspNetCore.Mvc.ViewEngines.CompositeViewEngine.FindView(ActionContext context, String viewName, Boolean isMainPage)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.FindView(ActionContext actionContext, ViewResult viewResult)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.<ExecuteAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ViewResult.<ExecuteResultAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeResultAsync>d__20.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextResultFilterAsync>d__28`2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeResultFilters>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextResourceFilter>d__23.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeFilterPipelineAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeAsync>d__16.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()
Regression?
Yes; works fine when pinning the .NET 6 SDK via global.json
Known Workarounds
- Pin the .NET 6.0.420 SDK via global.json, or
- Remove the reference to Microsoft.NETFramework.ReferenceAssemblies
Configuration
- .NET Framework 4.8
- Windows 10 22H2
- x64
Other information
No response
@jaredpar should this be moved to the razor repo ?
@mkArtakMSFT Looking at the repro this is occurring as part of runtime compilation, specifically as part of this call stack: https://github.com/dotnet/aspnetcore/blob/55e125cb3625f43ea7095c7e0cc1a9abda141992/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RuntimeViewCompiler.cs#L312
Previously we decided to track issues related to RC in this repo as it's where the code is located.
Ok, I at least understand the issue:
As part of the MetadataReferenceFeatureProvider we go over each application part and get the contained reference paths:
https://github.com/dotnet/aspnetcore/blob/5599953b969d73304a710417dc47bb2fd800d4ce/src/Mvc/Mvc.Razor/src/Compilation/MetadataReferenceFeatureProvider.cs#L36-L48
That in turn creates a dependency context for the given assembly to get the list of its dependent assemblies, which are then individually queried for their reference paths:
https://github.com/dotnet/aspnetcore/blob/5599953b969d73304a710417dc47bb2fd800d4ce/src/Mvc/Mvc.Core/src/ApplicationParts/AssemblyPart.cs#L52-L56
That calls into Microsoft.Extensions.DependencyModel.CompilationLibrary.ResolveReferencePaths() with the default resolver which is a combination resolver including the ReferenceAssembliesPathResolver. When the reference assembly's resolver is called we end up at
https://github.com/dotnet/runtime/blob/6d412f4efdd3215dc2cde7b13f5cc35e2c1e8607/src/libraries/Microsoft.Extensions.DependencyModel/src/Resolution/ReferenceAssemblyPathResolver.cs#L44-L61
For most assemblies this resolver doesn't do anything, but by adding the ReferenceAssemblies package a whole set of references go down this path because library.Type resolves to referenceassembly.
When the resolver fails to find the matching assembly in the reference assemblies folders it throw on line 56. That bubbles all the way back up to original call in MetadataReferenceFeatureProvider and beyond.
@Shane32 you mentioned that removing the ReferenceAssemblies reference is a workaround. Why do you need that reference in the first place?
@Shane32 you mentioned that removing the
ReferenceAssembliesreference is a workaround. Why do you need that reference in the first place?
I can't remember now exactly. I use GitHub Actions to test and compile the project. Perhaps I was trying to compile it on a Linux VM, even though it is deployed to a Windows VM, or perhaps I was targeting an old unsupported version of .NET Framework. I do remember that adding the references throughout my solution fixed all my compilation issues, although currently it does not appear that I need the reference (at least in this project).
Thanks for additional details. Given the limitations that you're facing we recommend you take reference to specific packages that you need instead.