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)).
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.
You can see the package in use in my Shtik project. It seems to work.
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" }
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.
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>
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.
- 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.
- 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).