DynamicsCrm.DevKit.Analyzers
4.0.0
dotnet add package DynamicsCrm.DevKit.Analyzers --version 4.0.0
NuGet\Install-Package DynamicsCrm.DevKit.Analyzers -Version 4.0.0
<PackageReference Include="DynamicsCrm.DevKit.Analyzers" Version="4.0.0" />
<PackageVersion Include="DynamicsCrm.DevKit.Analyzers" Version="4.0.0" />
<PackageReference Include="DynamicsCrm.DevKit.Analyzers" />
paket add DynamicsCrm.DevKit.Analyzers --version 4.0.0
#r "nuget: DynamicsCrm.DevKit.Analyzers, 4.0.0"
#:package DynamicsCrm.DevKit.Analyzers@4.0.0
#addin nuget:?package=DynamicsCrm.DevKit.Analyzers&version=4.0.0
#tool nuget:?package=DynamicsCrm.DevKit.Analyzers&version=4.0.0
____ _ ____ ____ _ ___ _ _ _
| _ \ _ _ _ __ __ _ _ __ ___ (_) ___ ___ / ___|_ __ _ __ ___ | _ \ _____ _| |/ (_) |_ / \ _ __ __ _| |_ _ _______ _ __ ___
| | | | | | | '_ \ / _` | '_ ` _ \| |/ __/ __| | | '__| '_ ` _ \ | | | |/ _ \ \ / / ' /| | __| / _ \ | '_ \ / _` | | | | |_ / _ \ '__/ __|
| |_| | |_| | | | | (_| | | | | | | | (__\__ \ |___| | | | | | | |_| |_| | __/\ V /| . \| | |_ _ / ___ \| | | | (_| | | |_| |/ / __/ | \__ \
|____/ \__, |_| |_|\__,_|_| |_| |_|_|\___|___/\____|_| |_| |_| |_(_)____/ \___| \_/ |_|\_\_|\__(_)_/ \_\_| |_|\__,_|_|\__, /___\___|_| |___/
|___/ https://github.com/phuocle/Dynamics-Crm-DevKit 4.00.00.00 Build: 31.12.2025 23:59:59|___/
đ DynamicsCrm.DevKit.Analyzers
A Roslyn-based code analyzer package for Microsoft Dynamics 365 / Power Platform development. It provides compile-time diagnostics to help developers follow best practices and avoid common pitfalls when building plugins, custom workflows, and other CRM customizations.
đĻ Installation
Install via NuGet:
dotnet add package DynamicsCrm.DevKit.Analyzers
Or add to your .csproj:
<PackageReference Include="DynamicsCrm.DevKit.Analyzers" Version="*" PrivateAssets="all" />
đ Diagnostic Rules
| Rule ID | Severity | Description |
|---|---|---|
| DEVKIT1001 | Error | Create/Update message should have filtering attributes |
| DEVKIT1002 | Warning | Don't use ColumnSet(true) |
| DEVKIT1003 | Error | Plugin image validation |
| DEVKIT1004 | Info | Use of deprecated SDK messages |
| DEVKIT1005 | Warning | EntityReference maybe null |
| DEVKIT1006 | Warning | Don't use batch request types in plug-ins |
| DEVKIT1007 | Error | IPlugin implementations should be stateless |
| DEVKIT1008 | Error | Don't use parallel execution in plug-ins |
| DEVKIT1009 | Warning | Set KeepAlive to false for external HTTP calls |
| DEVKIT1010 | Warning | Set Timeout for external HTTP calls |
| DEVKIT1011 | Warning | Use InvalidPluginExecutionException for errors |
| DEVKIT1012 | Info | Consider using ITracingService in plug-ins |
| DEVKIT1013 | Info | Avoid registering plugins on Retrieve/RetrieveMultiple |
| DEVKIT1014 | Error | Avoid AppDomain event registration in plug-ins |
| DEVKIT1015 | Info | Avoid blocking async patterns in plug-ins |
| DEVKIT1016 | Info | Avoid retrieving unpublished metadata |
| DEVKIT1017 | Info | Avoid Console output in plug-ins |
| DEVKIT1018 | Error | Avoid File/IO operations in plug-ins |
| DEVKIT1019 | Warning | Consider checking context.Depth to prevent infinite loops |
| DEVKIT1020 | Error | DataProvider must have DataSource |
â DEVKIT1001
Create/Update message should have filtering attributes
Severity: Error
MS Best Practice: đ Include filtering attributes with plug-in registration
This analyzer ensures that plugin registrations for Create, CreateMultiple, Update, UpdateMultiple, OnExternalCreated, or OnExternalUpdated messages include specific filtering attributes. This prevents the plugin from executing on every field change, which can significantly impact performance.
Bad Code:
// â Empty filtering attributes
[CrmPluginRegistration("Create", "account", StageEnum.PreOperation, ExecutionModeEnum.Synchronous,
filteringAttributes: "",
stepName: "Pre-Create Account")]
// â All attributes
[CrmPluginRegistration("Update", "account", StageEnum.PreOperation, ExecutionModeEnum.Synchronous,
filteringAttributes: "*",
stepName: "Pre-Update Account")]
Good Code:
// â
Specific attributes
[CrmPluginRegistration("Create", "account", StageEnum.PostOperation, ExecutionModeEnum.Synchronous,
filteringAttributes: "name,accountnumber",
stepName: "Post-Create Account")]
// â
Specific attributes
[CrmPluginRegistration("Update", "account", StageEnum.PreOperation, ExecutionModeEnum.Synchronous,
filteringAttributes: "name,accountnumber",
stepName: "Pre-Update Account")]
â ī¸ DEVKIT1002
Don't use ColumnSet(true)
Severity: Warning
MS Best Practice: Retrieve specific columns for a table via query APIs
Warns against using ColumnSet(true) which retrieves all columns from an entity. This is a performance anti-pattern as it retrieves unnecessary data.
Bad Code:
// â Retrieves all columns
var entity = service.Retrieve("account", id, new ColumnSet(true));
// â AllColumns = true
var query = new QueryExpression("account")
{
ColumnSet = new ColumnSet { AllColumns = true }
};
// â FetchXML with all-attributes
var fetch = @"<fetch><entity name='account'><all-attributes/></entity></fetch>";
Good Code:
// â
Only retrieve needed columns
var entity = service.Retrieve("account", id, new ColumnSet("name", "accountnumber"));
â DEVKIT1003
Plugin image validation
Severity: Error
MS Best Practice: Understand the execution context (Plugin Images)
Validates that plugin image configurations are compatible with the message and stage. The Dynamics 365 platform has specific rules about when Pre-Images and Post-Images are available:
| Message | Stage | Pre-Image | Post-Image |
|---|---|---|---|
| Create | Pre-Validation | â | â |
| Create | Pre-Operation | â | â |
| Create | Post-Operation | â | â |
| Update | Pre-Validation | â | â |
| Update | Pre-Operation | â | â |
| Update | Post-Operation | â | â |
| Delete | Pre-Validation | â | â |
| Delete | Pre-Operation | â | â |
| Delete | Post-Operation | â | â |
Bad Code:
// â Pre-Create cannot have Pre-Image or Post-Image
[CrmPluginRegistration("Create", "account", StageEnum.PreOperation, ExecutionModeEnum.Synchronous,
Image1Type = ImageTypeEnum.PreImage, Image1Attributes = "name")]
// â Pre-Update cannot have Post-Image
[CrmPluginRegistration("Update", "account", StageEnum.PreOperation, ExecutionModeEnum.Synchronous,
Image1Type = ImageTypeEnum.PostImage, Image1Attributes = "name")]
Good Code:
// â
Post-Create can have Post-Image
[CrmPluginRegistration("Create", "account", StageEnum.PostOperation, ExecutionModeEnum.Synchronous,
Image1Type = ImageTypeEnum.PostImage, Image1Attributes = "name")]
// â
Post-Update can have both images
[CrmPluginRegistration("Update", "account", StageEnum.PostOperation, ExecutionModeEnum.Synchronous,
Image1Type = ImageTypeEnum.PreImage, Image1Attributes = "name",
Image2Type = ImageTypeEnum.PostImage, Image2Attributes = "name")]
âšī¸ DEVKIT1004
Use of deprecated SDK messages
Severity: Warning
MS Best Practice: Deprecated SDK messages
Warns when using deprecated request/response classes from Microsoft.Crm.Sdk.Messages. These messages may be removed in future SDK versions.
Deprecated Messages Include:
AddProductToKitRequest/ResponseAddSubstituteProductRequest/ResponseAssociateEntitiesRequest/ResponseCompoundCreateRequest/ResponseCompoundUpdateRequest/ResponseConvertKitToProductRequest/ResponseConvertProductToKitRequest/ResponseDisassociateEntitiesRequest/ResponseExecuteFetchRequest/ResponseSetStateRequest/Response- And more...
Bad Code:
// â Deprecated message
var request = new SetStateRequest
{
EntityMoniker = new EntityReference("account", accountId),
State = new OptionSetValue(1),
Status = new OptionSetValue(2)
};
service.Execute(request);
Good Code:
// â
Use Update instead
var account = new Entity("account", accountId)
{
["statecode"] = new OptionSetValue(1),
["statuscode"] = new OptionSetValue(2)
};
service.Update(account);
â ī¸ DEVKIT1005
EntityReference maybe null
Severity: Error
MS Best Practice: Null safety pattern for lookup fields
Flags potential null reference exceptions when accessing Id, Name, or LogicalName properties of an EntityReference that may be null. Lookup fields in Dynamics 365 can return null if no value is set.
Bad Code:
// â EntityReference may be null
var ownerId = entity.GetAttributeValue<EntityReference>("ownerid").Id;
var ownerName = entity.GetAttributeValue<EntityReference>("ownerid").Name;
// â In string concatenation
var message = "Owner: " + entity.GetAttributeValue<EntityReference>("ownerid").Name;
Good Code:
// â
Null-conditional operator
var ownerId = entity.GetAttributeValue<EntityReference>("ownerid")?.Id;
var ownerName = entity.GetAttributeValue<EntityReference>("ownerid")?.Name;
// â
Null check first
var ownerRef = entity.GetAttributeValue<EntityReference>("ownerid");
if (ownerRef != null)
{
var ownerId = ownerRef.Id;
}
â ī¸ DEVKIT1006
Don't use batch request types in plug-ins and workflow activities
Severity: Warning
MS Best Practice: Don't use batch request types in plug-ins
Warns against using batch request types (ExecuteMultipleRequest, ExecuteTransactionRequest, CreateMultipleRequest, UpdateMultipleRequest, UpsertMultipleRequest) within plug-ins or workflow activities. These can cause performance issues and timeout errors.
Bad Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// â Using ExecuteMultipleRequest in plugin
var batch = new ExecuteMultipleRequest();
foreach (var entity in entities)
{
batch.Requests.Add(new UpdateRequest { Target = entity });
}
service.Execute(batch);
}
}
Good Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// â
Execute each request individually
foreach (var entity in entities)
{
service.Update(entity);
}
}
}
â DEVKIT1007
IPlugin implementations should be stateless
Severity: Error
Detects assignments to instance fields or properties during plug-in execution. IPlugin classes are cached and reused across multiple threads - storing state in instance members can cause thread-safety issues and data inconsistencies.
MS Best Practice: Develop IPlugin implementations as stateless
Bad Code:
public class MyPlugin : IPlugin
{
// â Mutable instance field
private IOrganizationService _service;
private IPluginExecutionContext _context;
public void Execute(IServiceProvider serviceProvider)
{
// â Assigning to instance field during execution
_context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
_service = factory.CreateOrganizationService(_context.UserId);
}
}
Good Code:
public class MyPlugin : IPlugin
{
// â
Readonly field assigned in constructor (for configuration)
private readonly string _secureConfig;
public MyPlugin(string unsecure, string secure)
{
_secureConfig = secure;
}
public void Execute(IServiceProvider serviceProvider)
{
// â
Local variables instead of instance fields
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var service = factory.CreateOrganizationService(context.UserId);
}
}
â DEVKIT1008
Don't use parallel execution in plug-ins and workflow activities
Severity: Error
MS Best Practice: Do not use parallel execution within plug-ins and workflow activities
Detects usage of parallel execution patterns within IPlugin or CodeActivity classes. Multi-threading and parallel execution are not supported in the Dataverse sandbox and can cause unpredictable behavior.
Detected Patterns:
Task.Run(),Task.Factory.StartNew()Parallel.For(),Parallel.ForEach(),Parallel.Invoke()new Thread()ThreadPool.QueueUserWorkItem()
Bad Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var entities = GetEntities();
// â Using Parallel.ForEach
Parallel.ForEach(entities, entity => {
service.Update(entity);
});
// â Using Task.Run
Task.Run(() => DoSomething());
// â Using Thread
var thread = new Thread(() => DoWork());
thread.Start();
}
}
Good Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var entities = GetEntities();
// â
Sequential processing
foreach (var entity in entities)
{
service.Update(entity);
}
// â
Direct method call
DoSomething();
}
}
â ī¸ DEVKIT1009
Set KeepAlive to false for external HTTP calls
Severity: Warning
MS Best Practice: Set KeepAlive to false when interacting with external hosts
Warns when using HttpClient or WebRequest in plugins without setting KeepAlive to false. The sandbox environment has connection pool limitations that can cause issues when KeepAlive is enabled.
Bad Code:
// â HttpClient with default KeepAlive (true)
using (var client = new HttpClient())
{
var response = client.GetAsync(url).GetAwaiter().GetResult();
}
Good Code:
// â
HttpClient with ConnectionClose = true
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.ConnectionClose = true;
var response = client.GetAsync(url).GetAwaiter().GetResult();
}
â ī¸ DEVKIT1010
Set Timeout for external HTTP calls
Severity: Warning
MS Best Practice: Set Timeout when making external calls from a plug-in
Warns when using HttpClient in plugins without setting an explicit Timeout. The default HttpClient timeout is 100 seconds, which may exceed the plugin timeout limit.
Bad Code:
// â HttpClient with default timeout (100 seconds)
using (var client = new HttpClient())
{
var response = client.GetAsync(url).GetAwaiter().GetResult();
}
Good Code:
// â
HttpClient with explicit timeout
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(15);
var response = client.GetAsync(url).GetAwaiter().GetResult();
}
â ī¸ DEVKIT1013
Avoid registering plugins on Retrieve/RetrieveMultiple
Severity: Warning
MS Best Practice: Limit the registration of plug-ins for Retrieve and RetrieveMultiple messages
Warns when a plugin is registered on Retrieve or RetrieveMultiple messages. These messages are called very frequently and can significantly impact system performance.
Bad Code:
// â Plugin on RetrieveMultiple - runs EVERY time a view is loaded
[CrmPluginRegistration("RetrieveMultiple", "account", StageEnum.PostOperation,
ExecutionModeEnum.Synchronous, "", "RetrieveMultiple Account")]
public class RetrieveMultipleAccountPlugin : IPlugin { }
Good Code:
// â
Use Create/Update to pre-calculate values instead
[CrmPluginRegistration("Update", "account", StageEnum.PreOperation,
ExecutionModeEnum.Synchronous, "revenue", "Calculate Account Rating")]
public class CalculateAccountRatingPlugin : IPlugin { }
â ī¸ DEVKIT1011
Use InvalidPluginExecutionException for errors
Severity: Warning
MS Best Practice: Use InvalidPluginExecutionException in plug-ins and workflow activities
Warns when throwing exceptions other than InvalidPluginExecutionException in plugins. Only this exception type is properly handled by the platform and shows messages to users.
Bad Code:
// â Generic Exception - user sees "An error occurred"
throw new Exception("Something went wrong");
Good Code:
// â
InvalidPluginExecutionException - message shown to user
throw new InvalidPluginExecutionException("Please provide a valid account name.");
âšī¸ DEVKIT1012
Consider using ITracingService in plug-ins
Severity: Warning
MS Best Practice: Use ITracingService in Plug-ins
Recommends using ITracingService in plug-in classes for debugging and monitoring.
Bad Code:
// â ī¸ No tracing - difficult to debug
public class AccountPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider) { }
}
Good Code:
// â
Uses tracing for debugging
public class AccountPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
tracingService.Trace("Plugin started");
}
}
â ī¸ DEVKIT1016
Avoid retrieving unpublished metadata
Severity: Warning
MS Best Practice: Retrieve published metadata
Detects RetrieveAsIfPublished = true on metadata requests. Retrieving unpublished metadata causes slower performance.
Bad Code:
// â ī¸ RetrieveAsIfPublished = true causes performance issues
var request = new RetrieveEntityRequest
{
MetadataId = entityId,
RetrieveAsIfPublished = true
};
Good Code:
// â
Default behavior retrieves published metadata only
var request = new RetrieveEntityRequest
{
MetadataId = entityId
};
â DEVKIT1014
Avoid AppDomain event registration in plug-ins
Severity: Error
Detects subscription to AppDomain events in plugins. Plugin instances are cached and reused, so subscribing to AppDomain events can cause memory leaks.
Bad Code:
// â Memory leak - event handler never removed
AppDomain.CurrentDomain.UnhandledException += (s, e) => { };
Good Code:
// â
Use try-catch for exception handling
try { /* work */ }
catch (Exception ex) { throw new InvalidPluginExecutionException("Error", ex); }
â ī¸ DEVKIT1015
Avoid blocking async patterns in plug-ins
Severity: Warning
Detects usage of GetAwaiter().GetResult(), .Result, and .Wait() on Tasks in plugins. These can cause deadlocks.
Patterns Detected:
// â ī¸ Can cause deadlocks
task.GetAwaiter().GetResult();
task.Result;
task.Wait();
Better Alternative:
// â
Use ConfigureAwait(false) or synchronous APIs
var result = task.ConfigureAwait(false).GetAwaiter().GetResult();
âšī¸ DEVKIT1017
Avoid Console output in plug-ins and workflow activities
Severity: Info
Console output methods like Console.Write() and Console.WriteLine() have no effect in the Dataverse sandbox environment. The console stream is redirected to null.
Patterns Detected:
// â ī¸ Has no effect in sandbox
Console.WriteLine("Debug message");
Console.Write($"Processing: {entity.Id}");
Console.Error.WriteLine("Error occurred");
Better Alternative:
// â
Use ITracingService for logging
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
tracingService.Trace("Debug message");
tracingService.Trace($"Processing: {entity.Id}");
â DEVKIT1018
Avoid File/IO operations in plug-ins and workflow activities
Severity: Error
System.IO file operations are blocked in the Dataverse sandbox environment and will throw SecurityException at runtime.
Bad Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// â File.ReadAllText - blocked in sandbox
var content = File.ReadAllText("config.txt");
// â File.WriteAllText - blocked in sandbox
File.WriteAllText("log.txt", "Plugin executed");
// â new FileStream - blocked in sandbox
using (var stream = new FileStream("data.bin", FileMode.Open))
{
}
// â new StreamReader - blocked in sandbox
using (var reader = new StreamReader("input.txt"))
{
}
}
}
Good Code:
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// â
Use Dataverse storage instead of files
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
tracingService.Trace("Use tracing instead of file logging");
// â
Store data using Note attachments
var note = new Entity("annotation")
{
["subject"] = "Plugin Output",
["notetext"] = "Data to store"
};
service.Create(note);
}
}
â ī¸ DEVKIT1019
Consider checking context.Depth to prevent infinite loops
Severity: Warning
This analyzer recommends checking IPluginExecutionContext.Depth in plugin classes to prevent infinite loops when plugins modify entities that trigger themselves recursively.
Bad Code:
public class AccountPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// â No depth check - can cause infinite loop
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
var target = (Entity)context.InputParameters["Target"];
// This update triggers the plugin again!
var update = new Entity("account", target.Id);
update["modifiedon"] = DateTime.UtcNow;
service.Update(update);
}
}
Good Code:
public class AccountPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
// â
Exit early if this is a recursive call
if (context.Depth > 1) return;
var target = (Entity)context.InputParameters["Target"];
// Now safe to update
var update = new Entity("account", target.Id);
update["modifiedon"] = DateTime.UtcNow;
service.Update(update);
}
}
â DEVKIT1020
DataProvider must have DataSource
Severity: Error
MS Best Practice: Virtual Table Data Providers
This analyzer detects when a CrmPluginRegistration attribute uses PluginType.DataProvider but the DataSource parameter is empty or missing. DataProvider plugins require a valid DataSource to function correctly at runtime.
Bad Code:
public class MyPlugin : IPlugin
{
// â DataSource is empty - will fail at runtime
[CrmPluginRegistration("Dev.DevKit.Server.DataProviders.Cds.Retrieve", "Retrieve",
PluginType.DataProvider, DataSource = "")]
public void Execute(IServiceProvider serviceProvider) { }
}
Good Code:
public class MyPlugin : IPlugin
{
// â
DataSource is specified with valid data source name
[CrmPluginRegistration("Dev.DevKit.Server.DataProviders.Cds.Retrieve", "Retrieve",
PluginType.DataProvider, DataSource = "v4_sql_datasource")]
public void Execute(IServiceProvider serviceProvider) { }
}
âī¸ Configuration
You can suppress specific rules in your .editorconfig:
[*.cs]
# Disable specific rules
dotnet_diagnostic.DEVKIT1001.severity = none
dotnet_diagnostic.DEVKIT1002.severity = suggestion
Or use #pragma directives:
#pragma warning disable DEVKIT1002
var entity = service.Retrieve("account", id, new ColumnSet(true));
#pragma warning restore DEVKIT1002
đ Requirements
- .NET Standard 2.0 compatible projects
- Visual Studio 2019+ or any IDE with Roslyn analyzer support
đ License
This project is part of DynamicsCrm.DevKit.
Learn more about Target Frameworks and .NET Standard.
This package has 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 |
|---|---|---|
| 4.0.0 | 113 | 12/31/2025 |
| 3.55.55.55 | 585 | 4/3/2025 |
| 3.45.67.89 | 214 | 3/31/2025 |
| 3.44.44.44 | 614 | 12/26/2024 |
| 3.33.33.34 | 668 | 3/28/2024 |
| 3.33.33.33 | 351 | 12/31/2023 |
| 2.13.33 | 11,878 | 9/24/2021 |
| 2.12.31 | 6,856 | 7/7/2021 |
| 2.10.31 | 8,038 | 10/31/2020 |
| 2.2.29 | 777 | 3/1/2020 |
| 2.1.1 | 2,100 | 11/14/2019 |
| 2.1.0 | 909 | 9/30/2019 |
| 2.0.0 | 870 | 7/31/2019 |
Version 4.0.0:
- 20 diagnostic rules covering plugin best practices
- DEVKIT1001-DEVKIT1020 analyzers for common Dynamics 365 pitfalls
- Support for filtering attributes, ColumnSet validation, image validation
- Detection of deprecated SDK messages, parallel execution, and file I/O operations
- Recommendations for ITracingService usage and context.Depth checks