This page contains the steps to build and run the Syncfusion Toolkit for Blazor repository from source. If you are looking to build apps with the Syncfusion Toolkit for Blazor, please head over to the links in the README to get started.
- Install Visual Studio 2022 (v17.10 or newer) with ASP.NET and web development workload.
- Install .NET 8 SDK.
- Clone the repository:
git clone https://github.com/syncfusion/blazor-toolkit.git cd blazor-toolkit
- Open a command prompt/terminal window.
- Navigate to the location of your cloned
syncfusion/blazor-toolkitrepo. - Run these commands:
dotnet restore dotnet build ./Syncfusion.Blazor.Toolkit.slnx
If you want to mimic what .github/workflows/nuget-publish.yml does on a release runner, pass -p:ContinuousIntegrationBuild=true so SourceLink and the package hash match what CI produces:
dotnet restore src/Syncfusion.Blazor.Toolkit.csproj -p:ContinuousIntegrationBuild=true
dotnet build src/Syncfusion.Blazor.Toolkit.csproj -c Release --no-restore -p:ContinuousIntegrationBuild=true
dotnet pack src/Syncfusion.Blazor.Toolkit.csproj -c Release --no-build -o nupkg -p:ContinuousIntegrationBuild=true
Note:
dotnet packtriggers aBeforeBuildtarget that runsnpm installandgulp blazor-toolkit-themesifsrc/wwwroot/styles/fluent.min.cssis absent. Make sure Node.js (LTS) is onPATH. The release workflow installs Node 22 explicitly to handle this.
- Open the
samples/Blazor.Toolkit.Samples.slnxfile in Visual Studio. - Set the desired sample project as the startup project and run.
As a general rule:
├── samples
│ ├── Blazor.Toolkit.Samples
│ ├── Blazor.Toolkit.Samples.Client
- Blazor.Toolkit.Samples: Full gallery sample with all of the controls and features of the Syncfusion Blazor Toolkit.
- Blazor.Toolkit.Samples.Client: Client-side Blazor WebAssembly sample project.
The threat model and security posture are reviewed on a structured schedule:
| Trigger | Activity |
|---|---|
Every major release (x.0.0) |
Full review of THREAT-MODEL.md: reassess all threats, verify mitigations, update or re-ratify the accepted-risks table. |
Every minor release (x.y.0) |
Review the accepted-risks table; escalate any entry whose circumstances have changed. |
| Monthly servicing | Dependency review and CVE triage per SECURITY.md §3.3. |
The outcome of each structured review is recorded by updating the "Security Self-Attestation" date in SECURITY.md and the accepted-risks table in THREAT-MODEL.md.
The project follows Semantic Versioning 2.0.0. Release history lives on the GitHub release page:
Release history: https://github.com/syncfusion/blazor-toolkit/releases
Every published tag has a release page on that URL that enumerates the added, changed, deprecated, and removed APIs for that version. Consumers should subscribe to the GitHub Releases feed (or watch the repo's releases-only notifications) to be notified of new versions.
The short version of the policy is:
- Major (
x.0.0) — breaking public-API changes; the public surface is re-baselined inPublicAPI.Shipped.txt. - Minor (
x.y.0) — backwards-compatible additions only. - Patch (
x.y.z) — backwards-compatible bug fixes only.
Until the project ships a 1.0.0 release, the minor digit may
include breaking changes per SemVer §4. The currently shipped
version is v1.0.1.
APIs marked [Obsolete] are retained for at least two minor
releases before removal. APIs marked [Experimental] are not
covered by the SemVer compatibility promise and may change in any
release.
The numbers below correspond to the readiness-defect list reviewed on 2026-09-06. Per current policy, NuGet package signing and publishing are performed manually by the release maintainer; no signing or publishing automation exists in this public repository and none must be added.
RepositoryCommit is not declared as a literal in
src/Syncfusion.Blazor.Toolkit.csproj. It is populated automatically
during dotnet pack by:
Microsoft.SourceLink.GitHubreading the local.git/HEADand storing the SHA inSourceRevisionId; andDirectory.Build.propsdefaultingRepositoryCommitto$(SourceRevisionId)(which the .NET SDK emits into the.nuspec<repository>element).
The maintainer must run dotnet pack from a clone whose HEAD
matches the on-main tag-candidate commit. Verification before sign:
git cat-file -e <SHA>^{commit} \
|| { echo "ERROR: <SHA> is not on main"; exit 1; }The <RepositoryCommit> field on the produced .nuspec is never a
literal in the repository; it is populated automatically at dotnet pack
time via Microsoft.SourceLink.GitHub + Directory.Build.props. The
release maintainer MUST run the following checklist on every candidate
build, before triggering Manual NuGet sign and publish (PI-01, PI-02)
below.
-
Confirm the public commit SHA on disk matches the on-
maintag-candidate commit.git rev-parse HEAD # local SHA git ls-remote https://github.com/syncfusion/blazor-toolkit.git HEAD \ | awk '{print $1}' # upstream SHA test "$LOCAL_SHA" = "$UPSTREAM_SHA" \ || { echo "ERROR: HEAD is not on main"; exit 1; }
-
Confirm
SourceRevisionId(set by SourceLink duringdotnet restoreforEmbedUntrackedSources=true) equals the local SHA.dotnet restore src/Syncfusion.Blazor.Toolkit.csproj git rev-parse HEAD # local SHA — must match # SourceLink injects the SHA into the assembly metadata; the # .nupkg's <repository> element will reflect it after pack.
-
Smoke-pack the unsigned
.nupkgper TFM and inspect the<repository>element.dotnet pack src/Syncfusion.Blazor.Toolkit.csproj \ -c Release \ -p:ContinuousIntegrationBuild=true \ -o ./verify-pkg unzip -p verify-pkg/Syncfusion.Blazor.Toolkit.<Version>.nupkg \ Syncfusion.Blazor.Toolkit.nuspec \ | grep -E '<repository ' # Expected: an XML element whose 'commit' attribute equals the local SHA.
-
Record the SHA + branch on the private release ticket. This is the same SHA the CI
packjob echoes (PACK_REPO_COMMIT=${github.event.pull_request.head.sha || github.sha}); the on-disk SHA MUST match it. If it does not, abort the release — do not ad-hoc re-pack, do not bypass.Directory.Build.props—RepositoryCommitdefault + branch resolution (<SourceRevision>→<RepositoryCommit>)src/Syncfusion.Blazor.Toolkit.csproj—<RepositoryUrl>,<EmbedUntrackedSources>true</EmbedUntrackedSources>,<PublishRepositoryUrl>true</PublishRepositoryUrl>.github/workflows/ci.yml—packjob smoke-packs an unsigned.nupkgper TFM to confirm the wire-up
Shipped assemblies are strong-name signed by the release maintainer
as part of the manual sign-and-publish procedure. The public
repository contains no strong-name key material, no SignAssembly=true
directive, and no AssemblyOriginatorKeyFile value — by policy.
Public CI does not import, reference, or attempt to use any signing
artefact. Consumers can therefore trust that the absence of automated
signing in CI is deliberate and that a signed .nupkg is the
result of the manual procedure in §Manual NuGet sign and publish
below.
- Accepted Risk AR-1 in THREAT-MODEL.md.
The inner DLLs are not Authenticode signed (NotSigned). The outer
.nupkg is signed manually (primary + counter) by the release
maintainer as part of the same manual sign-and-publish procedure. Public
CI does not generate, sign, or modify a .nupkg for publication.
- Accepted Risk AR-2 in THREAT-MODEL.md.
Every Sf* component parameter whose XML documentation describes the
parameter as required must also carry [EditorRequired]. The
tests/Syncfusion.Blazor.Toolkit.BUnitTest/Base/EditorRequiredAttributeTests.cs
bUnit test enforces this by reflection over every public [Parameter]
in src/Components/ and asserts EditorRequiredAttribute presence
when the docs mark the parameter as required.
Global styles for the toolkit are namespaced under e-*. They are
shipped from src/wwwroot/styles/ and consumed via the
_content/Syncfusion.Blazor.Toolkit/styles static asset path. The
contract, including stable selectors and renaming policy, is defined
in src/wwwroot/styles/STYLE-CONTRACT.md.
A subset of components uses Blazor CSS isolation for self-contained
styling (Border.razor.css under Spinner, SfNumericTextBox.razor.css);
those files are colocated with the component.
For every release, the release maintainer generates an SPDX 2.3 SBOM
and a CycloneDX 1.5 SBOM locally, from the signed .nupkg,
and attaches both to the GitHub release as release assets. This step
is part of the manual process described under D8 / D9. Public CI does
not upload any SBOM.
- Accepted Risk AR-5 in THREAT-MODEL.md.
Every PR runs restore → build → bUnit → Playwright → eslint → xss →
vulnerability scan → pack for .NET 8/9/10. The pack job
produces an unsigned .nupkg artifact labelled
unsigned-pkg-<tfm> for human review only. Pack must succeed with
RepositoryCommit=<PR head SHA> before the summary job reports
success. The artifact is not the publication candidate and is not
intended to be pushed to nuget.org.
There is no .github/workflows/nuget-publish.yml, no signing tool
invocation, no SHA-256 hand-off, no STRONG_NAME_KEY_BASE64 secret
reference, and no no-secrets.yml sentinel. Adding any of these is
prohibited by the manual-signing policy. The repository boundary is
the maintainer's local machine running the manual procedure below.
- Accepted Risk AR-4 in THREAT-MODEL.md.
There is no automated immutable-artifact job in CI. The
tamper-evident control point is the maintainer's local pre-publish
verification: the maintainer runs sha256sum against the final
.nupkg and records the digest in the private release ticket before
executing dotnet nuget push. No public workflow participates in or
records this hand-off.
- Accepted Risk AR-4 in THREAT-MODEL.md.
This procedure is the canonical sign-and-publish process. It is performed locally by the Syncfusion release maintainer; no part of it runs on public CI.
- Pre-flight:
- Confirm the tag candidate SHA exists on
main:git cat-file -e <SHA>^{commit}. RepositoryCommitis populated automatically from the local.git/HEADviaMicrosoft.SourceLink.GitHub+Directory.Build.props(AR-3) — no manual substitution is required.
- Confirm the tag candidate SHA exists on
- Restore + Build + Pack (locally):
dotnet restore src/Syncfusion.Blazor.Toolkit.csproj dotnet build src/Syncfusion.Blazor.Toolkit.csproj -c Release --no-restore dotnet pack src/Syncfusion.Blazor.Toolkit.csproj -c Release --no-build -o nupkg -p:ContinuousIntegrationBuild=true - Strong-name sign the inner assemblies (PI-01, manual):
sn -R <assembly.dll> <strong-name.snk> # private key, never committed
- Re-pack with the signed DLLs:
dotnet pack src/Syncfusion.Blazor.Toolkit.csproj -c Release --no-build -o nupkg-final - Sign the
.nupkg(PI-02, manual):nuget sign nupkg-final/Syncfusion.Blazor.Toolkit.<version>.nupkg \ -CertificateSubjectName "<maintainer's code-signing subject>" \ -CertificateStore Location=CurrentUser;StoreName=My \ -TimestampserverUrl http://timestamp.digicert.com \ -HashAlgorithm SHA256
- Cross-sign with a counter-signing certificate as configured by
the maintainer:
nuget sign nupkg-final/Syncfusion.Blazor.Toolkit.<version>.nupkg \ -CertificateSubjectName "<counter-signing subject>" \ -CertificateStore Location=CurrentUser;StoreName=My
- Generate SBOMs locally from the signed
.nupkg:cd nupkg-final cyclonedx-dotnet -i Syncfusion.Blazor.Toolkit.<version>.nupkg -o bom.xml spdx-tools generate -i Syncfusion.Blazor.Toolkit.<version>.nupkg -o spdx.json
- Compute the audit-trail hashes:
sha256sum *.nupkg *.spdx.json bom.xml > SHA256SUMS
- Publish:
dotnet nuget push nupkg-final/Syncfusion.Blazor.Toolkit.<version>.nupkg \ -ApiKey $NUGET_API_KEY -Source https://api.nuget.org/v3/index.json
- Attach
*.spdx.json,bom.xml, andSHA256SUMSto the GitHub release page corresponding to the tag. The signed.nupkgitself is published only viadotnet nuget pushin step 9. - No csproj revert is required —
RepositoryCommitis automatic.
Directory.Build.props (repo root) sets defaults so a normal
dotnet pack run from a public commit automatically wires:
RepositoryUrl— set per packable project in its.csproj.RepositoryType—git, set per packable project.RepositoryCommit— auto-populated fromMicrosoft.SourceLink.GitHub'sSourceRevisionId(the local.git/HEADSHA). Override with-p:SourceRevision=<SHA>if the checkout context demands it.RepositoryBranch— resolved at build time from.git/HEADby the_ResolveRepositoryBranchtarget. Override with-p:RepositoryBranch=<name>for a hotfix-branch release.- SBOM —
Directory.Build.targetsrunsdotnet CycloneDXafter the kernel is compiled and packagesSyncfusion.Blazor.Toolkit.cdx.jsoninside the.nupkgat_sbom/cyclonedx/<tfm>/, plus a sibling copy atartifacts/sbom/<id>/<version>/<tfm>/.
Sources:
Directory.Build.props(defaults +_ResolveRepositoryBranch)Directory.Build.targets(_GenerateCycloneDxSbom,_PackCycloneDxSbomIntoNupkg)src/Syncfusion.Blazor.Toolkit.csproj(<RepositoryUrl>,<PublishRepositoryUrl>true,<EmbedUntrackedSources>true)
The maintainer must dotnet tool install --global CycloneDX once on
the release workstation before the first release.
There is intentionally no .github/workflows/nuget-publish.yml in
this repository. NuGet Trusted Publishing (OIDC) is not used for
this package. The reasons are:
- Manual signing — the
.nupkgis signed (primary + counter) on the release maintainer's local machine with an HSM-backed code signing identity. OIDC would have CI sign and push directly, which contradicts the published manual-signing policy (THREAT-MODEL.md AR-1, AR-2, AR-4). - Trusted identity in CI is broader — an OIDC trust relationship between GitHub Actions and nuget.org means that any workflow job with the right permissions can push a package impersonating this repo. The release workstation, in contrast, is the only place the code-signing certificate exists.
- Audit trail at the human boundary — the manual sign-and-publish procedure in §Manual NuGet sign and publish leaves the maintainer in control of the publish command. This is the audit boundary the threat model relies on.
- No secrets in CI — there is no
STRONG_NAME_KEY_BASE64, noNUGET_API_KEY, and no certificate in public CI. Adding them would directly conflict with AR-1, AR-2 and AR-4.
If the publish process is ever changed, it must be re-ratified through a new Accepted Risk entry and reflected in THREAT-MODEL.md before deployment.
Blazor offers three render modes: static SSR, Interactive Server, and Interactive WebAssembly. Each has a different security profile:
- Static SSR produces no JS interop and no SignalR circuit; it is
equivalent to a server-rendered page. Components run on the server
using only server-allowed APIs (
IHttpContextAccessor,NavigationManager, DI services markedScoped). There is no browser-exposed attack surface beyond the HTML payload. - Interactive Server operates over a SignalR circuit. Components retain access to all server-side APIs; the browser sees only diff-rendered DOM. State is server-resident and never sent to the browser other than through Blazor's diff protocol.
- Interactive WebAssembly runs code on the client. Components
in this mode MUST NOT call server-only APIs
(
HttpContextAccessor,IDbContextFactorywithout preloading,SignInManager, etc.) directly; doing so throws at runtime.
The codebase contains components that work in Interactive Server and
WebAssembly (e.g. SfButton, SfDialog, SfTooltip) and components
that are documented as static or server-only. The render mode is
declared per sample, not per component. The render-mode contract for
each component is documented in its XML doc-comment Remarks
section. Security implications are summarised in
RENDER-MODE-SECURITY.md.
The codebase uses a number of standard Blazor performance patterns:
- Virtualization is used in
SfChartfor large data sets. @keyis supplied on collection items inSfDropDownList-style inputs and in the dialog list rendering to keep DIff operations stable across re-renders.ShouldRenderoverrides are used in components where re-rendering is expensive (SfNumericTextBox,SfDatePicker).- JS interop is limited to one well-typed module surface
(
Base/SfJsInterop) to keep marshalling overhead low.
Performance regressions should be tracked with a perf-labelled bug.
src/Syncfusion.Blazor.Toolkit.csproj declares
<IsTrimmable>true</IsTrimmable> and <IsAotCompatible>true</IsAotCompatible>.
This means:
- A publish of a sample with
-p:PublishTrimmed=trueis run prior to every minor release. Any remaining ILLink warnings are either fixed or annotated in the Known analyzer / trim / AOT findings section. - A publish with
-p:PublishAot=trueis run prior to every major release againstsamples/Blazor.Toolkit.Samples.Client(the WebAssembly sample). Any remaining ILCompiler warnings are either fixed or annotated.
.github/workflows/ci.yml runs the bUnit component tests on the full
.NET matrix (8.0.x, 9.0.x, 10.0.x). Playwright validation runs on the
same triple. The unpacked WebAssembly sample smoke runs on .NET 10
only. Documented coverage and gaps are kept up to date in
TEST-MATRIX.md.
The compiler-analyzer configuration promotes only security-relevant
CA rules to errors (src/Syncfusion.Blazor.Toolkit.csproj,
<WarningsAsErrors>). The following pre-existing findings are
documented and not treated as defects:
| Finding | Source | Rationale |
|---|---|---|
| Finding | Source | Rationale |
| --- | --- | --- |
CA1014 / CA1017 (assembly attributes) |
applies to all TFM builds | The toolkit is a client-only Blazor component library; COM exposure and CLS-compliance enforcement are not consumer surfaces. Suppressed assembly-wide in src/Properties/GlobalSuppressions.cs. |
CA1305 (string IFormat) |
applies to error/log message formatting | Error/log message formatting in this codebase never substitutes user-controlled values. Suppressed assembly-wide in src/Properties/GlobalSuppressions.cs. |
CA1716 (identifier naming) |
applies consistently across contributors | Identifiers in the public Data namespace mirror .NET design-time naming (Dynamic, Value, etc.) required by the style guide. Suppressed assembly-wide in src/Properties/GlobalSuppressions.cs. |
Suppressions are added via src/Properties/GlobalSuppressions.cs so
they are visible to maintainers during code review and re-evaluated at
each major release. The file's per-rule rationale is duplicated in
Justification comments so each audit can be completed without
referring back to this table.
Residual ILLink warnings from -p:PublishTrimmed=true against
samples/Blazor.Toolkit.Samples and residual ILCompiler warnings
from -p:PublishAot=true against
samples/Blazor.Toolkit.Samples.Client are captured under the
trim-and-aot label in the issues queue. Each issue lists the rule
id, the symbol, and the planned remediation.