Best Practices pro Dataverse a Power Platform pluginy
Onboarding příručka pro zkušeného .NET Framework / C# developera, který začíná s Microsoft Dataverse a Power Platform.
1. Úvod do Dataverse pluginů a plugin pipeline
Dataverse plugin je .NET třída implementující Microsoft.Xrm.Sdk.IPlugin, která se spouští na serveru Dataverse jako reakce na zprávu platformy, typicky Create, Update, Delete, Retrieve, RetrieveMultiple, Associate, Disassociate nebo custom API/action.
Plugin není webový endpoint ani background service pod vaší kontrolou. Je to rozšíření serverové pipeline Dataverse. Dataverse vytvoří instanci plugin třídy, předá jí IServiceProvider a očekává rychlé, deterministické dokončení.
Základní pojmy:
- Assembly: zkompilované DLL obsahující jednu nebo více plugin tříd.
- Plugin Type: konkrétní třída implementující
IPlugin. - Step: registrace plugin typu na konkrétní message/entity/stage/mode.
- Pipeline: interní zpracování jedné operace v Dataverse.
- Execution Context: metadata o aktuálním spuštění, vstupní/výstupní parametry, uživatel, depth, correlation id atd.
- Organization Service: API klient pro čtení a zápis Dataverse dat uvnitř pluginu.
- Tracing Service: serverové logování dostupné v Plugin Trace Log.
Typická pipeline pro operaci jako Update:
- PreValidation: před hlavní validací a často mimo databázovou transakci.
- PreOperation: před uložením, uvnitř transakce.
- Main Operation: Dataverse provede vlastní změnu.
- PostOperation: po hlavní operaci, synchronně uvnitř transakce nebo asynchronně mimo ni.
Praktické pravidlo:
- Validace vstupu: PreValidation.
- Úprava hodnot před uložením: PreOperation.
- Reakce na již uloženou změnu: PostOperation.
- Těžká práce, integrace, e-maily, HTTP volání: raději async PostOperation nebo Power Automate/Azure Function.
2. Assembly/projekt pro pluginy
Doporučená struktura řešení
Příklad struktury pro týmový vývoj:
Contoso.Dataverse.Plugins.sln
src/
Contoso.Dataverse.Plugins/
Contoso.Dataverse.Plugins.csproj
PluginBase.cs
LocalPluginContext.cs
Account/
AccountPreOperationUpdatePlugin.cs
AccountPostOperationCreatePlugin.cs
Contact/
ContactPreValidationCreatePlugin.cs
Common/
EntityExtensions.cs
OptionSetValues.cs
Constants.cs
Contoso.Dataverse.Plugins.Tests/
Contoso.Dataverse.Plugins.Tests.csproj
Account/
AccountPreOperationUpdatePluginTests.cs
Doporučení:
- Jedna assembly pro související pluginy jedné domény nebo solution.
- Třídy pojmenovat podle entity, stage, message a účelu.
- Sdílené helpery držet malé a bez závislosti na globálním stavu.
- Pro větší projekty generovat early-bound třídy nebo konstanty pro entity/atributy.
- Testovat byznys logiku mimo samotný
IPlugin.Executewrapper.
Target framework
Dataverse pluginy jsou tradičně .NET Framework pluginy. Pro klasické plugin assembly používejte target framework podporovaný Dataverse a vaším toolingem, typicky:
<TargetFramework>net462</TargetFramework>
V praxi se často používá .NET Framework 4.6.2 pro kompatibilitu s Dataverse plugin runtime a balíkem Microsoft.CrmSdk.CoreAssemblies.
Poznámka: Microsoft postupně rozšiřuje Power Platform tooling a existují i modernější scénáře okolo Power Platform CLI, ale pro standardní server-side Dataverse pluginy počítejte s klasickým .NET Framework modelem, pokud dokumentace vaší verze/tenant scénáře neříká jinak.
NuGet reference
Minimální reference:
<ItemGroup>
<PackageReference Include="Microsoft.CrmSdk.CoreAssemblies" Version="9.*" />
</ItemGroup>
Často používané další balíky:
<ItemGroup>
<PackageReference Include="Microsoft.CrmSdk.XrmTooling.CoreAssembly" Version="9.*" PrivateAssets="All" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="FakeXrmEasy" Version="2.*" />
</ItemGroup>
Doporučení:
- Do plugin assembly nepřidávejte zbytečné externí knihovny.
- Pokud musíte použít externí knihovnu, ověřte podporu registrace a deploymentu. Plugin runtime nenačte libovolně komplexní dependency graph stejně jako běžná aplikace.
- Preferujte malé interní helpery před velkými dependency.
- Nepoužívejte balíky, které očekávají ASP.NET hosting, filesystem, registry, environment variables nebo dlouho běžící proces.
Strong-name signing
Plugin assembly musí být strong-name signed.
V .csproj:
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Contoso.Dataverse.Plugins.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
Vytvoření SNK:
sn -k Contoso.Dataverse.Plugins.snk
Doporučení:
- SNK chraňte jako build artifact/secrets podle firemní politiky.
- Pro opakovatelné buildy používejte stejný klíč pro stejnou assembly.
- Změna public key tokenu znamená pro Dataverse jinou assembly identitu.
Assembly metadata a verzování
Dataverse rozlišuje assembly podle jména, verze, culture a public key tokenu. Při deploymentu mějte jasnou strategii:
- Patch/bugfix: obvykle update existující assembly bez změny identity.
- Breaking změny: nová major verze a koordinovaná registrace steps.
- CI/CD: build verze by měla být dohledatelná z commit SHA/release tagu.
Praktický .csproj skeleton:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<LangVersion>latest</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Contoso.Dataverse.Plugins.snk</AssemblyOriginatorKeyFile>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CrmSdk.CoreAssemblies" Version="9.0.2.57" />
</ItemGroup>
</Project>
Registrace assembly
Nejčastější možnosti:
- Plugin Registration Tool v XrmToolBox nebo Microsoft tooling.
- Power Platform CLI / pac tooling podle zvoleného ALM procesu.
- Deployment přes Dataverse solution.
Při registraci nastavujete:
- Assembly isolation mode: typicky Sandbox pro Dataverse Online.
- Storage: Database.
- Plugin types: třídy v assembly.
- Steps: message, primary entity, stage, mode, filtering attributes, images, execution order.
Best practice:
- Registrovat pouze nutné steps.
- U
Updatevždy nastavovat filtering attributes, pokud plugin nepotřebuje reagovat na všechny změny. - Krok pojmenovat čitelně, např.
Account | Update | PreOperation | Normalize Name. - Registration metadata držet v solution a verzovat deployment procesem.
3. Thread-safe a stateless pluginy
Dataverse může z důvodu výkonu cachovat a znovu používat instance plugin tříd. Proto plugin nesmí spoléhat na mutable instance state.
Špatně:
public class BadPlugin : IPlugin
{
private IOrganizationService _service;
private Entity _target;
public void Execute(IServiceProvider serviceProvider)
{
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
_service = factory.CreateOrganizationService(null);
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
_target = (Entity)context.InputParameters["Target"];
// Nebezpečné: instance může být znovu použita pro jiné spuštění.
}
}
Správně:
public class GoodPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
if (!context.InputParameters.TryGetValue("Target", out var targetObj) || targetObj is not Entity target)
{
return;
}
tracing.Trace("Processing {0} {1} {2}", context.MessageName, target.LogicalName, context.CorrelationId);
// Všechny hodnoty jsou lokální proměnné pro toto spuštění.
}
}
Pravidla pro thread-safe plugin:
- Plugin třída má být stateless.
- Nepoužívejte mutable instance fields pro context, service, target, cache, výsledky výpočtů.
static readonlykonstanty jsou v pořádku.- Vyhněte se
staticmutable cache, pokud přesně neřešíte invalidaci a thread-safety. - Nevytvářejte vlastní background thread/task uvnitř pluginu.
- Nesdílejte
IOrganizationServicemezi spuštěními. - Vše získávejte z
IServiceProvideruvnitřExecutea předávejte dál jako lokální kontext.
Správné použití služeb
var tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
// Operace jako aktuální uživatel
var userService = factory.CreateOrganizationService(context.UserId);
// Operace jako owner plugin step / system kontext podle konfigurace; používat opatrně
var systemService = factory.CreateOrganizationService(null);
Použití:
IServiceProvider: pouze vstupní service locator pro aktuální spuštění.IPluginExecutionContext: metadata,InputParameters,OutputParameters,PreEntityImages,PostEntityImages,Depth,UserId,InitiatingUserId.IOrganizationServiceFactory: vytváří service pro konkrétního uživatele.IOrganizationService: CRUD a execute requesty do Dataverse.ITracingService: bezpečné server-side trace logy.
4. Reusable interface/base class pro pluginy
Cíl: odstranit boilerplate a vynutit jednotný pattern.
Návrh
Princip:
- Veřejná plugin třída implementuje
IPluginpřes base class. - Base class vytvoří
LocalPluginContext. - Base class řeší tracing, exception wrapping a základní validace.
- Konkrétní plugin implementuje pouze byznys logiku.
Ukázkový kód
using System;
using Microsoft.Xrm.Sdk;
namespace Contoso.Dataverse.Plugins
{
public interface IPluginHandler
{
void Execute(LocalPluginContext localContext);
}
public sealed class LocalPluginContext
{
public LocalPluginContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
OrganizationServiceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
UserService = OrganizationServiceFactory.CreateOrganizationService(PluginExecutionContext.UserId);
SystemService = OrganizationServiceFactory.CreateOrganizationService(null);
}
public IServiceProvider ServiceProvider { get; }
public ITracingService TracingService { get; }
public IPluginExecutionContext PluginExecutionContext { get; }
public IOrganizationServiceFactory OrganizationServiceFactory { get; }
public IOrganizationService UserService { get; }
public IOrganizationService SystemService { get; }
public bool TryGetTargetEntity(out Entity target)
{
target = null;
if (!PluginExecutionContext.InputParameters.TryGetValue("Target", out var targetObject))
{
return false;
}
target = targetObject as Entity;
return target != null;
}
public Entity GetPreImage(string imageName = "PreImage")
{
return PluginExecutionContext.PreEntityImages.TryGetValue(imageName, out var image)
? image
: null;
}
public Entity GetPostImage(string imageName = "PostImage")
{
return PluginExecutionContext.PostEntityImages.TryGetValue(imageName, out var image)
? image
: null;
}
public void Trace(string message, params object[] args)
{
TracingService?.Trace(message, args);
}
}
public abstract class PluginBase : IPlugin
{
protected PluginBase(string unsecureConfiguration = null, string secureConfiguration = null)
{
UnsecureConfiguration = unsecureConfiguration;
SecureConfiguration = secureConfiguration;
}
protected string UnsecureConfiguration { get; }
protected string SecureConfiguration { get; }
public void Execute(IServiceProvider serviceProvider)
{
var localContext = new LocalPluginContext(serviceProvider);
var context = localContext.PluginExecutionContext;
localContext.Trace(
"Start plugin {0}. Message={1}, Entity={2}, Stage={3}, Mode={4}, Depth={5}, CorrelationId={6}",
GetType().FullName,
context.MessageName,
context.PrimaryEntityName,
context.Stage,
context.Mode,
context.Depth,
context.CorrelationId);
try
{
ExecuteDataversePlugin(localContext);
}
catch (InvalidPluginExecutionException)
{
throw;
}
catch (Exception ex)
{
localContext.Trace("Unhandled exception: {0}", ex);
throw new InvalidPluginExecutionException("Unexpected error in plugin. Contact support with CorrelationId: " + context.CorrelationId, ex);
}
finally
{
localContext.Trace("End plugin {0}. CorrelationId={1}", GetType().FullName, context.CorrelationId);
}
}
protected abstract void ExecuteDataversePlugin(LocalPluginContext localContext);
}
}
Konkrétní plugin s minimem boilerplate
using Microsoft.Xrm.Sdk;
namespace Contoso.Dataverse.Plugins.Account
{
public sealed class AccountPreOperationUpdateNormalizeNamePlugin : PluginBase
{
protected override void ExecuteDataversePlugin(LocalPluginContext localContext)
{
var context = localContext.PluginExecutionContext;
if (!string.Equals(context.MessageName, "Update", System.StringComparison.OrdinalIgnoreCase))
{
return;
}
if (!string.Equals(context.PrimaryEntityName, "account", System.StringComparison.OrdinalIgnoreCase))
{
return;
}
if (!localContext.TryGetTargetEntity(out var target))
{
return;
}
if (!target.Contains("name"))
{
return;
}
var name = target.GetAttributeValue<string>("name");
if (string.IsNullOrWhiteSpace(name))
{
throw new InvalidPluginExecutionException("Account name is required.");
}
target["name"] = name.Trim();
localContext.Trace("Normalized account name for Target Id={0}", target.Id);
}
}
}
Ještě lepší: oddělení byznys logiky od plugin wrapperu
public interface IAccountNameNormalizer
{
string Normalize(string name);
}
public sealed class AccountNameNormalizer : IAccountNameNormalizer
{
public string Normalize(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new InvalidPluginExecutionException("Account name is required.");
}
return name.Trim();
}
}
Plugin pak jen orchestrace:
public sealed class AccountPreOperationUpdateNormalizeNamePlugin : PluginBase
{
private static readonly IAccountNameNormalizer Normalizer = new AccountNameNormalizer();
protected override void ExecuteDataversePlugin(LocalPluginContext localContext)
{
if (!localContext.TryGetTargetEntity(out var target) || !target.Contains("name"))
{
return;
}
target["name"] = Normalizer.Normalize(target.GetAttributeValue<string>("name"));
}
}
Tady je static readonly bezpečné, protože AccountNameNormalizer je stateless.
5. Plugin stages, sync/async, images, depth, filtering attributes
Stages
Dataverse používá číselné stage hodnoty:
- PreValidation: 10
- PreOperation: 20
- PostOperation: 40
PreValidation
Použití:
- Rychlá validace vstupu před transakcí.
- Zastavení operace před nákladnější logikou.
- Kontroly, které nemění databázi.
Pozor:
- Nemusí být v transakci.
- Některé operace mohou být spuštěné v kontextu jiné pipeline.
PreOperation
Použití:
- Úprava
Targetpřed uložením. - Nastavení default hodnot.
- Normalizace dat.
- Validace, která má být součástí transakce.
Best practice:
- Pokud chcete změnit sloupce právě ukládaného záznamu, v PreOperation měňte
Target; nedělejte dalšíUpdatena stejný záznam.
Špatně:
// Zbytečný extra update a riziko rekurze
var account = new Entity("account", target.Id);
account["name"] = normalizedName;
service.Update(account);
Správně v PreOperation:
target["name"] = normalizedName;
PostOperation
Použití:
- Práce s výsledkem po uložení.
- Vytváření navazujících záznamů.
- Výpočty závislé na finálním ID záznamu.
- Async integrace.
Pozor:
- Synchronní PostOperation je stále v transakci a blokuje uživatele.
- Nepište těžkou logiku do synchronního PostOperation.
Sync vs async
Synchronní plugin:
- Běží v transakci a uživatel čeká.
- Vhodné pro validaci a rychlé úpravy dat.
- Chyba vrátí uživateli dialog/API error a rollbackne transakci.
Asynchronní plugin:
- Běží později přes async service.
- Nezpomaluje přímou uživatelskou operaci.
- Vhodné pro integrace, následné akce, e-mail, dlouhodobější zpracování.
- Chyba se řeší přes system jobs/monitoring, ne přímo uživateli.
Rozhodovací pravidlo:
- Musí uživatel dostat chybu hned a zabránit uložení? Sync.
- Dá se práce provést později? Async.
- Trvá práce déle, volá externí systém nebo zpracovává více záznamů? Async nebo mimo plugin.
Entity images
U Update obsahuje Target pouze změněné atributy. Pokud potřebujete původní nebo finální hodnoty, registrujte images.
- Pre Image: snapshot před operací.
- Post Image: snapshot po operaci.
Příklad použití:
var preImage = localContext.GetPreImage();
var oldCreditLimit = preImage?.GetAttributeValue<Money>("creditlimit")?.Value;
var target = localContext.TryGetTargetEntity(out var t) ? t : null;
var newCreditLimit = target?.GetAttributeValue<Money>("creditlimit")?.Value ?? oldCreditLimit;
Best practice:
- Do image zahrnout pouze potřebné atributy.
- Nepoužívat image jako náhradu za retrieve všeho.
- U
Updatepočítat s tím, žeTargetnení kompletní entity.
Depth a prevence rekurze
context.Depth říká, jak hluboko je aktuální pipeline v řetězu následných operací. Pokud plugin aktualizuje stejnou nebo související entitu, může spustit další pluginy a vznikne rekurze.
Jednoduchá ochrana:
if (context.Depth > 1)
{
localContext.Trace("Skipping plugin because Depth={0}", context.Depth);
return;
}
Pozor: samotné Depth > 1 return není architektura, jen pojistka. Lepší je:
- Registrovat filtering attributes.
- V PreOperation měnit
Targetmísto dalšíhoUpdate. - Aktualizovat jen atributy, které se opravdu změnily.
- Oddělit steps tak, aby si navzájem nespouštěly smyčku.
Filtering attributes
U Update nastavte filtering attributes na konkrétní sloupce, které plugin zajímají.
Příklad:
Plugin normalizující account name má filtering attributes:
name
Ne:
všechny atributy
Výhody:
- Méně zbytečných spuštění.
- Menší riziko rekurze.
- Lepší výkon.
- Čitelnější registrace.
Execution order
Pokud je více steps ve stejné stage, Dataverse je řadí podle execution order.
Doporučení:
- Nepoužívat pořadí jako skrytou závislost, pokud to není nutné.
- Pokud pořadí důležité je, dokumentovat ho v názvu/README.
- Používat mezery v číslování, např. 10, 20, 30.
6. Error handling a tracing best practices
Kdy házet výjimku
Pro business validaci používejte InvalidPluginExecutionException.
if (creditLimit > 100000 && !userCanApprove)
{
throw new InvalidPluginExecutionException("Credit limit above 100,000 requires approval.");
}
Doporučení:
- Uživatelům zobrazujte srozumitelnou zprávu.
- Technický detail logujte do tracingu.
- Nevracejte stack trace ani citlivá data do uživatelské chyby.
- Neošetřujte chyby tak, že je potichu spolykáte.
Špatně:
try
{
service.Update(entity);
}
catch
{
// chyba zmizí, data jsou nekonzistentní
}
Správně:
try
{
service.Update(entity);
}
catch (Exception ex)
{
tracing.Trace("Failed to update account {0}. Exception: {1}", entity.Id, ex);
throw new InvalidPluginExecutionException("Account update failed. Contact support with the operation time and correlation id.", ex);
}
Tracing
Používejte ITracingService.Trace.
Logujte:
- Start/end pluginu.
- Message, entity, stage, mode, depth.
- CorrelationId.
- Důležité rozhodovací větve.
- ID záznamů, ne celé payloady.
- Exception detail.
Neloggovat:
- Access tokeny.
- Connection stringy.
- Osobní/citlivé údaje, pokud nejsou nutné.
- Celé entity s mnoha atributy.
Příklad:
localContext.Trace(
"Account credit validation. AccountId={0}, OldLimit={1}, NewLimit={2}, CorrelationId={3}",
target.Id,
oldLimit,
newLimit,
context.CorrelationId);
Výkon a limity
Plugin by měl být rychlý. V sandboxu existují časové a bezpečnostní limity.
Best practices:
- Minimalizujte počet
Retrieve/Update/Executevolání. - Nepoužívejte
ColumnSet(true)bez velmi dobrého důvodu. - U
RetrieveMultiplevždy omezte sloupce a počet dat. - Nevytvářejte dlouhé transakce synchronními pluginy.
- Nevkládejte do pluginu batch processing velkých objemů dat.
- Externí HTTP volání dělejte asynchronně a s timeoutem.
Špatně:
var account = service.Retrieve("account", accountId, new ColumnSet(true));
Správně:
var account = service.Retrieve("account", accountId, new ColumnSet("name", "creditlimit"));
7. Debugging
Plugin Registration Tool
Plugin Registration Tool slouží k:
- Registraci assembly.
- Registraci steps.
- Nastavení stage/mode/entity/filtering attributes.
- Nastavení pre/post images.
- Instalaci a použití Plug-in Profileru.
Doporučený postup kontroly registrace:
- Ověř assembly a plugin type.
- Ověř step message/entity/stage/mode.
- U
Updateověř filtering attributes. - Ověř image aliasy a sloupce.
- Ověř user/context, ve kterém step běží.
- Ověř, že plugin trace logging je povolený.
Plug-in Profiler
Profiler zachytí execution context a umožní lokální replay/debug.
Typický postup:
- Nainstalovat Profiler přes Plugin Registration Tool.
- Profilovat konkrétní step.
- Reprodukovat akci v aplikaci/API.
- Stáhnout profile log.
- Otevřít plugin projekt ve Visual Studiu.
- Spustit debug replay s profile logem.
- Po dokončení profiling vypnout/odinstalovat.
Pozor:
- Profiler používejte cíleně a krátkodobě.
- Neprofilujte produkci bez schválení.
- Profile log může obsahovat data zákazníka.
Plugin Trace Logs
Trace logy jsou základní diagnostika v Dataverse.
Nastavení se dělá v environment/system settings podle aktuálního UI. Typické režimy:
- Off
- Exception
- All
Doporučení:
- Na produkci typicky
Exception, dočasněAllpři diagnostice. - V dev/test prostředí klidně
All. - Logy pravidelně čistit/monitorovat.
- V trace zprávách vždy uvádět correlation id.
8. Deployment: solutions, managed/unmanaged, versioning, registration
Solutions
Power Platform solution je balíček komponent: plugin assembly, steps, tables, columns, flows, model-driven app atd.
Typický ALM model:
- DEV: unmanaged solution pro vývoj.
- TEST/UAT/PROD: managed solution exportovaná z buildu.
Doporučení:
- Plugin assembly a steps patří do solution.
- Registraci nedělat ručně na každém prostředí bez evidence.
- Deployment automatizovat přes pipeline, pokud projekt roste.
- Environment-specific hodnoty dávat do environment variables / secure configuration, ne hardcodovat.
Managed vs unmanaged
Unmanaged:
- Vhodné pro vývoj.
- Komponenty lze přímo editovat.
- Není ideální pro produkční distribuci.
Managed:
- Vhodné pro test/prod.
- Umožňuje řízené vrstvení a uninstall.
- Lépe odpovídá release procesu.
Secure a unsecure configuration
Plugin step může mít unsecure a secure configuration.
Použití:
- Unsecure: ne-citlivé nastavení, které může být součástí solution.
- Secure: citlivější hodnoty, které nemají být běžně čitelné všemi administrátory; stále ale nepoužívat jako náhradu enterprise secret manageru pro velká tajemství.
Příklad konstruktoru pluginu:
public sealed class MyConfiguredPlugin : PluginBase
{
public MyConfiguredPlugin(string unsecureConfiguration, string secureConfiguration)
: base(unsecureConfiguration, secureConfiguration)
{
}
protected override void ExecuteDataversePlugin(LocalPluginContext localContext)
{
localContext.Trace("Plugin has configuration: {0}", !string.IsNullOrWhiteSpace(UnsecureConfiguration));
}
}
Versioning a release
Best practice:
- Každý release má verzi a changelog.
- Build produkuje DLL, symboly a solution package.
- Registrace steps je součástí solution nebo automatizovaného deploymentu.
- Po deploymentu ověřit plugin trace logy a system jobs.
- Rollback plán: předchozí managed solution verze nebo hotfix solution.
CI/CD checklist
- Restore NuGet packages.
- Build Release.
- Spustit unit testy.
- Zkontrolovat strong-name signing.
- Zabalit solution.
- Import do test prostředí.
- Spustit smoke testy.
- Import do produkce jako managed.
- Ověřit plugin steps, trace logs a async jobs.
9. Checklist pro nového developera
Projekt a build
- [ ] Plugin projekt targetuje podporovaný .NET Framework, typicky
net462. - [ ] Assembly je strong-name signed.
- [ ] Používá se
Microsoft.CrmSdk.CoreAssemblies. - [ ] Nejsou přidány zbytečné externí dependency.
- [ ] Projekt má jasnou strukturu podle entity/domény.
- [ ] Sdílený plugin base/context pattern je použit konzistentně.
Kód
- [ ] Plugin třídy jsou stateless.
- [ ] Žádný mutable instance state.
- [ ]
IServiceProvider, context, service a tracing se získávají v aktuálnímExecute. - [ ]
Targetje validován na existenci a typ. - [ ] U
Updatese počítá s částečnýmTarget. - [ ]
ColumnSet(true)není použito bez důvodu. - [ ] Business validace hází
InvalidPluginExecutionException. - [ ] Trace log neobsahuje secrets ani zbytečná osobní data.
Registrace
- [ ] Step má správnou message/entity/stage/mode.
- [ ] U
Updatejsou nastavené filtering attributes. - [ ] Images obsahují pouze potřebné atributy.
- [ ] Execution order je zdokumentovaný, pokud na něm záleží.
- [ ] Async je použit pro dlouhou nebo integrační práci.
- [ ] Plugin step je součástí solution.
Debugging a provoz
- [ ] Plugin trace logging je v dev/test zapnutý.
- [ ] Chybové zprávy jsou srozumitelné pro uživatele.
- [ ] Technické detaily jsou v trace logu.
- [ ] CorrelationId je dohledatelné.
- [ ] Plug-in Profiler se používá cíleně a po použití se vypne.
- [ ] Async jobs jsou monitorované.
Deployment
- [ ] DEV používá unmanaged solution.
- [ ] TEST/PROD používají managed solution.
- [ ] Release má verzi a changelog.
- [ ] Deployment je opakovatelný.
- [ ] Environment-specific konfigurace není hardcoded.
- [ ] Po deploymentu proběhl smoke test.
10. Zdroje z Microsoft Learn
Doporučené oficiální zdroje:
- Dataverse plug-ins: learn.microsoft.com/en-us/power-apps/developer/data-platform/plug-ins
- Write a plug-in: learn.microsoft.com/en-us/power-apps/developer/data-platform/write-plug-in
- Register a plug-in: learn.microsoft.com/en-us/power-apps/developer/data-platform/register-plug-in
- Event framework: learn.microsoft.com/en-us/power-apps/developer/data-platform/event-framework
- Understand the execution context: learn.microsoft.com/en-us/power-apps/developer/data-platform/understand-the-data-context
- Event execution pipeline: learn.microsoft.com/en-us/power-apps/developer/data-platform/event-framework#event-execution-pipeline
- Use ITracingService in plug-ins: learn.microsoft.com/en-us/power-apps/developer/data-platform/logging-tracing
- Debug a plug-in: learn.microsoft.com/en-us/power-apps/developer/data-platform/debug-plug-in
- Best practices and guidance for Dataverse business logic: learn.microsoft.com/en-us/power-apps/developer/data-platform/best-practices/business-logic/
- Build and package Dataverse solutions: learn.microsoft.com/en-us/power-platform/alm/solution-concepts-alm
- Power Platform ALM: learn.microsoft.com/en-us/power-platform/alm/
Shrnutí pro .NET developera
Největší rozdíl proti běžné .NET aplikaci je hosting model. Plugin běží uvnitř Dataverse pipeline, ve sdíleném serverovém runtime, s krátkou životností operace a s možností reuse instance. Proto:
- Pište pluginy jako stateless command handlery.
- Nechte Dataverse řídit lifecycle.
- Minimalizujte práci v synchronní transakci.
- Používejte správnou stage místo dodatečných update operací.
- Vždy myslete na rekurzi, partial target, images a filtering attributes.
- Debuggability stavte na tracingu, correlation id a opakovatelném deploymentu.