Skip to content

Hacky dotnet resource embedding while we wait for it to be available in Core

License

Notifications You must be signed in to change notification settings

RendleLabs/Embedder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Embedder

Resource embedding doesn't work properly in .NET Core. It's weird, it works OK when you build a project in VS2017.3, but not when you dotnet build, or dotnet msbuild, or even msbuild.exe.

So this is my super-hacky dotnet resource embedder while we wait for it to work properly, cross-platformly, command-linely in Core. It adds a dotnet embed command to the project, that embeds stuff in the project. Hence the name (hah, so much for Karlton (I'll do cache invalidation next)).

Nuget

RendleLabs.Embedder.Tools

It needs to be installed as a DotNetCliToolReference:

<ItemGroup>
  <DotNetCliToolReference Include="RendleLabs.Embedder.Tools" Version="1.0.1" />
</ItemGroup>

There is no runtime dependency. All the necessary code (all ~100 lines of it) is generated into your project.

Usage

You can see the package in use in my Shtik project. It seems to work.

Config

The tool is driven by a file, embedconfig.json, in the root of the project you want to use it in. The file specifies the namespace and directory to put embedded-resource classes in, and a list of files to be embedded, something like this:

{
  "namespace": "ExampleProject.Embedded",
  "Directory": "Embedded",
  "TextFileExtensions": ["txt","html","htm","js","cs","svg","json","xml"],
  "Classes": {
    "Web": {
      "normalize_css": "Web/normalize.css",
      "theme_css": "Web/theme.css",
      "fonts_fira_sans_v7_latin_700_woff": "Web/fonts/fira-sans-v7-latin-700.woff"
    }
  }
}

Running dotnet embed with this config would create a class ExampleProject.Embedded.Web in an Embedded directory, with properties called normalize_css, theme_css and fonts_fira_sans_v7_lating_700_woff.

The properties are all ArraySegment<byte>, for reasons that will be explained lower down. The class is declared partial so you can extend it.

You can generate a new embedconfig.json file by running dotnet embed --init directory .... All files in the specified directories will be added to the config, using the directory name as a class name.

Files with an extension included in the TextFileExtensions array will be treated as Text; all others will be treated as Binary. You can override this on a per-file basis by using a configuration object instead of a string, like this:

"random.txt": { "File": "random.txt", "FileType": "Binary" }

CLI

dotnet embed will generate the class(es), overwriting any existing file(s).

It will also add a couple of static extension classes to your project, adding a Utf8ToString method to ArraySegment<byte>, and a WriteAsync(ArraySegment<byte> bytes) method to Stream. This Extensions.cs file will only be written if it does not already exist.

At build time

You can make dotnet embed run on every build by adding it as a Target in your csproj file, like this:

<Target Name="DotnetEmbed" BeforeTargets="Build">
  <Exec Command="dotnet embed" />
</Target>

How it works

Proper .NET embedded resources are written as a binary chunk into the assembly itself, and I don't know how to do that, and the fact that it doesn't already work suggests it might be a bit complicated. So what I do is, I add all the bytes from all the files into a single, static byte[] array, and the properties return ArraySegment<byte> values that point to the bytes for a particular file (see, I told you it would be explained). Text files are encoded as UTF8 bytes, because internet.

If the generated byte array is less than 85000 bytes long, it is resized to be 85000 bytes long (using a static constructor), so that it will get put on the Large Object Heap, which is the best place to keep big byte arrays that are going to be around until your application crashes.

Now, this array is just created using a standard array initializer (i.e. new byte[] { 0, 10, ... }), which feels like it might not be the best way but I Googled for two minutes and I couldn't find a better one. Oh, and if it has to do the resize thing then I guess the original shorter array has to be cleaned up. I don't know if it adds a bunch of time to startup or anything like that, doesn't seem too bad with three text files and eight woff/woff2 font files. If you know something I don't know (about this exact thing, not just generally) then open an issue or send me a PR.

TODO

  • I should probably push the code from my PC, shouldn't I? Seems like it might help people. I'll do that tonight.
  • Some sort of indexer against properties (e.g. Web.Properties["theme_css"]) for dynamic purposes.
  • Build an aquatic mammal assault course for dynamic porpoises.
  • Additional CLI commands to do things like add more directories and so on.
  • Scrap it completely as soon as proper resource embedding becomes available.

Other notes

  • It's only available for .NET Core SDK 2.0, because that's what I'm using.
  • Opening the generated file with lots of bytes (~100K) in it drives Visual Studio 2017 Update 3 crazy. It just hangs. So don't do it. It's fine in VS Code, though (at least, in the x64 build it is).

About

Hacky dotnet resource embedding while we wait for it to be available in Core

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages