Getting started

edit

Install the package

Add the Meziantou.Framework.EmbeddedConstantsGenerator package to the project that uses the files:

dotnet package add Meziantou.Framework.EmbeddedConstantsGenerator

The package contains an MSBuild task and the targets that run it. It is a development dependency: it adds nothing to the compiled assembly except the generated class, and the projects that reference yours do not get it.

List the files

Add an EmbeddedConstant item for each file, with its Kind:

<PropertyGroup>
  <EmbeddedConstantsNamespace>MyApp.Generated</EmbeddedConstantsNamespace>
  <EmbeddedConstantsClassName>EmbeddedFiles</EmbeddedConstantsClassName>
</PropertyGroup>

<ItemGroup>
  <EmbeddedConstant Include="Assets/hello.txt" Kind="Text" />
  <EmbeddedConstant Include="Assets/logo.bin" Kind="Binary" />
  <EmbeddedConstant Include="Assets/config.json" Kind="Both" Name="ConfigJson" />
</ItemGroup>
KindGenerated members
TextA const string named <Name>Text.
BinaryA ReadOnlySpan<byte> property named <Name>Bytes.
BothBoth members.

<Name> is the file name without its extension, in PascalCase (hello.txt gives HelloText), or the value of the Name metadata when it is set (ConfigJson). The two properties are optional: without them, the class is EmbeddedConstants in the root namespace of the project. See Configuration.

Build and use the members

The class is generated just before the compiler runs, so the members are available as soon as the project builds:

using MyApp.Generated;

namespace MyApp;

public static class Assets
{
    public static string Greeting => EmbeddedFiles.HelloText;

    public static byte[] GetLogo() => EmbeddedFiles.LogoBytes.ToArray();

    public static string ConfigurationJson => EmbeddedFiles.ConfigJsonText;

    public static int ConfigurationLength => EmbeddedFiles.ConfigJsonBytes.Length;
}

LogoBytes returns a ReadOnlySpan<byte> over data stored in the assembly: reading it does not allocate. Call ToArray() only when an API needs a byte[].

What is generated

For the files above (hello.txt contains Hello, "World"!, logo.bin 4 bytes and config.json a small JSON object), the build writes this file to the obj folder:

// <auto-generated/>
#nullable enable

namespace MyApp.Generated;

internal static partial class EmbeddedFiles
{
    public const string ConfigJsonText = "{ \"name\": \"demo\" }";

    public static global::System.ReadOnlySpan<byte> ConfigJsonBytes => new byte[]
    {
        0x7B, 0x20, 0x22, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x64, 0x65, 0x6D, 0x6F, 0x22,
        0x20, 0x7D
    };

    public const string HelloText = "Hello, \"World\"!";

    public static global::System.ReadOnlySpan<byte> LogoBytes => new byte[]
    {
        0x89, 0x50, 0x4E, 0x47
    };
}
  • The class is static partial, so you can add your own members to it in another file.
  • The members are sorted by name, and each text value is a C# string literal of the whole file.
  • The file is regenerated on every build of the project and deleted by dotnet clean. Do not edit it.

Next steps

  • Configuration: rename the class and the members, make them public, or write the file somewhere else.
  • Errors: what to do when the build fails with an MFECG error.