C# - Sharing resources file between project - c#

I would like two how to do to share Resources files between 2 (or more) projects?
So, to resume, I've three project :
the development project (CF.NET) that include the resource file (with all definition).
I've two other projects that are empty BUT linking to the development projects, it's just a different build each time, so when I modify the development project, all three projects are updated too. (Modification of the csproj file.)
Question is, what about Resources files? When I try to access from the development project I get all resources but when I try from the 2 others, it throws an "MissingManifestResourceException".
Any idea how to solve this issue?
Thanks.
[EDIT]
Here is what I've done :
Create a project named "RealProject" which contains all code (including resources files)
Create a project named "LinkedProject" which contains nothing (I deleted all files into it and modify the csproj file as the following :
<ItemGroup>
<Compile Include="..\RealProject\**\*.cs" />
</ItemGroup>
So in LinkedProject directory I've only :
[Directory] bin
[Directory] obj
[File ] LinkedProject.csproj
The whole LinkedProject uses the RealProject files, it's just a different configuration build (see here to know why : C# - Code compiler for .NET & CF.NET )
Once in that configuration, I've no access to the resources files from the RealProject ...
If you need screens or more detailed explanation, just ask.
[EDIT]
With this code, it works, Resource manager isn't loaded on the good Assembly name, but it should exists a better solution !!!
Assembly ass = Assembly.ReflectionOnlyLoadFrom(#"..\..\..\RealProject\bin\Debug\RealProject.dll");
ResourceManager manager = new ResourceManager("RealProject.Properties.Resources", ass);
[Solution]
Things to check :
The LinkedProject as the same
namespace as the RealProject
Add Resources as links
Clean up all your solution
Rebuild it
Test !

Try to add the resource file as a link to the other two projects and make sure the namespaces as defined in the project file is the same.

Try adding existing file in other projects as a link.

The problem with sharing resources files between different projects is that the root namespace has to be the same in all the projects you use the same file in.
Or not.
You can get the root namespace at runtime in the *Resources.designer.cs file. Note the links in the comments to related answers. Make sure you commit this and keep an eye on it, the code-generator has a habit of overwriting it which would break its universality. I used the xml doc comments to remind me what's going on, if the code-gen obliterates it I'll see it in the commit diff.
/// <summary> Returns the cached ResourceManager instance used by this class. </summary>
/// <remarks> DO NOT commit any version of this file the tool overwrites, it will break it for other projects</remarks>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null))
{
// https://stackoverflow.com/a/1329631/492 https://stackoverflow.com/a/51978052/492
Assembly thisAssembly = typeof(/*thisClassName*/).Assembly;
// you need a class called "App", or just change the name on the next line to one you have in the root namespace
var apps = thisAssembly?.GetTypes().Where(t => t.Name == "App");
if (apps.Count() != 1)
throw new InvalidOperationException($"Too Many App classes. Count: {apps.Count()}, Apps: {apps}");
Type appType = apps.FirstOrDefault();
string ns = appType.Namespace;
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager($"{ns}.OtherNames.Spaces.thisClassName", typeof(thisClassName).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}

Related

How to output a file from a roslyn code analyzer?

I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.

Get assembly name at compile time and use it in code [duplicate]

Is there a way to find out the assembly name at design-time (i.e. not using reflection or runtime APIs such as System.Reflection.Assembly.GetEntryAssembly) from within Visual Studio?
The scenario requires a tool to get the assembly name that a Visual Studio project will eventually compile into.
This is like parsing the AssemblyName property of the .csproj - I am wondering if there are any APIs that can give this information reliably.
Please do not respond back with runtime APIs that use reflection - there is no assembly file present at the time I need the assembly name - just the metadata of the assembly in the csproj file.
if you are calling the tool via a post/pre-build event, this data is very easy to access.
Just go to the "project properties->Build Events" tab, then select either "edit pre-build" or "edit post-build", depending on when you want the tool to run. This should bring up an edit window with the ever helpful "Macros >>" button. Press this and you will be given a heap of macros to use and should be pretty much everything you need.
The "API" you could use is LINQ to XML after all the .csproj file is just xml. (and you can get the location of the .csproj file if you need from the solution file which for some reason is not XML but can be easily parsed)
You can use "TargetName" available in Macros for Post-build events. It will give you the assembly name for your project.
After a quick run through MSDN I found this article which might be a good start for some further research:
Accessing Project Type Specific Project, Project Item, and Configuration Properties
I think you will need to write some regular expression that will give you the value of "AssemblyTitle" attribute in AssemblyInfo.cs file.
Something like this:
public class Assembly
{
public static string GetTitle (string fileFullName) {
var contents = File.ReadAllText (fileFullName); //may raise exception if file doesn't exist
//regex string is: AssemblyTitle\x20*\(\x20*"(?<Title>.*)"\x20*\)
//loading from settings because it is annoying to type it in editor
var reg = new Regex (Settings.Default.Expression);
var match = reg.Match (contents);
var titleGroup = match.Groups["Title"];
return (match.Success && titleGroup.Success) ? titleGroup.Value : String.Empty;
}
}

Different resource files for different client deployments

I'm developing a WinForms application for 2 clients. The difference between the clients are only in branding: ClientA gets LogoA.png. ClientB gets LogoB.png. I need to ship the application to them as in installer and as zip file with all executables.
I'm thinking putting the images in different resource files and compile them as satellite assemblies and on the build server, when I produce zip-file and installer, I include only ResourceA for ClientA and ResourceB for ClientB. That is the plan, but I've never done this before.
The documentation says that resource files should be identified by language and culture codes. Both of my clients will run their machines in English (en-GB or en-US). I can ignore the recommendation and call the resources by the name of clients. But would they be picked up by the application? (taking there is only one resource file and machine culture does not match the resource culture code).
Is there a better solution for that?
p.s. I know about compiler directives, but it is making code hacky and dirty. Possibly, in the future, clients will have different text on the screens and that is the perfect case for the resources.
You can create a separate build configuration for each company. Then you can change the .csproj file to have msbuild tasks which will replace default resource file with chosen company resources, here is example how to check current configuration in msbuild.
<PropertyGroup Condition="'$(Configuration)' == 'CompanyABuild'">
//set resource to point to company A
</PropertGroup>
<PropertyGroup Condition="'$(Configuration)' == 'CompanyBBuild'">
//set resource to point to company B
</PropertGroup>
You can add to separate resource file one for clientA another one for clientB (ClientA.resx, Clinetb.resx). Then add a config entry in your app.config file with the name of the resource to use.
Then you need to create a wrapper class which will provide you resources depending on the config value, you need to use dynamic objects and resource managers here is a sample code:
class Program
{
static void Main(string[] args)
{
var res = new CompanyAResource();
var companyResources = new global::System.Resources.ResourceManager("ConsoleApplication1.CompanyAResource", typeof(CompanyAResource).Assembly);
dynamic resources = new DynamicResources(companyResources);
string name = resources.CompanyName;
Console.WriteLine(name);
}
}
public class DynamicResources : System.Dynamic.DynamicObject
{
private ResourceManager resources;
public DynamicResources(ResourceManager resources)
{
this.resources = resources;
}
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
result = this.resources.GetString(binder.Name);
return true;
}
}

How to get Current Project Directory path using C#

okay, here is the question. I have two projects one is C# Console and other is Class library.
I am accessing/calling Class library method from the console app.
There is a folder called Files within the class library project.
I need to get the path of the Class library's files folder but whenever I use
System.IO.Directory.GetCurrentDirectory();
and
Environment.CurrentDirectory;
it is giving me path of the Console project which I am using to call the method.
Above methods are giving me path like
C:\\ConsolePro\\bin\\Debug
but I need the path of Class library project
C:\\ClassLibPro\\bin\\Debug
Please advise
Once the code is compiled and running, 'Project Path' has no meaning. All you can determine are the file locations of the compiled assemblies. And you can only do what you are asking if your Console project references the built 'class library' DLL directly, rather than via a Project Reference.
Then, you can make use of Reflection to get Assembly paths like;
string path = Assembly.GetAssembly(typeof (SomeClassInOtherProject)).Location;
You should be able to use Directory.GetParent(Directory.GetCurrentDirectory()) a few times to get higher level directories and then add the path of the lib directory to the end of that.
I believe the problem is:
Since the Console project has the DLL file reference it is using DLL to call any methods.
At this time it is returning the class library projct's DLL location which is located in console project's bin directory and it doesn't know about the physical location of class library project.
so essentially it is returning the same project path. I will have to move both projects in same directory in order to solve this issue.
If you loading the class library from another assembly.
string Path = System.Reflection.Assembly.GetAssembly(typeof({LibraryClassName})).Location;
string PathToClassLibPro = Path.GetDirectoryName( Path);
Replace {LibraryClassName} with the class name of your library.
I hope I understand u corretly:
Path.GetDirectoryName(typeof(Foo.MyFooClass).Assembly.Location);
I would recommend one of two options.
If the files are small include them in the class library and stream them to a temp location when needed
Other option is to copy the files during the build to the output directory and use them that way. In cases of multiple shared projects it is best to have a common bin folder that you copy assemblies to and run from that location.
Despite i cant find a good solution i use this trick :
as long as you want to come back to your ideal path u should add Directory.GetParent() instead of ...
Directory.GetParent(...(Directory.GetParent(Directory.GetCurrentDirectory()).ToString()...).ToString()
I use the following approach to get the current project path at runtime:
public static class ProjectInfo {
public static string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
public static string projectPath = appDirectory.Substring(0, appDirectory.IndexOf("\\bin"));
}
I had this exact issue as well where I couldn't access the file in my namespace's bin/debug folder. My solution was to manipulate the string using Split() then construct a new string which is the absolute path to the json file I have in my namespace.
private static string GetFilePath()
{
const char Escape = '\\'; //can't have '\' by itself, it'll throw the "Newline in constant" error
string directory = Environment.CurrentDirectory;
string[] pathOccurences = directory.Split(Escape);
string pathToReturn = pathOccurences[0] + Escape; //prevents index out of bounds in upcoming loop
for(int i = 1; i < pathOccurences.Length; i++)
{
if (pathOccurences[i] != pathOccurences[i - 1]) //the project file name and the namespace file name are the same
pathToReturn += pathOccurences[i] + Escape;
else
pathToReturn += typeof(thisClass).Namespace + Escape; //In the one occurrence of the duplicate substring, I replace it with my class Namespace name
}
return pathToReturn + "yourFile.json";
}
I personally don't like this solution, but it was the only answer I could think of.

Could not find any resources appropriate for the specified culture or the neutral culture

I have created an assembly and later renamed it.
Then I started getting runtime errors when calling:
toolsMenuName = resourceManager.GetString(resourceName);
The resourceName variable is "enTools" at runtime.
Could not find any resources
appropriate for the specified culture
or the neutral culture. Make sure
"Jfc.TFSAddIn.CommandBar.resources"
was correctly embedded or linked into
assembly "Jfc.TFSAddIn" at compile
time, or that all the satellite
assemblies required are loadable and
fully signed.
The code:
string resourceName;
ResourceManager resourceManager = new ResourceManager("Jfc.TFSAddIn.CommandBar", Assembly.GetExecutingAssembly());
CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);
if(cultureInfo.TwoLetterISOLanguageName == "zh")
{
CultureInfo parentCultureInfo = cultureInfo.Parent;
resourceName = String.Concat(parentCultureInfo.Name, "Tools");
}
else
{
resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
}
toolsMenuName = resourceManager.GetString(resourceName); // EXCEPTION IS HERE
I can see the file CommandBar.resx included in the project, I can open it and can see the "enTools" string there. It seems that either resources are not included into assembly or resource are included but .NET cannot resolve the name.
I think simpler solution would be to create separate resources file for each language.
As far as this case is concerned check if the assembly containing resources has the default namespace set to the same text (Project->Properties->Default namespace; in VS)
Check as well if the resx file has a property BuildAction set to "Embedded resource"
Sounds similar to an issue we had. The namespace was incorrect in the resource file's designer. I fixed it by manually re-running the custom-tool on the resx file.
Right click your.resx, and click Run Custom Tool.
I'm sure you've already got the answer, but just in case:
You can view your ManifestResourceName by calling
System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames()
Check that Manifest name and your name in GetString() calling are identical.
Also, be sure you have correct namespace in designer.resx file:
namespace Jfc.TFSAddIn {
...
global::System.Resources.ResourceManager temp =
new global::System.Resources.ResourceManager(
"Jfc.TFSAddIn.CommandBar", typeof(CommandBar).Assembly);
...
}
Open resx file properties: "Build Action" should be "Embedded Resource"
For me, the source of the problem was naming the rex files starting with a number:
20160216_tranlation.resx
I had to add an underscore _ before the resx file name when calling GetGlobalResourceObject:
public static string getResource(string key)
{
return HttpContext.GetGlobalResourceObject("_20160216_tranlation", key).ToString();
}
I corrected the namespace in designer file (Resources.Designer.cs) in ResourceManager static property & it worked for me.
See the code below:
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XYZAssembly.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
I added a temporary class within my Form.cs while (testing || debugging) that caused this exception to be thrown. The Form.resx file (Name || Resource ID) was modified to the temporary class name instead of the Form class name. This caused the issue for me. I (corrected || alleviated) this by creating a separate file for my temporary class in the project.
One Solution is to change the property of resx file from content to Embedded Resource and Build it.Sure this time u vil get
I have encountered this issue in Xamarin.Forms, when I tried to the rename the project, the resources could not be loaded anymore with the same stated error text.
To fix the problem I had to modify the .csproj by a text editor, and change the logical name of the embedded resource.
<EmbeddedResource Include="Localization\TextResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TextResources.Designer.cs</LastGenOutput>
<LogicalName>YourNewNamespaceName.TextResources.resources</LogicalName>
<SubType>Designer</SubType>
</EmbeddedResource>
Also watch out for the autogenerated class when you rebuild it, the namespace stated in there might change.
Hope it helps someone that went into the same situation.
Got this error when I added a class ABOVE the partial form class in my Windows forms app.
It went away when I moved the class BELOW the partial form class.
This answer solved the problem for me! GetGlobalResourceObject

Categories