Warning
This project is currently in a development phase and not ready for production use. While we actively use these tools internally, our aim is to share and collaborate with the broader community to refine and enhance their capabilities. We are in the process of gradually open-sourcing the code, removing internal dependencies to make it universally applicable. At this stage, it serves as a source of inspiration and a basis for collaboration. We welcome feedback, suggestions, and contributions through pull requests.
If wish to use this project for your team, please contact us at hello@networg.com for a personalized onboarding experience and customization to meet your specific needs.
Caution
Only use this if you understand the standard platform customization capabilities. Using these templates with parameter combinations other than those documented here might generate invalid source code, which could still be importable to Dataverse. In some situations, this could cause your environment to become irreversibly corrupted.
The primary objective of this NuGet package is to help Power Platform developers scaffold Power Platform components using a code-first approach.
You can refer to a VS Code snippets file used by @TomProkop for conference demos.
# If you're using .NET CLI for the first time, you might need to set up nuget.org as a package source
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
# Install PowerShell 7+ to have "pwsh" executable present in your terminal
# To support running the templates cross-platform "pwsh" is used instead of "powershell.exe"
# You can use other installation methods
dotnet tool install --global PowerShell
# Install the template package to your machine
dotnet new install TALXIS.DevKit.Templates.DataverseNote
Template commands are designed to be run in the folder where *.*proj is located. Use --output parameter if your working directory is different.
Initialize a new empty solution:
dotnet new pp-solution `
--output "src/Solutions.DataModel" `
--PublisherName "tomas" `
--PublisherPrefix "tom" `
--allow-scripts yesCreate a new standard table:
dotnet new pp-entity `
--output "src/Solutions.DataModel" `
--Behavior New `
--PublisherPrefix "tom" `
--LogicalName "location" `
--LogicalNamePlural "locations" `
--DisplayName "Location" `
--DisplayNamePlural "Locations" `
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a new activity table:
dotnet new pp-entity `
--output "src/Solutions.DataModel" `
--EntityType "Activity" `
--Behavior "New" `
--PublisherPrefix "tom" `
--LogicalName "shiftevent" `
--LogicalNamePlural "shiftevents" `
--DisplayName "Shift Event" `
--DisplayNamePlural "Shift Events" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd an existing custom table to a solution:
dotnet new pp-entity `
--output "src/Solutions.UI" `
--Behavior "Existing" `
--PublisherPrefix "tom" `
--LogicalName "shiftevent" `
--DisplayName "Shift Event" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd an existing system table to a solution:
dotnet new pp-entity `
--output "src/Solutions.UI" `
--Behavior "Existing" `
--IsSystemEntity "true" `
--LogicalName "account" `
--DisplayName "Account" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd a whole number column to table:
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "tom_warehouseitem" `
--AttributeType "WholeNumber" `
--RequiredLevel "required" `
--PublisherPrefix "tom" `
--LogicalName "availablequantity" `
--DisplayName "Available Quantity" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd a lookup column to table:
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "tom_warehousetransaction" `
--AttributeType "Lookup" `
--RequiredLevel "required" `
--LogicalName "tom_itemid" `
--DisplayName "Item" `
--ReferencedEntityName "tom_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a global OptionSet:
dotnet new pp-optionset-global `
--output "src/Solutions.DataModel" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_paymentmethod" `
--DisplayName "Payment Method" `
--SolutionRootPath "Declarations" `
--OptionSetOptions "Visa,Mastercard,Cash" `
--allow-scripts yes
Add global OptionSet to the table:
```console
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "${publisherPrefix}_warehousetransaction" `
--AttributeType "OptionSet (Global)" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_paymentmethod" `
--DisplayName "Payment Method" `
--GlobalOptionSetType "Existing" `
--SolutionRootPath "Declarations" `
--allow-scripts yes
Create a local OptionSet:
```console
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--AttributeType "OptionSet (Local)" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_packagetype" `
--DisplayName "Package Type" `
--OptionSetOptions "Box,Bag,Envelope" `
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a model-driven app:
dotnet new pp-app-model `
--output "src/Solutions.UI" `
--PublisherPrefix "tom" `
--LogicalName "warehouseapp" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd a table to a model-driven app component:
dotnet new pp-app-model-component `
--output "src/Solutions.UI" `
--EntityLogicalName "tom_warehouseitem" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yesAdd an area to the sitemap:
dotnet new pp-sitemap-area `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yesAdd an group to the area:
dotnet new pp-sitemap-group `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yesAdd an subarea into the group:
dotnet new pp-sitemap-subarea `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--EntityLogicalName "tom_warehouseitem" `
--AppName "tom_warehouseapp" `,
--allow-scripts yesCreate a main form for a table:
dotnet new pp-entity-form `
--output "src/Solutions.UI" `
--FormType "main" `
--SolutionRootPath "Declarations" `
--EntitySchemaName "tom_warehouseitem" `
--allow-scripts yesForm Structure Hierarchy:
┌──────────────────────────────────────────────────────────┐
│ Form │
│ ├─ Tab │
│ ├─ Column │
│ ├─ Section │
│ ├─ Row │
│ ├─ Cell │
│ └─ Control │
└──────────────────────────────────────────────────────────┘
Create a new tab in the form:
dotnet new pp-form-tab `
--output "src/Solutions.UI" `
--FormType "main" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--RemoveDefaultTab "True" `
--allow-scripts yesCreate a new column in the specific tab:
dotnet new pp-form-column `
--output "src/Solutions.UI" `
--FormType "main" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnWidth "75"
--allow-scripts yesCreate a new section in the specific column:
dotnet new pp-form-section `
--output "src/Solutions.UI" `
--FormType "main" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionName "GENERAL"
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a new row in the specific section:
dotnet new pp-form-row `
--output "src/Solutions.UI" `
--FormType "main" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1 `
--allow-scripts yesCreate a new cell in the specific row:
dotnet new pp-form-cell `
--output "src/Solutions.UI" `
--RowIndex "1" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1 `
--FormType "main" `
--DisplayName "Name" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a new control in the specific cell:
dotnet new pp-form-cell-control `
--output "src/Solutions.UI" `
--AttributeType "Text" `
--RowIndex "1" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1
--AttributeLogicalName "${publisherPrefix}_name" `
--FormType "main" `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yesCreate a security role:
dotnet new pp-security-role `
--output "src/Solutions.Security" `
--SolutionRootPath "Declarations" `
--rolename "Warehouse Manager" `
--allow-scripts yesAdd privileges to a security role:
dotnet new pp-security-role-privilege `
--output "src/Solutions.Security" `
--SolutionRootPath "Declarations" `
--EntitySchemaName "tom_warehouseitem" `
--rolename "Warehouse Manager" `
--PrivilegeTypeAndLevel "[{ PrivilegeType: Read, Level: Global }, { PrivilegeType: Write, Level: Global }]" `
--allow-scripts yesInitialize a new plugin:
dotnet new pp-plugin `
--output "src/Plugins.Warehouse" `
--PublisherName "tomas" `
--SigningKeyFilePath "PluginKey.snk" `
--Company "NETWORG" `
--allow-scripts yesAdd new assembly:
dotnet new pp-plugin-assembly-step `
--output "src/Solutions.Logic" `
--PluginProjectRootPath "..\\Plugins.Warehouse" `
--SolutionRootPath "Declarations" `
--allow-scripts yesAdd new step to the assembly:
dotnet new pp-plugin-assembly-step `
--output "src/Solutions.Logic" `
--PrimaryEntity "tom_warehousetransaction" `
--PluginProjectName "Plugins.Warehouse" `
--PluginName "ValidateWarehouseTransactionPlugin" `
--Stage "Pre-validation" `
--SdkMessage "Create" `
--SolutionRootPath "Declarations" `
--FilteringAttributes "{tom_itemid, tom_quantity}" `
--AssemblyId "GUID to identifying your assembly" `
--allow-scripts yesTip
You can add component schema validation to your build process using Power Platform MSBuild targets.
The Templates Builder is a utility tool that helps you create custom .NET templates specifically for TALXIS custom controls. This tool reads a ControlManifest.Input.xml file and generates the necessary template files and configuration needed for a .NET template.
The Templates Builder reads a ControlManifest.Input.xml file (which contains the custom control definition) and generates the necessary template files and configuration needed for a .NET template. This allows you to:
- Convert existing custom controls into reusable templates
- Standardize custom control creation across your organization
- Automate the scaffolding of custom control structures
- Maintain consistency in custom control naming and structure
./TALXIS.DevKit.Templates.Builder.exe
--PathToTheImputXmlFile "ControlManifest.Input.xml" `
--ResultFolderPath "C:\result" `
--TemplateName "New Custom Control Template" `
--TemplateIdentity "New.Custom.Control.Template" `
--TemplateShortName "pp-control-custom-template"--PathToTheImputXmlFile[Required]: Path to theControlManifest.Input.xmlfile containing the custom control definition--ResultFolderPath: Directory where the generated template files will be created--TemplateName: Display name for the new template--TemplateIdentity: Unique identifier for the template (used in template.json)--TemplateShortName: Short name used when invoking the template withdotnet new
A .NET project template for building Dataverse script libraries with TypeScript. It scaffolds a net462 class library project that executes an npm/TypeScript build during MSBuild and outputs a single AMD bundle for use as a web resource.
- A .NET SDK project targeting
net462 - TypeScript workspace under
TS/with:tsconfig.jsonconfigured to emit a single bundle toTS/build/<LibraryName>.jspackage.jsonwithtypescriptand@types/xrm- npm scripts:
buildandstart(watch)
- MSBuild target that runs
npm installandnpm run buildautomatically onBuild
- .NET SDK 6+ (
dotnet --version) - Node.js and npm (
node -v,npm -v)
dotnet new pp-script-library -n UI.Scripts --LibraryName MyCompany.Scripts- Build with MSBuild (this will run npm automatically):
dotnet build
- Develop with watch mode (run inside
TS/):Then build the .NET project (optional) to copy outputs to the project output directory.npm install npm run start
After building, you will find:
TS/build/<LibraryName>.jsTS/build/<LibraryName>.js.mapTS/build/<LibraryName>.d.ts
- Upload
TS/build/<LibraryName>.jsas a Script (JavaScript) Web Resource.
- If
npmis not found duringdotnet build, ensure Node.js is installed and on PATH. - To force a clean TypeScript build:
cd TS rm -rf node_modules build npm install npm run build - If watch mode does not re-emit, verify
tsconfig.jsonpaths and that files are saved.
This template creates a test project for Power Platform JavaScript/TypeScript web resources using Jest. It provides a complete testing infrastructure with Xrm API mocks, helper functions, and integration with .NET test framework.
The pp-test-script template generates a test project configured for testing Dataverse web resources (form scripts, ribbon commands, etc.). It includes:
- Jest test framework with jsdom environment
- Xrm API mocks for Dataverse client-side API
- Helper functions for creating test objects (forms, attributes, controls)
- Web resource loader utility for testing your scripts
- .NET project integration for running tests via
dotnet test - Automatic npm package installation
Create a script test project:
dotnet new pp-test-script `
--output "tests/Script.Tests" `
--ScriptTestProjectName "Script.Tests" `
--ScriptLibraryPath "../src/Scripts.Warehouse" `
--allow-scripts yesThe template creates:
- .NET Test Project - A .NET 8.0 project confiured to run Jest tests via
dotnet test - Jest Confiuration -
jest.config.jsconfigured for jsdom environment - Packae Configuration -
package.jsonwith Jest dependencies - jest-core Directory - Reusable core library containing:
- Xrm API mocks (
setupXrm.js) - Helper functions (
helpers.js) - Main export (
index.js)
- Xrm API mocks (
- Tests Directory - Sample test structure with utilities
- Web Resource Loader - Utility for loading and testing web resources
Script.Tests/
├── jest-core/
│ ├── index.js # Main export for jest-core
│ ├── setupXrm.js # Xrm API mock setup
│ ├── helpers.js # Helper functions for test objects
│ └── package.json # jest-core package definition
├── tests/
│ └── utils/
│ └── loadWebRes.js # Web resource loader utility
├── jest.config.js # Jest configuration
├── package.json # Project npm dependencies
└── Script.Tests.csproj # .NET project file
Create a test file in the tests directory (e.g., tests/myScript.test.js):
const { setupXrm, resetXrmMocks, makeForm, makeAttr, makeControl } = require('../jest-core');
const { loadWebResource } = require('./utils/loadWebRes');
describe('My Form Script', () => {
beforeEach(() => {
setupXrm();
});
afterEach(() => {
resetXrmMocks();
});
test('should set field value on form load', () => {
// Arrange
const nameAttr = makeAttr('');
const nameControl = makeControl();
const formContext = makeForm(
{ name: nameAttr },
{ name: nameControl }
);
// Load your web resource
const webRes = loadWebResource('path/to/your/script.js');
// Act - Call your function
webRes.onFormLoad(formContext);
// Assert
expect(nameAttr.setValue).toHaveBeenCalledWith('Default Value');
});
});- FakeXrmEasy v2 documentation:
https://dynamicsvalue.github.io/fake-xrm-easy-docs/
FakeXrmEasyTestBase.cs: a base class that:- creates an
IXrmFakedContextviaMiddlewareBuilder(.AddCrud(),.UseCrud(),.UseMessages()); - sets the license via
SetLicense(...)(default isCommercial); - provides an
IOrganizationServicevia_context.GetOrganizationService().
- creates an
Inherit from the base class and use _context to seed data and _service to invoke the Organization Service.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;
namespace Plugins.Tests
{
[TestClass]
public class AccountPluginTests : FakeXrmEasyTestBase
{
[TestMethod]
public void Creates_account_successfully()
{
// Arrange: seed in-memory data
var account = new Entity("account")
{
Id = Guid.NewGuid(),
["name"] = "Test Account"
};
_context.Initialize(new List<Entity> { account });
// Act: use IOrganizationService from the base
var createdId = _service.Create(new Entity("contact"));
// Assert: verify with context storage
var created = _context.Data["contact"][createdId];
Assert.IsNotNull(created);
}
}
}- FakeXrmEasy license dependency: calling
SetLicense(...)is required before.Build(). If needed, replaceCommercialwithRPL_1_5orNonCommercialaccording to your usage terms. - Message executors dependency: if you need custom message executors, uncomment in the base class
.AddFakeMessageExecutors(typeof(FakeXrmEasyTestBase).Assembly)before.UseMessages().- Dependency: custom executors become available only after their assembly is added.
- Activation:
.UseMessages()enables the message pipeline.
We are happy to collaborate with developers and contributors interested in enhancing Power Platform development processes. If you have feedback, suggestions, or would like to contribute, please feel free to submit issues or pull requests.
Run the following terminal command in the folder src/Dataverse/templates:
dotnet new install "." --force
For further information or to discuss potential use cases for your team, please reach out to us at hello@networg.com.