Dverse.Sdk
0.1.0-ci.100
dotnet add package Dverse.Sdk --version 0.1.0-ci.100
NuGet\Install-Package Dverse.Sdk -Version 0.1.0-ci.100
<PackageReference Include="Dverse.Sdk" Version="0.1.0-ci.100" />
<PackageVersion Include="Dverse.Sdk" Version="0.1.0-ci.100" />
<PackageReference Include="Dverse.Sdk" />
paket add Dverse.Sdk --version 0.1.0-ci.100
#r "nuget: Dverse.Sdk, 0.1.0-ci.100"
#:package Dverse.Sdk@0.1.0-ci.100
#addin nuget:?package=Dverse.Sdk&version=0.1.0-ci.100&prerelease
#tool nuget:?package=Dverse.Sdk&version=0.1.0-ci.100&prerelease
dverse
Declarative deployment + SDK for Microsoft Dataverse / Dynamics 365 plugins. dverse replaces spkl-style imperative registration with a Terraform-like plan/apply model, and replaces hand-written plugin boilerplate with source-generated code.
Dataverse online only. dverse authenticates via Microsoft Entra ID (
Azure.Identity) and always registers plugins in the sandbox — there is no on-premises / IFD support and none is planned for v1.
You write only this, in a plain SDK-style csproj:
using Dverse;
using Dverse.Model;
public partial class AccountPlugins
{
[Step(typeof(Account), Msg.Update, Stage.PreOperation,
Filtering = new[] { Account.Cols.CreditLimit })]
public void BlockCreditLimitDowngrade(IStepContext<Account> ctx)
{
if (ctx.Target.CreditLimit < ctx.PreImage.CreditLimit)
throw ctx.Fail("Credit limits can't be lowered.");
}
}
Then run dotnet build -t:Deploy. Everything else is generated or computed:
- the
IPluginentry class and the embedded context runtime (no base classes, no runtime DLL, no ILMerge), - the pre-image registration — columns inferred from your code (
ctx.PreImage.CreditLimitabove ⇒ a pre-image with exactlycreditlimit); escapes are compile errors, never a silent "all columns", - the registration manifest, diffed field-by-field against the live environment.
A no-op deploy performs zero writes in ~5 read round trips. dverse plan shows exactly what would change
(--json for machines, exit code 0 = clean / 2 = changes pending) — put it in CI and never let an
environment drift again.
Feature overview
| Feature | How |
|---|---|
| Early-bound models | entities declared once in dataverse.yaml; plan/apply refresh dataverse.snapshot.json automatically (with a schema diff); a source generator emits entity classes, Cols constants, and optionset enums in-memory — no generated .cs files on disk |
| Plugin steps | [Step(typeof(Account), Msg.Update, Stage.PreOperation, ...)] on a plain method taking IStepContext<Account> |
| Images | Inferred from ctx.PreImage.X / ctx.PostImage.X access; explicit [Image(...)] as the escape hatch |
| Custom APIs | [CustomApi("dverse_Reverse")] on a method with record request/response types; parameters sync automatically; typed callers are generated |
| Computed columns | [Derive(Account.Cols.Description)] on a pure static method — compiles to coalesced, dependency-ordered Create/Update steps with inferred filtering |
| Real-time rollups | [Rollup(Account.Cols.OpenBalance)] over Children<Contact> — the Where/aggregate expression compiles to one server-side FetchXML aggregate per affected parent, with reparenting (old + new parent) handled via an inferred pre-image |
| Web resources | a webresources: entry in dataverse.yaml — built (e.g. esbuild), content-hash diffed, published in one PublishXml; dverse webresources pull adopts existing ones |
| PCF controls | dverse pcf init scaffolds the standard pcf-scripts project; a pcf: entry in dataverse.yaml deploys every built control via a staged solution import — hash-diffed, so an unchanged control is zero writes |
| Code signing | dverse sign init creates the code-signing certificate; a signing: entry in dataverse.yaml Authenticode-signs every assembly before upload — in-process, cross-platform, no signtool, and deterministic so a no-op deploy stays zero writes |
| Managed identity | a managedIdentity: entry in dataverse.yaml creates and binds the managedidentity row so plugins reach Azure with no stored credentials (ctx.ManagedIdentity); dverse sign info prints the exact Azure federated credential to create for your certificate |
| Command-bar buttons | [Command(typeof(Account), "Recalculate", Confirm = …, Progress = …, Success = "{response.Count} done", Refresh = Refresh.Form)] next to a [CustomApi] — the button, its JavaScript and its registration are generated; conditions (EnableWhenNotEmpty, RequireSelection, …) compile to a classic ribbon rule automatically |
| Modern commands | a commands: entry in dataverse.yaml — appaction rows declared in YAML with a real field-level diff, so a changed label is one PATCH of one column; pruned by publisher prefix |
| Classic ribbons | a ribbons: entry in dataverse.yaml — committed RibbonDiffXml per entity plus the application ribbon, diffed per element against the platform's decomposed storage, deployed by staged solution import |
| Schema & data | a schema: entry declares tables, columns, option sets and relationships; seed: upserts reference rows; dverse schema adopt captures what already exists |
| Forms, views & apps | committed formxml / savedquery / app-module fragments (dverse forms pull, dverse views pull), converged and published in the same single PublishXml |
| Deployment | dverse plan / dverse apply (or dotnet build -t:Plan / -t:Deploy): field-level diff, sha256 assembly-upload skip, stale components pruned automatically, source versions auto-bumped when content changes (assembly revision / PCF patch) |
| Guard rails | 42 compile-time diagnostics (DVERSE0xx): invalid stage/mode combos, pre-image on Create, filtering on unsupported messages, duplicate keys, impure derives, … |
Getting started
- New project: docs/getting-started.md
- Solution layout & multi-assembly: docs/solution-layout.md
- Migrating from spkl: docs/migrating-from-spkl.md
- Runtime API contract: docs/dverse-context-api-spec.md
- Planned work: GitHub issues
The five-minute version
# 1. install the CLI (or run it from source: dotnet run --project src/Dverse.Cli --)
dotnet tool install --global Dverse.Cli --prerelease
dverse version # the installed package version, e.g. 0.1.0-ci.89 (+ the commit it was built from)
# 2. in your plugin repo: describe your environments (no secrets — this file is committed)
cat > dataverse.yaml <<'EOF'
default: dev
environments:
dev:
url: https://yourorg.crm.dynamics.com
solution: yoursolution
prefix: your
EOF
# 3. sign in once (device code; later runs are silent)
dverse auth login
dverse whoami
# 4. declare the entities once in dataverse.yaml:
# model:
# entities: [account, contact]
# plan/apply keep the model snapshot fresh automatically from then on
# 5. deploy (from the plugin project directory: builds, diffs, applies)
dverse apply
# ... or preview first / via MSBuild:
dverse plan
dotnet build -t:Deploy
Architecture in one paragraph
Plugins target net462 and reference exactly one package, Dverse.Sdk (attributes + source generators +
MSBuild targets). The generators are pure: metadata comes exclusively from the committed
dataverse.snapshot.json, so builds are reproducible and CI needs no Dataverse connection. The
registration manifest is emitted as an inactive preprocessor block — it contributes zero IL, so a
registration-only change (a rank, a filter column) never changes the assembly hash and never re-uploads
the DLL. The dverse CLI talks raw Dataverse Web API (no Microsoft.PowerPlatform.Dataverse.Client
dependency), reads the environment in a constant number of queries, canonicalizes both sides, and
PATCHes only changed columns. Matching precedence for existing steps: explicit Id → Key → natural key
(plugin type, message, entity, stage) when unique on both sides → name; ambiguity is a hard error,
never a heuristic.
Repository layout
src/Dverse.Cli/ the dverse CLI (net9.0, packs as dotnet tool Dverse.Cli)
src/Dverse.Sdk/ attributes (netstandard2.0) — the package plugins reference
src/Dverse.Sdk.Generators/ Roslyn incremental generators + diagnostics (ships inside Dverse.Sdk)
src/Dverse.Sdk.Targets/ MSBuild props/targets (ship inside Dverse.Sdk)
tests/ unit, generator, and live-environment integration tests
samples/TestPlugins/ end-to-end sample plugin project (project references, for development)
samples/pcf/CreditGauge/ sample PCF control (scaffolded with `dverse pcf init`, built output committed)
demos/NugetDemo/ standalone consumer demo — uses only the published NuGet packages
docs/ getting started, spkl migration, API spec, backlog
Building
dotnet build && dotnet test
Integration tests run only when DVERSE_TEST_URL is set and create only prefixed, torn-down test
components. CI publishes prerelease packages to NuGet on every push to main.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-ci.100 | 0 | 7/30/2026 |
| 0.1.0-ci.99 | 0 | 7/30/2026 |
| 0.1.0-ci.98 | 0 | 7/30/2026 |
| 0.1.0-ci.97 | 0 | 7/30/2026 |
| 0.1.0-ci.96 | 0 | 7/30/2026 |
| 0.1.0-ci.95 | 0 | 7/30/2026 |
| 0.1.0-ci.94 | 0 | 7/30/2026 |
| 0.1.0-ci.93 | 34 | 7/29/2026 |
| 0.1.0-ci.92 | 36 | 7/28/2026 |
| 0.1.0-ci.91 | 38 | 7/28/2026 |
| 0.1.0-ci.90 | 35 | 7/28/2026 |
| 0.1.0-ci.89 | 38 | 7/27/2026 |
| 0.1.0-ci.88 | 41 | 7/27/2026 |
| 0.1.0-ci.87 | 35 | 7/27/2026 |
| 0.1.0-ci.86 | 39 | 7/27/2026 |
| 0.1.0-ci.85 | 46 | 7/27/2026 |
| 0.1.0-ci.84 | 47 | 7/27/2026 |
| 0.1.0-ci.83 | 43 | 7/27/2026 |
| 0.1.0-ci.82 | 53 | 7/27/2026 |
| 0.1.0-ci.81 | 40 | 7/27/2026 |