aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

.NET 7/8 SDK breaks .NET Framework compilation

Open Shane32 opened this issue 1 year ago • 1 comments

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

  1. Create .NET Framework 4.8 MVC web application which uses ASP.Net Core 2.1
  2. Add a reference to Microsoft.NETFramework.ReferenceAssemblies pursuant to MS documentation
  3. 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

  1. Pin the .NET 6.0.420 SDK via global.json, or
  2. Remove the reference to Microsoft.NETFramework.ReferenceAssemblies

Configuration

  • .NET Framework 4.8
  • Windows 10 22H2
  • x64

Other information

No response

Shane32 avatar Mar 14 '24 03:03 Shane32

@jaredpar should this be moved to the razor repo ?

mkArtakMSFT avatar Mar 15 '24 16:03 mkArtakMSFT

@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.

chsienki avatar Mar 18 '24 22:03 chsienki

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.

chsienki avatar Mar 19 '24 21:03 chsienki

@Shane32 you mentioned that removing the ReferenceAssemblies reference is a workaround. Why do you need that reference in the first place?

mkArtakMSFT avatar Apr 03 '24 16:04 mkArtakMSFT

@Shane32 you mentioned that removing the ReferenceAssemblies reference 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).

Shane32 avatar Apr 03 '24 16:04 Shane32

Thanks for additional details. Given the limitations that you're facing we recommend you take reference to specific packages that you need instead.

mkArtakMSFT avatar Apr 04 '24 16:04 mkArtakMSFT