DEVKIT1007 - phuocle/Dynamics-Crm-DevKit GitHub Wiki
This analyzer detects assignments to instance fields or properties during plug-in or workflow activity execution. IPlugin and CodeActivity classes are cached and reused across multiple threads - storing state in instance members causes thread-safety issues and data inconsistencies.
📚 Develop IPlugin implementations as stateless
The platform caches plug-in class instances. The way they cache and reuse the instance means that developers cannot use class member variables in plug-ins except in specific, well-known patterns.
Storing state in plugin instance members causes:
- Thread-Safety Issues: Multiple threads share the same instance simultaneously
- Data Corruption: One execution can overwrite another's data mid-execution
- Race Conditions: Unpredictable behavior based on execution timing
- Difficult Debugging: Issues are intermittent and hard to reproduce
| Pattern | Status |
|---|---|
| Constructor assignments | ✅ Safe |
readonly field assignments in constructor |
✅ Safe |
static field/property assignments |
✅ Safe |
const declarations |
✅ Safe |
| Local variable assignments | ✅ Safe |
| Instance field assignment in Execute | ❌ Unsafe |
The analyzer flags assignments to instance fields or properties in:
-
IPluginimplementations during any method execution -
CodeActivity,NativeActivity, orActivityimplementations - Helper methods called from execution context
public class BadPlugin : IPlugin
{
// ❌ Mutable instance fields - shared across all executions!
private IOrganizationService _service;
private IPluginExecutionContext _context;
private Entity _target;
public void Execute(IServiceProvider serviceProvider)
{
// ❌ Assigning to instance fields during execution
_context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
_service = factory.CreateOrganizationService(_context.UserId);
_target = (Entity)_context.InputParameters["Target"];
ProcessEntity(); // Uses instance fields - DANGEROUS!
}
}public class GoodPlugin : IPlugin
{
// ✅ Readonly field - assigned in constructor only
private readonly string _secureConfig;
public GoodPlugin(string unsecure, string secure)
{
// ✅ Constructor assignment is safe
_secureConfig = secure;
}
public void Execute(IServiceProvider serviceProvider)
{
// ✅ Local variables - each execution has its own
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
var target = (Entity)context.InputParameters["Target"];
// ✅ Pass dependencies to helper methods
ProcessEntity(service, target);
}
private void ProcessEntity(IOrganizationService service, Entity target)
{
// ✅ Uses parameters, not instance fields
service.Update(target);
}
}- Convert to Local Variables: Replace instance fields with local variables in Execute method
- Pass as Parameters: Pass dependencies to helper methods instead of using instance state
- Use Constructor for Configuration Only: Only store immutable configuration in instance fields
- private IOrganizationService _service;
public void Execute(IServiceProvider serviceProvider)
{
- _service = GetService();
- DoWork();
+ var service = GetService();
+ DoWork(service);
}
- private void DoWork()
+ private void DoWork(IOrganizationService service)
{
- _service.Update(entity);
+ service.Update(entity);
}If you have a legitimate need to suppress this warning:
#pragma warning disable DEVKIT1007
private IOrganizationService _service;
#pragma warning restore DEVKIT1007Or in .editorconfig:
[*.cs]
dotnet_diagnostic.DEVKIT1007.severity = warning| Property | Value |
|---|---|
| Rule ID | DEVKIT1007 |
| Category | DynamicsCrm.DevKit |
| Severity | Error |
| Enabled by default | Yes |