dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.19k stars 4.72k forks source link

System.Xaml FileNotFoundException when using XmlnsPrefix and .resx in a referenced assembly #804

Closed koyote closed 2 years ago

koyote commented 4 years ago

Hi,

When a .Net Core project references a .Net Framework project which has "XmlnsPrefix" set in AssemblyInfo AND we call code that accesses a resource in a .resx file, we get the following exception:

System.IO.FileNotFoundException: Could not load file or assembly 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. The system cannot find the file specified. File name: 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' at System.ModuleHandle.ResolveTypeHandleInternal(RuntimeModule module, Int32 typeToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext) at System.Reflection.RuntimeModule.ResolveType(Int32 metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(MetadataToken caCtorToken, MetadataImport& scope, RuntimeModule decoratedModule, MetadataToken decoratedToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder1& derivedAttributes, RuntimeType& attributeType, IRuntimeMethodInfo& ctor, Boolean& ctorHasParameters, Boolean& isVarArg) at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes) at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType) at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeAssembly assembly, RuntimeType caType) at System.Reflection.RuntimeAssembly.GetCustomAttributes(Type attributeType, Boolean inherit) at System.Attribute.GetCustomAttributes(Assembly element, Type attributeType, Boolean inherit) at System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit) at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](Assembly element) at System.Resources.ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(Assembly a, UltimateResourceFallbackLocation& fallbackLocation) at System.Resources.ResourceManager.CommonAssemblyInit() at System.Resources.ResourceManager..ctor(String baseName, Assembly assembly) at ClassLibrary1.Strings.get_ResourceManager() in Strings.Designer.cs:line 42 at ClassLibrary1.Strings.get_XXX() in Strings.Designer.cs:line 68

See attached Project: ConsoleApp2.zip

danmoseley commented 4 years ago

@ericstj do you know where this should go?

ericstj commented 4 years ago

a .Net Core project references a .Net Framework project

We do our best to make .NETCore work with .NETFramework libraries when the types exist, but in the case of this sample the type used by the .NETFramework library (System.Xaml) doesn't exist in the base shared framework.

You can change your .NETCore console project use the WindowsDesktop sdk instead, this contains WPF and will be able to resolve System.Xaml. That's probably the best workaround if you're compiling for windows.

There's not a great way to avoid this since reflection needs to be able to load assemblies in order to reflect.

One could argue that Assembly.GetCustomAttribute(type) shouldn't throw for other attribute types which cannot be resolved, since the caller passed in a resolved runtime type. This is probably correct most of the time, but it could be wrong in the case the type that wasn't resolved was actually forwarding to the type you were looking for. Perhaps FilterCustomAttributeRecord could be improved to only throw when it cannot resolve a type and that type has the same name as the one your filtering on. Otherwise it can ignore the unresolved type since it cannot possibly match the one its looking for (since it has a different name). That makes this a make reflection more resilient bug, so keeping this tagged as reflection.

ericstj commented 4 years ago

/cc @steveharter

steveharter commented 4 years ago

Per discussion with @GrabYourPitchforks the proposed work-around will not work in the general case because GetCustomAttributes(Type) will enumerate all attributes that are compatible with Type (checking base types). So just comparing the type name wouldn't work.

We could attempt to detect it the Type passed in is sealed, but then this becomes very specific.

A better work-around is to have System.Resources.ManifestBasedResourceGroveler use GetCustomAttributeData instead of GetCustomAttribute so no attribute is instantiated. So moving to System.Resources.

Is this issue blocking any key scenarios?

GrabYourPitchforks commented 4 years ago

@buyaa-n @tarekgh We think a better workaround would be to change ManifestBasedResourceGroveler. See above.

ericstj commented 4 years ago

GetCustomAttributeData doesn't avoid load of the assembly which defines the custom attribute. We would need something that just reads the metadata and doesn't try to create Type objects (or activate types).

Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. The system cannot find the file specified.
File name: 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
   at System.ModuleHandle.ResolveMethodHandleInternalCore(RuntimeModule module, Int32 methodToken, IntPtr[] typeInstantiationContext, Int32 typeInstCount, IntPtr[] methodInstantiationContext, Int32 methodInstCount)
   at System.ModuleHandle.ResolveMethodHandleInternal(RuntimeModule module, Int32 methodToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext)
   at System.Reflection.CustomAttributeData..ctor(RuntimeModule scope, MetadataToken caCtorToken, ConstArray& blob)
   at System.Reflection.CustomAttributeData.GetCustomAttributes(RuntimeModule module, Int32 tkTarget)
   at System.Reflection.CustomAttributeData.GetCustomAttributesInternal(RuntimeAssembly target)
   at System.Reflection.RuntimeAssembly.GetCustomAttributesData()
   at custAttr.Program.GetCustomAttribute[T](Assembly assembly) in C:\Users\erics\source\repos\custAttr\custAttr\Program.cs:line 19
   at custAttr.Program.Main(String[] args) in C:\Users\erics\source\repos\custAttr\custAttr\Program.cs:line 14

custAttr.zip

GrabYourPitchforks commented 4 years ago

At that point then it seems that addressing this in the reflection layer would be a non-trivial work item, and per Steve's earlier comment it wouldn't solve the issue more generally anyway. The workaround previously given by retargeting the assembly against WindowsDesktop seems like it'd work since it'd allow the runtime to discover System.Xaml, no?

ericstj commented 4 years ago

Retargeting the application to WindowsDesktop would work, but that's not always possible.

I think the problem in general is that Reflection isn't very resilient to dangling references. It's pretty unfortunate that this surfaces in the cases where the user isn't calling reflection but the framework itself needs to do so as part of its basic functionality.

GrabYourPitchforks commented 4 years ago

Would xcopying System.Xaml alongside their app work? You know heaps more about assembly loading nits than I do. :)

(Edit: This still assumes changing the app, not the library.)

steveharter commented 4 years ago

Would xcopying System.Xaml alongside their app work? You know heaps more about assembly loading nits than I do. :)

You can't just copy (.NET Core requires entry in .deps.json), but you can modify the project to add an assembly reference to System.Xaml and make sure it exists alongside their app.

I think the problem in general is that Reflection isn't very resilient to dangling references

Is there an argument to be made that the current behavior is desired -- i.e. that for consistency attributes should never be "skipped" because the attribute's Type can't be loaded?

Also what scenario this is current blocking, if any (WPF interop with existing assemblies?). The work-around of copying the assembly may be fine for this particular case.

If we think this scenario is important for 5.0 I will investigate potential fix.

ericstj commented 4 years ago

The scenario blocked would be someone building a shared class-library that needs to work in both WPF apps and console apps. I suspect this is @koyote's scenario, but I could be wrong. The specific attribute is https://docs.microsoft.com/en-us/dotnet/api/system.windows.markup.xmlnsprefixattribute?view=netframework-4.8

The workaround would be move this attribute (and presumably any others like https://docs.microsoft.com/en-us/dotnet/api/system.windows.markup.xmlnsdefinitionattribute?view=netframework-4.8) into an assembly which is only used when WPF is available.

I don't think this scenario alone warrants prioritizing new API for 5.0, but I do think it's a ref count against reflection APIs that are resilient to missing types/assemblies.

steveharter commented 4 years ago

The workaround would be move this attribute (and presumably any others like https://docs.microsoft.com/en-us/dotnet/api/system.windows.markup.xmlnsdefinitionattribute?view=netframework-4.8) into an assembly which is only used when WPF is available.

Along with a [TypeForwardedTo] in System.Xaml I assume.

Should we create a new WPF issue then? (keep this issue to track resilient missing types).

ericstj commented 4 years ago

Right. This would need to happen in runtime repo, not WPF. WPF would need to react to it afterwards. We would need to add a System.Xaml shim to the set that we build in runtime, like WindowsBase. I’m not inclined to do this based on a single report for the reasons I called out above as well as the cost & complexity it adds.

If we hear from more folks that need this to work and can’t depend on WindowsDesktop we can reconsider. @vatsan-madhavan

vatsan-madhavan commented 4 years ago

Agree that a System.Xaml shim is not yet warranted. A separate System.Xaml package (https://github.com/dotnet/wpf/issues/46) might be of help to those who can't retarget to windowsdesktop entirely.

vatsan-madhavan commented 4 years ago

/cc @dotnet/wpf-developers

koyote commented 4 years ago

The scenario blocked would be someone building a shared class-library that needs to work in both WPF apps and console apps. I suspect this is @koyote's scenario, but I could be wrong. The specific attribute is https://docs.microsoft.com/en-us/dotnet/api/system.windows.markup.xmlnsprefixattribute?view=netframework-4.8

That is correct. The dotnet core application was extracted from a larger .net framework/WPF application and both share some assemblies including the .net framework assembly with the issue above.

You can't just copy (.NET Core requires entry in .deps.json), but you can modify the project to add an assembly reference to System.Xaml and make sure it exists alongside their app.

So if I understand correctly, the workaround is for the referenced project (or the dotnet core project?) to add a reference to System.Xaml?

steveharter commented 4 years ago

Based on discussion, moving to Future. If there are additional scenarios or hits on this, please re-open.

ericstj commented 4 years ago

@koyote here's the workarounds in order of precedence:

  1. Make your application target WindowsDesktop.
  2. Either remove, or factor your usage of System.Xaml attributes into a separate assembly.
  3. Add a System.Xaml to your app that defines these attributes.
ghost commented 2 years ago

Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process.

This process is part of our issue cleanup automation.

ghost commented 2 years ago

This issue will now be closed since it had been marked no-recent-activity but received no further activity in the past 14 days. It is still possible to reopen or comment on the issue, but please note that the issue will be locked if it remains inactive for another 30 days.