0

I am writing a custom C# console program targeting .net 7.0 and running on a Raspberry Pi 4B with Rasbian after being published self-contained targeting ARM64. There is 1 property of my configuration that seemingly refuses to deserialize automatically.

The LedDefinitions property deserializes perfectly when I debug it on my desktop (windows or wsl), but when I run in on the Pi, it won't deserialize and I get an empty array. I have hacked it with a PostConfigure setup like below, but I don't understand why I have to do this.

I have projects at my job that are setup similarly and they seem to work without any hackery, although they run inside of containers based on the mcr.microsoft.com/dotnet/aspnet:6.0 docker image.

Any insight as to what I need to look at or tweak is much appreciated.

Program.cs snippet

    var builder = Host.CreateDefaultBuilder(args)
        .ConfigureServices((bc, s) =>
        {
            s.Configure<LightingConfiguration>(bc.Configuration.GetSection("LightingConfiguration"));
            s.AddTransient<LightingExecutor>();
            s.PostConfigure<LightingConfiguration>(x =>
            {
                var b = bc.Configuration
                          .GetSection("LightingConfiguration")
                          .GetChildren()
                          .Where(y => y.Path.EndsWith("LedDefinitions"))
                          .SelectMany(y => y.GetChildren())
                          .ToArray();
    
                var propIndex  = typeof(LedRange).GetProperty("Index");
                var propLedStart  = typeof(LedRange).GetProperty("LedStart");
                var propLedEnd  = typeof(LedRange).GetProperty("LedEnd");
                var propPatternStart = typeof(LedRange).GetProperty("PatternStart");
    
                var newLedRanges = new List<LedRange>();
    
                foreach (var q in b)
                {
                    var obj = new LedRange();
    
                    foreach(var p in q.GetChildren())
                    {
                        var prop = p.Key switch
                        {
                            "Index" => propIndex,
                            "LedStart" => propLedStart,
                            "LedEnd" => propLedEnd,
                            "PatternStart" => propPatternStart,
                            _ => null,
                        };
    
                        prop?.SetValue(obj, int.Parse(p.Value));
                    }
                    newLedRanges.Add(obj);
                }
    
                x.LedDefinitions = newLedRanges.ToArray();
            });
        });
    
    var host = builder.Build();
    
    var config = host.Services.GetRequiredService<IOptionsMonitor<LightingConfiguration>>();

Configuration class

    class LightingConfiguration
    {
        public int NumberOfLEDs { get; init; }
        public bool DoFire { get; init; }
        public bool HorizontalFire { get; init; }

        public int PatternOffsetPerFrame { get; init; }
        public int TargetFps { get; init; }
        public string[] PatternToRepeat { get; init; }
        public LedRange[] LedDefinitions { get; set; }

        public int Cooling { get; init; }       // Rate at which the pixels cool off
        public int Sparks { get; init; }        // How many sparks will be attempted each frame
        public int SparkHeight { get; init; }   // If created, max height for a spark
        public int Sparking { get; init; }      // Probability of a spark each attempt
        public bool Reversed { get; init; }     // if reversed, we draw from 0 outwards
        public bool Mirrored { get; init; }     // if mirrored, we split and duplicate the drawing
    }

    class LedRange
    {
        public int Index { get; init; }
        public int LedStart { get; init; }
        public int LedEnd { get; init; }
        public int PatternStart { get; init; }
    }

Example appsettings.json file.

    {
      "LightingConfiguration": {
        "NumberOfLEDs": 60, //300
        "DoFire": false, //true
        "HorizontalFire": true, //true
    
        "PatternOffsetPerFrame": 1,
        "TargetFps": 5,
        "PatternToRepeat": [
          "00800000",
          "00008000",
          "00000080"
        ],
        "LedDefinitions": [
          {
            "Index": 0,
            "LedStart": 1,
            "LedEnd": 29,
            "PatternStart": 0
          },
          {
            "Index": 1,
            "LedStart": 31,
            "LedEnd": 58,
            "PatternStart": 0
          }
        ],
    
        "Cooling": 30,
        "Sparking": 100,
        "Sparks": 5,
        "SparkHeight": 300,
        "Reversed": false,
        "Mirrored": false
      }
    }
$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>disable</Nullable>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ProduceReferenceAssembly>False</ProduceReferenceAssembly>
    <Platforms>AnyCPU;ARM64</Platforms>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="appsettings.json" />
  </ItemGroup>

  <ItemGroup>
    <Content Include="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Iot.Device.Bindings" Version="3.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
  </ItemGroup>

</Project>

Publishing properties

Configuration: Debug | ARM64
Target Framework: net7.0
Deployment mode: Self-contained
Target runtime: linux-arm64
Options: Produce single file
         Trim unused code

1 Answer 1

0

I found a solution, but I am not sure why it is required. I changed the host builder to the below code.

            var builder = Host.CreateDefaultBuilder(args)
                .ConfigureServices((bc, s) =>
                {
                    s.Configure<LightingConfiguration>(bc.Configuration.GetSection("LightingConfiguration"));
                    s.Configure<LedRange>(bc.Configuration.GetSection("LightingConfiguration").GetSection("LedDefinitions"));
                    s.AddTransient<LightingExecutor>();
                });

I'm posting this as an answer in case someone else runs into this.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.