Skip to content

net472: strong-named NPOI.Core cannot load unsigned ExcelFinancialFunctions — all financial functions throw FileLoadException #1830

Description

@ken-swyfft

NPOI Version

master (unreleased). Introduced by #1808, merged 2026-04-17 (0fc7bb25).

Released versions are not affected — 2.8.0 shipped 2026-04-06, before #1808, and does not list ExcelFinancialFunctions in any dependency group. This is a heads-up before the next release.

File Type

  • OTHER — build/packaging issue, not file-format specific

Issue Description

On net472, every financial function throws at runtime:

System.IO.FileLoadException : Could not load file or assembly
'ExcelFinancialFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
or one of its dependencies. A strongly-named assembly is required.
(Exception from HRESULT: 0x80131044)

main/NPOI.Core.csproj sets SignAssembly=true (../npoi.snk) and targets net472. #1808 added a PackageReference to ExcelFinancialFunctions 3.0.0, whose assembly is not strong-named (PublicKeyToken=null). .NET Framework forbids a strong-named assembly from binding to an unsigned dependency, so the load fails at runtime.

.NET Core / .NET 5+ dropped strong-name identity checks, which is why every net10.0 test assembly is green and only net472 fails.

This affects all net472 consumers regardless of whether they sign their own assemblies — the failing link is NPOI.CoreExcelFinancialFunctions, independent of the caller.

The reference has no PrivateAssets="all" Pack="false" (unlike the SkiaSharp entries alongside it), so it will flow into the published package as a real dependency for the .NETFramework4.7.2 group.

Reproduce Steps

Current CI already reproduces it — the windows-latest job on every master push fails with 34 net472 failures in NPOI.TestCases.dll, e.g. run 30535975609 (job 90849260174).

Equivalent minimal repro for a consumer: reference NPOI (built from master) from a net472 project and evaluate any financial formula, e.g. PMT(0.08/12, 20*12, 500).

Scope

Because CLR assembly loading is lazy, the failure only triggers when a financial function actually executes — ordinary formula evaluation on net472 is unaffected. The affected functions are the ones routed through the new engine:

PV FV PMT NPER RATE IRR MIRR XIRR XNPV NPV IPMT PPMT CUMIPMT CUMPRINC DB DDB SLN SYD VDB EFFECT NOMINAL

Why CI didn't flag it

Three things happened to combine, which may be worth addressing independently of the fix:

  1. The compile-time warning was suppressed in the same commit. 59d46e7a added <NoWarn>$(NoWarn);CS8002</NoWarn> to main/NPOI.Core.csproj in the same diff that added the package reference. CS8002 is "Referenced assembly does not have a strong name" — precisely this problem.
  2. The run-level conclusion hides the failing job. Integrate ExcelFinancialFunctions as backing engine for NPOI financial formula evaluation #1808's final PR run (24537756217) and the post-merge master push (24581212794) both report conclusion: success, while their windows-latest jobs are conclusion: failure. The red job never surfaced as a red check.
  3. windows-latest was already red before Integrate ExcelFinancialFunctions as backing engine for NPOI financial formula evaluation #1808, so the 34 new failures blended into existing noise. Per-job status on master runs 24281587961, 24291664324, 24484880085 and 24490962919 (2026-04-11 → 04-16) is windows-latest: failure in all four.

As an aside, #1808's last PR run failed on a different load error in the same netstandard2.0-on-net472 chain (System.Memory, Version=4.0.1.2). That was pinned in 52bc1204 and the PR merged; the strong-name failure only appeared post-merge, so no PR-level run ever displayed it.

Possible fixes

I don't have a strong opinion on which is right for the project, but the options seem to be:

  1. Exclude the reference on net472 and keep NPOI's previous managed implementations for that TFM (Condition="'$(TargetFramework)' != 'net472'"), guarding the call sites.
  2. Get a strong-named ExcelFinancialFunctions. Upstream is netstandard2.0-only and was last published 2021-12-07, so this looks unlikely without a fork. (Note the project already uses MathNet.Numerics.Signed rather than plain MathNet for the same reason.)
  3. Drop SignAssembly for net472 — probably a non-starter, since it would break existing signed consumers.
  4. Drop net472 from NPOI.Core's TargetFrameworks.

Whichever route, re-enabling CS8002 (or at least scoping the NoWarn narrowly) would prevent a recurrence, and making the windows-latest job's status visible at the run level would help the next case surface on the PR rather than after merge.


Filed by Claude (AI assistant) on behalf of @ken-swyfft. All run IDs, commit SHAs and package metadata above were verified against the GitHub and NuGet APIs; happy to provide the underlying log excerpts.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions