Jump to content

c# Copy Embeded Resource File to disk ?


mickle026

Recommended Posts

mickle026

I am trying to copy an embeded resource file to disk

The file is an embeded resource, type text file called Search.php.

My Resource file is called MyEmbeds.resx, the php text file is saved in the resource simply as Search, MyEmbeds.Search

I am trying

            using (Stream output = File.Create(Path.Combine(config.CreatePersonApiServerString, "Search.php")))
            {
                Assembly.GetExecutingAssembly().GetManifestResourceStream(MyEmbeds.Search).CopyTo(output);
            }

My file is created so the output path is correct but it has no contents so the resource path must be the problem.  ie the problem must be where its looking for the resource.

Assembly.GetExecutingAssembly()

So with this in mind as I might be getting the refering assembly I tried

Assembly.GetAssembly(GetType()).GetManifestResourceStream(MyEmbeds.Search).CopyTo(output);

And my output is still blank.

 

What is the correct path to the assembly file of a plugin?

 

Link to comment
Share on other sites

I know how to do that, I do that all the time.

I'm at work so I do t have the exact code which I can post later.

But, get the embedded resource stream and CopyTo FileStream with your path data filled in.

But yeah, FileStream and make sure you dispose.

Also make sure your reflected namespace is pointing to the proper embedded resource.

I can post exactly what you have to later if there is still a problem. 👍

Link to comment
Share on other sites

mickle026
46 minutes ago, Luke said:

If there's an exception in the server log that might clue you in to the problem.

There's no errors in the logfile 😕

@chef, I'm at work myself now but if you can point me to the correct path, that would be brilliant, thanks.  I'm doing a using clause so it's automatically disposed of.

Quote

Also make sure your reflected namespace is pointing to the proper embedded resource

This is what I believe the problem is.

Link to comment
Share on other sites

pünktchen

Something like this?

using (Stream fileStream = File.Create(Path.Combine(config.CreatePersonApiServerString, "Search.php")))
{
    Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(MyEmbeds.Search);
    
    resourceStream.Seek(0, SeekOrigin.Begin);
    resourceStream.CopyTo(fileStream);
}

 

  • Thanks 1
Link to comment
Share on other sites

@mickle026 - you are mixing up two fundamentally different and separate ways of dealing with resources in C#

I will show you how to work with your resource in both cases

1. RESX Resources

You define RESX resource by adding a RESX file. You can add many different kinds of resources to a RESX files, like strings, images, icons, binary files and text files.

Strings are stored directly in the RESX file. Assuming you have MyResources.resx and added a string with the key 'MyString' and the value 'abc', you can access that value at runtime with MyResources.MyString, which will evaluate to 'abc'. 

But you can also add files from your project to a RESX resource file, and depending on the type of file, it will be available in the same way as a property of the resource class, either of type string or as a byte array (byte[]) in case of binary files.

In your case, you have done the latter. You have added the file Search.php to your MyEmbeds.resx file as text file. (I know that from your other snippets which wouldn't have compiled otherwise). And that means, that at runtime, The MyEmbeds.Search property already contains the contents of Search.php as string!.

So, the code you are looking for is

File.WriteAllText(Path.Combine(config.CreatePersonApiServerString, "Search.php"), MyEmbeds.Search);

Note: For this approach you do NOT need to set the Build Action of the file to "Embedded Resource" - you just leave it set to "None".

 

2. Embedded Resources

When you want to go the way of including the file as an Embedded Resource, 

  • There is no RESX file involved
  • You set the  Build Action of the file to "Embedded Resource"
  • It is important where the file is located within the project folder structure
    (because this determines the resource name)

The resource name of an embedded resource is determined by several components:

  • The project's "Default Namespace"
    e.g. Softworkz.MyGreatPlugin
  • The filename
    e.g. MyTextFile.txt
  • The relative folder path, separated by dots
    assuming the file's relative path in the project is Web\Resources\MyTextFile.txt
    it would be Web.Resources

Now, putting it all together, the resource path would be Softworkz.MyGreatPlugin.Web.Resources.MyTextFile.txt

And that's the path you need to use for calling GetManifestResourceStream().

But as developers, we should always avoid hardcoding any strings (as they might stop working when making changes or refactorings).
So, a better way would be to use an existing type for reference, ideally a type that resides in the same folder as your resource file (e.g. a class MyClass).
Then you would be able to do like this:

using (Stream output = File.Create(Path.Combine(config.CreatePersonApiServerString, "Search.php")))
{
    var type = typeof(MyClass);
    using (var source = type.Assembly.GetManifestResourceStream(type.Namespace + ".Search.php"))
    {
        source.CopyTo(output);
    }
}

Please note the Using for the resource stream to make sure it's getting closed after use.
Also note the prefix dot for the resource name building.

Edited by softworkz
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

mickle026
4 hours ago, softworkz said:

@mickle026 - you are mixing up two fundamentally different and separate ways of dealing with resources in C#

I will show you how to work with your resource in both cases

1. RESX Resources

You define RESX resource by adding a RESX file. You can add many different kinds of resources to a RESX files, like strings, images, icons, binary files and text files.

Strings are stored directly in the RESX file. Assuming you have MyResources.resx and added a string with the key 'MyString' and the value 'abc', you can access that value at runtime with MyResources.MyString, which will evaluate to 'abc'. 

But you can also add files from your project to a RESX resource file, and depending on the type of file, it will be available in the same way as a property of the resource class, either of type string or as a byte array (byte[]) in case of binary files.

In your case, you have done the latter. You have added the file Search.php to your MyEmbeds.resx file as text file. (I know that from your other snippets which wouldn't have compiled otherwise). And that means, that at runtime, The MyEmbeds.Search property already contains the contents of Search.php as string!.

So, the code you are looking for is

File.WriteAllText(Path.Combine(config.CreatePersonApiServerString, "Search.php"), MyEmbeds.Search);

Note: For this approach you do NOT need to set the Build Action of the file to "Embedded Resource" - you just leave it set to "None".

 

2. Embedded Resources

When you want to go the way of including the file as an Embedded Resource, 

  • There is no RESX file involved
  • You set the  Build Action of the file to "Embedded Resource"
  • It is important where the file is located within the project folder structure
    (because this determines the resource name)

The resource name of an embedded resource is determined by several components:

  • The project's "Default Namespace"
    e.g. Softworkz.MyGreatPlugin
  • The filename
    e.g. MyTextFile.txt
  • The relative folder path, separated by dots
    assuming the file's relative path in the project is Web\Resources\MyTextFile.txt
    it would be Web.Resources

Now, putting it all together, the resource path would be Softworkz.MyGreatPlugin.Web.Resources.MyTextFile.txt

And that's the path you need to use for calling GetManifestResourceStream().

But as developers, we should always avoid hardcoding any strings (as they might stop working when making changes or refactorings).
So, a better way would be to use an existing type for reference, ideally a type that resides in the same folder as your resource file (e.g. a class MyClass).
Then you would be able to do like this:

using (Stream output = File.Create(Path.Combine(config.CreatePersonApiServerString, "Search.php")))
{
    var type = typeof(MyClass);
    using (var source = type.Assembly.GetManifestResourceStream(type.Namespace + ".Search.php"))
    {
        source.CopyTo(output);
    }
}

Please note the Using for the resource stream to make sure it's getting closed after use.
Also note the prefix dot for the resource name building.

Thank you all for replying.

 

Thank you to @softworkzfor the very detailed reply.  I now understand what I was trying to do!

All I needed was simply this:

File.WriteAllText(Path.Combine(config.CreatePersonApiServerString, "Search.php"), MyEmbeds.Search);

As it was an embeded resource I was trying to reference it like the plugin does to the icon file, and then read the resx container. I didnt need to reference the path to the embeded resource , all i needed to do was write the file and use the RESX container and key as the string contents! (MyEmbeds.Search).

So simple when you know how!

I made this much harder than it needed to be.

 

Many thanks!

 

 

 

 

  • Like 1
Link to comment
Share on other sites

4 minutes ago, mickle026 said:

I made this much harder than it needed to be.

Yea, correct! 🙂 

4 minutes ago, mickle026 said:

Many thanks!

You're welcome!

Link to comment
Share on other sites

Check this out it 😃 It loads binaries that aren't included in emby in an  IServerEntryPoint (so on load).

I will share here, because I'm kind of proud of the code :)

 I call it AssembyLoader. I think it's cool.

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;

namespace Emby.MovieLens.ML_Net
{
    // ReSharper disable once InconsistentNaming
    // ReSharper disable once UnusedType.Global
    public class AssemblyManager : IServerEntryPoint
    {
        private static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
        private static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
        private static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
        
        private IApplicationPaths ApplicationPaths { get; }
        private ILogger Log { get; }

        public static AssemblyManager Instance { get; set; }
        public AssemblyManager(IApplicationPaths paths, ILogManager logManager)
        {
            ApplicationPaths = paths;
            Log = logManager.GetLogger(Plugin.Instance.Name);
            Instance = this;
        }
        
        private string GetMaxFactorizationNativeAssemblyName()
        {
            if (IsLinux())   return "libMatrixFactorizationNative.so";
            if (IsWindows()) return "MatrixFactorizationNative.dll";
            if (IsMacOS())   return "libMatrixFactorizationNative.dylib";

            throw new Exception("Unable to find MaxFactorization Library.");
        }

        //These are the runtimes. The only file we need to copy into Emby root (system folder)
        //But, it doesn't have to be there when emby loads because we are going to load all the other Link Librarys on command.
        //It only has to be there when the dynamic librarys are looking for it. Amazing!
        //The epitome of side loading!
        private static string MatrixFactorizationNativeEmbeddedResourceAssembly()
        {
            var location = string.Empty;
            var architecture = RuntimeInformation.OSArchitecture;
            if (IsLinux())
            {
                switch (architecture)
                {
                    case Architecture.X64   : location += "libMatrixFactorizationNative_Linux64.so";    break;
                    case Architecture.Arm   : location += "libMatrixFactorizationNative_LinuxArm.so";   break;
                    case Architecture.Arm64 : location += "libMatrixFactorizationNative_LinuxArm64.so"; break;
                }
            }
            if (IsWindows())
            {
                switch (architecture)
                {
                    case Architecture.X64 : location += "MatrixFactorizationNative_Win64.dll"; break;
                    case Architecture.X86 : location += "MatrixFactorizationNative_Win86.dll"; break;
                }
            }
            if (IsMacOS())
            {
                switch (architecture)
                {
                    case Architecture.X64   : location += "libMatrixFactorizationNative_OSX64.dylib";    break;
                    case Architecture.Arm64 : location += "libMatrixFactorizationNative_OSXARM64.dylib"; break;
                }
            }

            return location;

        }
        
        public void Dispose()
        {
            
        }

        public void Run()
        {
            // MaxFactorizationNative is a dependency for the ML.Net library.
            // It needs to live the Application Root (System folder).
            // Copy over the appropriate version for the appropriate OS.
            var maxFactorizationNativeLibraryName = GetMaxFactorizationNativeAssemblyName();
            Log.Info($"ML.Net loading dependency {maxFactorizationNativeLibraryName}");
            var matrixFactorizationNativeLibraryEmbeddedResourceStream = GetEmbeddedResourceStream(MatrixFactorizationNativeEmbeddedResourceAssembly());
            
            //Copy the resource into the system root.
            using (var fileStream = new FileStream(Path.Combine(ApplicationPaths.ProgramSystemPath, maxFactorizationNativeLibraryName), FileMode.Create, FileAccess.Write))
            {
                matrixFactorizationNativeLibraryEmbeddedResourceStream?.CopyTo(fileStream);
            }

            //This event will take care of loading the rest of the library we we need to run ML.Net at runtime.
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        }

        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            //Don't try and load items that are not in the Microsoft.ML namespace
            if (!args.Name.Contains(".ML") && !args.Name.Contains("Newtonsoft")) return null;
           
            //Don't load the assembly twice
            var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
            if (assembly != null) return assembly;

            Log.Info($"ML.Net loading assembly {args.Name} {args.RequestingAssembly}");

            var r1 = Assembly.GetExecutingAssembly().GetManifestResourceNames().FirstOrDefault(s => s.Contains(args.Name.Split(',')[0]));
            if (r1 is null) return null;
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(r1))
            {
                byte[] assemblyData = new byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }
        }

        public Stream GetEmbeddedResourceStream(string resourceName)
        {
            var assembly = Assembly.GetExecutingAssembly();
           //might not know the extact path, so as long as it ends with the name. What could go wrong???....
            var name = assembly.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith(resourceName)); 

            return GetType().Assembly.GetManifestResourceStream(name);
        }

        public async Task SaveEmbeddedResourceToFileAsync(Stream embeddedResourceStream, string output)
        {
            using (var fileStream = new FileStream(output, FileMode.Create, FileAccess.Write))
            {
                await embeddedResourceStream.CopyToAsync(fileStream);
            }
        }
       
    }
}

Isn't it cool? 😃

Edited by chef
  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...