I started with a working app with static exported C# methods like
[SupportedOSPlatform("browser")] // suppress CA1416 "only supported on: 'browser'"
public static partial class Code
{
[JSExport]
public static string NameOf([JSMarshalAs<JSType.Any>] object entity)
=> ((IHasName)entity).Name;
}
Then I created a marshaling system for using C# conveniently in JS so you could write, for example,
import { DictionaryOfInt32_String } from 'csharp'; // generated TypeScript
let dict = DictionaryOfInt32_String.new(7); // .NET dictionary, capacity 7
dict.set(7, "se7en"); // dict[7] = "se7en";
console.log(dict.get(7));
But now I'm trying to separate my app's code from that of the marshaling system, so I created an extra JSMarshalerWasm library:
<!-- My app's .csproj for WebAssembly -->
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>obj/generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
...
<ProjectReference Include="..\JSMarshalCore\JSMarshalCore.csproj" />
<ProjectReference Include="..\JSMarshalerWasm\JSMarshalerWasm.csproj" />
<ProjectReference Include="..\JSMarshalerGenerator\JSMarshalerGenerator.csproj"
ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
</ItemGroup>
</Project>
<!-- JSMarshalerWasm library (meant to be compiled to WebAssembly) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<ProjectReference Include="..\JSMarshalCore\JSMarshalCore.csproj" />
</ItemGroup>
</Project>
The Marshaler project does not use Microsoft.NET.Sdk.WebAssembly because every
WebAssembly project demands a Main() method to prevent this error:
Program does not contain a static 'Main' method suitable for an entry point
(I not only don't call Main(), but couldn't even figure out how to call it.) But if I add a Main() method to both projects, the following error occurs:
C:\Program Files\dotnet\sdk\9.0.305\Sdks\Microsoft.NET.Sdk.StaticWebAssets\targets\Microsoft.NET.Sdk.StaticWebAssets.targets(616,5): error : Conflicting assets with the same target path '_framework/blazor.boot.json'. For assets 'Identity: C:\Dev\MyApp\Server\MyAppWasm\bin\Debug\net8.0\wwwroot_framework\blazor.boot.json, SourceType: Computed, SourceId: MyAppWasm, ContentRoot: C:\Dev\MyApp\Server\MyAppWasm\bin\Debug\net8.0\wwwroot, BasePath: /, RelativePath: _framework/blazor.boot.json, AssetKind: Build, AssetMode: All, AssetRole: Primary, AssetRole: , AssetRole: , RelatedAsset: , AssetTraitName: WasmResource, AssetTraitValue: manifest, Fingerprint: fmiu3i5gfl, Integrity: OYuLu5ui6DeTmqCHsz8viTMsr9s2gps4W8mqrbYrE4w=, FileLength: 22689, LastWriteTime: 11/5/2025 4:27:23 AM +00:00, CopyToOutputDirectory: PreserveNewest, CopyToPublishDirectory: Never, OriginalItemSpec: obj\Debug\net8.0\blazor.boot.json' and 'Identity: C:\Dev\MyApp\Server\JSMarshalerWasm\bin\Debug\net8.0\wwwroot_framework\blazor.boot.json, SourceType: Project, SourceId: JSMarshalerWasm, ContentRoot: C:\Dev\MyApp\Server\JSMarshalerWasm\bin\Debug\net8.0\wwwroot, BasePath: /, RelativePath: _framework/blazor.boot.json, AssetKind: Build, AssetMode: All, AssetRole: Primary, AssetRole: , AssetRole: , RelatedAsset: , AssetTraitName: WasmResource, AssetTraitValue: manifest, Fingerprint: qpeekuqssc, Integrity: /q1cS1sIQjh1Us8LmqZQEEOpnikjG+LDv6FJZIMtp+U=, FileLength: 17475, LastWriteTime: 11/5/2025 4:26:53 AM +00:00, CopyToOutputDirectory: PreserveNewest, CopyToPublishDirectory: Never, OriginalItemSpec: obj\Debug\net8.0\blazor.boot.json' from different projects.
So I used Microsoft.NET.Sdk instead, but then the JSExport attributes in the Marshaler don't work:
import { dotnet } from '_framework/dotnet.js';
...
...
const { setModuleImports, getAssemblyExports, getConfig, runMain } = await dotnet
.withApplicationArguments('start')
.create();
const config = getConfig();
DotNetExports = await getAssemblyExports(config.mainAssemblyName);
// RUNTIME ERROR:
// TypeError: can't access property "Marshaler", DotNetExports.JSMarshaling is undefined
Marshaler = DotNetExports.JSMarshaling.Marshaler;
Note: this is not a Blazor app, but AFAICT Microsoft didn't give a name to its .NET-in-WebAssembly system. If you're reading this Microsoft, please give your system a name so that people can discuss it properly. I see "blazor" in the error message above, but Blazor is a UI-in-C# system and I'm not doing any UI work in C#.