Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResourceManager has incorrect BaseName when constructed with a Type #74918

Closed
madelson opened this issue Sep 1, 2022 · 9 comments · Fixed by #75497
Closed

ResourceManager has incorrect BaseName when constructed with a Type #74918

madelson opened this issue Sep 1, 2022 · 9 comments · Fixed by #75497
Labels
area-System.Resources bug good first issue Issue should be easy to implement, good for first-time contributors help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@madelson
Copy link
Contributor

madelson commented Sep 1, 2022

Description

According to the docs:

the BaseName property reflects the fully qualified namespace name and the root resource name of a resource file, without its culture or file name extension. For example, if an app's default resource file is named SampleApps.StringResources.resources, the value of the BaseName property is "SampleApps.StringResources".

This is true when you access the generated MyResource.ResourceManager.BaseName property, which uses the ResourceManager(baseName, assembly) constructor.

However, if you construct a ResourceManager with just a Type, it sets BaseName to Type.Name which is different. The resource lookup still works because it uses the type's full name internally.

Reproduction Steps

Console.WriteLine(MyResourceType.ResourceManager.BaseName); // e.g. "MyNamespace.MyResourceType"
Console.WriteLine(new ResourceManager(typeof(MyResourceType)).BaseName); // e.g. "MyResourceType"

Expected behavior

The BaseName property should be initialized from the type's full name.

Actual behavior

The BaseName property is initialized from the type's name.

Regression?

No.

Known Workarounds

Don't use the ResourceManager(Type) constructor if you will be accessing the BaseName property. This is unfortunate because that is the easiest constructor to use.

Configuration

.NET 6 Windows 10 x64.

Other information

Possibly this is "by design" and the docs just need to be updated? Not sure why that would be the case, though.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 1, 2022
@madelson madelson changed the title ResourceManager has incorrect BaseName when constructed with a type ResourceManager has incorrect BaseName when constructed with a Type Sep 1, 2022
@ghost
Copy link

ghost commented Sep 1, 2022

Tagging subscribers to this area: @dotnet/area-system-resources
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

According to the docs:

the BaseName property reflects the fully qualified namespace name and the root resource name of a resource file, without its culture or file name extension. For example, if an app's default resource file is named SampleApps.StringResources.resources, the value of the BaseName property is "SampleApps.StringResources".

This is true when you access the generated MyResource.ResourceManager.BaseName property, which uses the ResourceManager(baseName, assembly) constructor.

However, if you construct a ResourceManager with just a Type, it sets BaseName to Type.Name which is different. The resource lookup still works because it uses the type's full name internally.

Reproduction Steps

Console.WriteLine(MyResourceType.ResourceManager.BaseName); // e.g. "MyNamespace.MyResourceType"
Console.WriteLine(new ResourceManager(typeof(MyResourceType)).BaseName); // e.g. "MyResourceType"

Expected behavior

The BaseName property should be initialized from the type's full name.

Actual behavior

The BaseName property is initialized from the type's name.

Regression?

No.

Known Workarounds

Don't use the ResourceManager(Type) constructor if you will be accessing the BaseName property. This is unfortunate because that is the easiest constructor to use.

Configuration

.NET 6 Windows 10 x64.

Other information

Possibly this is "by design" and the docs just need to be updated? Not sure why that would be the case, though.

Author: madelson
Assignees: -
Labels:

area-System.Resources, untriaged

Milestone: -

@joperezr
Copy link
Member

joperezr commented Sep 6, 2022

Thanks for the report @madelson. Let us know if you are interested in throwing in a PR for this fix as well 👍

@joperezr joperezr added this to the 8.0.0 milestone Sep 6, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Sep 6, 2022
@joperezr joperezr added bug good first issue Issue should be easy to implement, good for first-time contributors help wanted [up-for-grabs] Good issue for external contributors untriaged New issue has not been triaged by the area owner and removed untriaged New issue has not been triaged by the area owner labels Sep 6, 2022
@madelson
Copy link
Contributor Author

madelson commented Sep 6, 2022

@joperezr sure I'll put together a quick fix for this!

madelson added a commit to madelson/runtime that referenced this issue Sep 13, 2022
This property is documented to return the "qualified namespace name and
the root resource name of a resource file". However, previously when
constructing a ResourceManager with a Type the BaseName property would
instead return the name of the type without its namespace.

fix dotnet#74918
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 13, 2022
@buyaa-n
Copy link
Member

buyaa-n commented Oct 15, 2022

According to the docs:

Have you checked the Note part of the doc?
image

It might be explaining the behavior you are seeing.

Possibly this is "by design" and the docs just need to be updated? Not sure why that would be the case, though.

I feel like it is by design, this constructor overload is used in so many places and the BaseName is used for loading the resources without any issue, I don't think it could be a bug. What you think @tarekgh?

@madelson
Copy link
Contributor Author

Have you checked the Note part of the doc?

@buyaa-n thanks I hadn't seen that note. I don't think it covers exactly what I'm describing here (since it is talking about commandline vs. VS behavior whereas I'm talking about constructing a ResourceManager in code from a Type), however, it does at least suggest that BaseName can omit the namespace sometimes.

I feel like it is by design, this constructor overload is used in so many places and the BaseName is used for loading the resources without any issue, I don't think it could be a bug

So this "bug" doesn't impact loading resources because currently the ResourceManager stores the passed-in Type internally and uses the namespace from that when retrieving resources.

Why I consider it a bug is that the current behavior makes the BaseName property fairly useless since it is unreliable. Where I've come across this is more niche cases like using BaseName to identify the underlying resource for a ResourceManager (we had an app mode which would render the UI with resource values replaced with names + keys to make things easier for translators).

Currently, you can have 2 ResourceManagers pointed at the same underlying resource, but the BaseNames are different, which doesn't make sense to me from a design perspective.

If there is a reason why this makes sense OR if it doesn't make sense but there is a desire to just leave things as is out of caution, that's fine and I'd just send a PR to update the docs to call out what the actual behavior is for the different constructors.

@buyaa-n
Copy link
Member

buyaa-n commented Oct 17, 2022

however, it does at least suggest that BaseName can omit the namespace sometimes.

Yes, it is not exactly explaining the behavior but at least mentions BaseName can omit the namespace

Found better explanations from the constructor doc ResourceManager(Type):

In addition to defining an app class named Example, the source code defines an internal class whose name, GreetingResources, is the same as the base name of the resource files. This makes it possible to successfully instantiate a ResourceManager object by calling the ResourceManager(Type) constructor.

From the Remarks part of the doc seems Desktop and VS expect the BaseName has same name as the type name:

image

So this "bug" doesn't impact loading resources because currently the ResourceManager stores the passed-in Type internally and uses the namespace from that when retrieving resources.

It seems the BaseName used for retrieving resource file name when changing locale(language), I would test that behavior before making this change. https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceManager.cs,322

@madelson
Copy link
Contributor Author

Found better explanations from the constructor doc

Hmm I'm not sure I see an explanation in the docs you referenced. This is just saying that you can use new ResourceManager(typeof(MyNamespace.MyClass)) as a way to reference a resource whose name is MyNamespace.MyClass in the assembly of that Type. It doesn't explain why the BaseName property is different than if you instead called new ResourceManager("MyNamespace.MyClass", typeof(MyNamespace.MyClass).Assembly), even though both managers are pointing to the exact same embedded resources.

It seems the BaseName used for retrieving resource file name when changing locale(language)

Yes, BaseName is used. However, the missing namespace gets patched in later because the original Type is stored in a private field locationInfo which then gets passed to GetManifestResourceStream(Type, string) which adds the namespace in (see https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs,279). So it ends up doing the same thing. I believe the test coverage does already cover this case.

Just to back out a bit, the essence of the issue here is that today new ResourceManager(typeof(MyNamespace.MyClass)) and new ResourceManager("MyNamespace.MyClass", typeof(MyNamespace.MyClass).Assembly) behave identically except for having different BaseName properties, which seems wrong to me.

The benefit of changing it is that it makes this property much more useful for consumers, who can actually rely on it to contain the resource name. The main risk as I see it (since we haven't identified a reason why this discrepancy is compelling), is backwards compat: maybe someone out there is relying on the current behavior in a way that would break under the "correct" behavior.

@buyaa-n
Copy link
Member

buyaa-n commented Oct 28, 2022

Sorry for delayed response @madelson, I have been out sick for a while, I did some research about this issue to clear my doubt.

The doc note below was the main cause of my doubt:

The BaseName property value of a resource file that is compiled and embedded from the command line does not include a namespace name unless you explicitly include one when compiling the file.

And the ResourceManager(Type) overload is looked like designed for loading resources in this case. The satellite assemblies loaded at runtime, so worried changing the BaseName for this overload might cause runtime failure. Therefore, I spend some time to load manually created resources in .Net core app (all existing examples are for .net framework). The testing showed that the BaseName not needs to be same as the Type name for ResourceManager(Type) overload. Now I am OK with fixing this issue.

The main risk as I see it (since we haven't identified a reason why this discrepancy is compelling), is backwards compat: maybe someone out there is relying on the current behavior in a way that would break under the "correct" behavior.

Right, I would not expect many depend on this behavior, though it is somewhat breaking change, and I am not sure how acceptable this breaking change is. CC @tarekgh @ericstj @steveharter @joperezr.

@madelson
Copy link
Contributor Author

Appreciated the detailed response and investigation @buyaa-n . Sorry to hear that you were ill!

buyaa-n pushed a commit that referenced this issue Nov 3, 2022
* Make ResourceManager.BaseName work as documented.

This property is documented to return the "qualified namespace name and
the root resource name of a resource file". However, previously when
constructing a ResourceManager with a Type the BaseName property would
instead return the name of the type without its namespace.

fix #74918

* Remove _locationInfo field

* Add additional test cases for ResourceManager.BaseName.

Address feedback from #75497 (comment).

* Fix BaseName test case in response to #75497 (comment)
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Nov 3, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Resources bug good first issue Issue should be easy to implement, good for first-time contributors help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants