Skip to content

Commit 87e72b7

Browse files
committed
CHORE: Add CLAUDE.md and .editorconfig to prepare for Agent development
1 parent f7186f3 commit 87e72b7

File tree

9 files changed

+748
-49
lines changed

9 files changed

+748
-49
lines changed

.claude/settings.local.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(dotnet build:*)",
5+
"Bash(dotnet test:*)"
6+
],
7+
"deny": [],
8+
"ask": []
9+
}
10+
}

.editorconfig

Lines changed: 526 additions & 0 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
XrmPluginCore is a NuGet library that provides base functionality for developing plugins and custom APIs in Microsoft Dynamics 365/Dataverse. It streamlines plugin development through dependency injection, context wrappers, and automatic registration utilities.
8+
9+
The project consists of:
10+
- **XrmPluginCore**: Main implementation library
11+
- **XrmPluginCore.Abstractions**: Interfaces and enums used for plugin/custom API registration
12+
- **XrmPluginCore.Tests**: Unit and integration tests
13+
14+
## Build & Test Commands
15+
16+
```bash
17+
# Restore dependencies
18+
dotnet restore
19+
20+
# Build the solution (Release configuration)
21+
dotnet build --configuration Release --no-restore
22+
23+
# Run all tests
24+
dotnet test --configuration Release --no-build --verbosity normal
25+
26+
# Run tests for a specific framework
27+
dotnet test --configuration Release --framework net8.0
28+
29+
# Pack NuGet packages locally
30+
./scripts/Pack-Local.ps1
31+
# OR
32+
dotnet pack --configuration Release --no-build --output ./nupkg
33+
```
34+
35+
## Architecture
36+
37+
### Core Plugin Execution Flow
38+
39+
1. **Plugin Base Class** (`XrmPluginCore/Plugin.cs`): All plugins inherit from this class
40+
- Implements `IPlugin.Execute(IServiceProvider)` from Dynamics SDK
41+
- Builds a local scoped service provider for each execution using DI
42+
- Matches incoming context against registered plugin steps/custom APIs
43+
- Invokes the appropriate registered action
44+
45+
2. **Registration Pattern**: Plugins register their steps in the constructor using fluent builders:
46+
- `RegisterStep<TEntity, TService>(EventOperation, ExecutionStage, Action<TService>)` - Modern DI-based approach
47+
- `RegisterPluginStep<T>(EventOperation, ExecutionStage, Action<LocalPluginContext>)` - Legacy approach (deprecated)
48+
- `RegisterAPI<TService>(string name, Action<TService>)` - For Custom APIs
49+
50+
3. **Service Provider Pattern**:
51+
- `ExtendedServiceProvider` wraps the Dynamics SDK's IServiceProvider
52+
- `ServiceProviderExtensions.BuildServiceProvider()` creates a scoped DI container per execution
53+
- Built-in services injected: IPluginExecutionContext, IOrganizationServiceFactory, ITracingService (as ExtendedTracingService), ILogger
54+
- Custom services registered via `OnBeforeBuildServiceProvider()` override
55+
56+
4. **Configuration Builders**:
57+
- `PluginStepConfigBuilder<T>`: Fluent API for configuring plugin step metadata (filtered attributes, images, deployment, etc.)
58+
- `CustomApiConfigBuilder`: Fluent API for configuring Custom API metadata (binding type, allowed custom processing steps, parameters, etc.)
59+
- These builders produce `IPluginStepConfig` and `ICustomApiConfig` consumed by XrmSync for automatic registration
60+
61+
### Project Structure
62+
63+
**XrmPluginCore/** (Main library)
64+
- `Plugin.cs` - Base class for all plugins/custom APIs
65+
- `LocalPluginContext.cs` - Legacy context wrapper (provides OrganizationService, TracingService, etc.)
66+
- `ExtendedTracingService.cs` - Enhanced ITracingService with helper methods
67+
- `ExtendedServiceProvider.cs` - Wrapper for IServiceProvider with DI support
68+
- `Plugins/` - Plugin step registration infrastructure (PluginStepConfigBuilder, ImageSpecification, etc.)
69+
- `CustomApis/` - Custom API registration infrastructure (CustomApiConfigBuilder, RequestParameter, ResponseProperty)
70+
- `Extensions/` - Extension methods for context, service provider, etc.
71+
72+
**XrmPluginCore.Abstractions/** (Shared contracts)
73+
- `Enums/` - EventOperation, ExecutionStage, ExecutionMode, ImageType, CustomApiParameterType, etc.
74+
- `Interfaces/Plugin/` - IPluginStepConfig, IImageSpecification (used by registration tools)
75+
- `Interfaces/CustomApi/` - ICustomApiConfig, IRequestParameter, IResponseProperty
76+
- `IPluginDefinition.cs` - Interface for retrieving plugin step configurations
77+
- `ICustomApiDefinition.cs` - Interface for retrieving custom API configuration
78+
79+
### Dependency Injection
80+
81+
Override `OnBeforeBuildServiceProvider()` in your base plugin class to register services:
82+
83+
```csharp
84+
protected override IServiceCollection OnBeforeBuildServiceProvider(IServiceCollection services)
85+
{
86+
return services
87+
.AddScoped<IMyService, MyService>()
88+
.AddSingleton<IConfiguration, Configuration>();
89+
}
90+
```
91+
92+
Services are scoped to the plugin execution and disposed automatically.
93+
94+
### Multi-Targeting
95+
96+
The library targets both .NET Framework 4.6.2 and .NET 8 to support:
97+
- Traditional on-premise Dynamics 365 deployments (net462)
98+
- Modern Dataverse environments (net8)
99+
100+
Different SDK packages are used per framework:
101+
- net462: `Microsoft.CrmSdk.CoreAssemblies` 9.0.2.59
102+
- net8: `Microsoft.PowerPlatform.Dataverse.Client` 1.2.3
103+
104+
## Key Patterns
105+
106+
### Event Operation Registration
107+
108+
Previously used `EventOperation` enum, but now accepts strings to support custom messages:
109+
```csharp
110+
// Standard operation using enum
111+
RegisterStep<Account, IMyService>(EventOperation.Update, ExecutionStage.PostOperation, s => s.DoSomething())
112+
113+
// Custom message using string
114+
RegisterStep<MyEntity>("custom_CustomMessage", ExecutionStage.PostOperation, s => s.DoSomething())
115+
```
116+
117+
### Plugin Step Images
118+
119+
Images are configured through the builder:
120+
```csharp
121+
RegisterStep<Account, IAccountService>(EventOperation.Update, ExecutionStage.PostOperation, s => s.Process())
122+
.AddFilteredAttributes(x => x.Name, x => x.AccountNumber)
123+
.AddPreImage("PreImage", x => x.Name, x => x.Revenue)
124+
.AddPostImage("PostImage", x => x.Name, x => x.Revenue);
125+
```
126+
127+
### Custom APIs
128+
129+
Custom APIs use a single registration per class:
130+
```csharp
131+
public class MyCustomApi : BasePlugin
132+
{
133+
public MyCustomApi()
134+
{
135+
RegisterAPI<IMyService>("custom_MyApiName", service => service.Execute())
136+
.WithBindingType(BindingType.Entity)
137+
.WithBoundEntityLogicalName("account")
138+
.AddRequestParameter("InputParam", CustomApiParameterType.String, isOptional: false)
139+
.AddResponseProperty("OutputValue", CustomApiParameterType.Integer);
140+
}
141+
}
142+
```
143+
144+
## Deployment
145+
146+
Plugins are deployed to Dynamics 365 as ILMerged assemblies containing all dependencies. Use ILRepack to merge:
147+
- XrmPluginCore.dll
148+
- XrmPluginCore.Abstractions.dll
149+
- Microsoft.Extensions.DependencyInjection.dll
150+
- Microsoft.Extensions.DependencyInjection.Abstractions.dll
151+
- Microsoft.Bcl.AsyncInterfaces.dll
152+
153+
Registration is automated using XrmSync (https://github.com/delegateas/XrmSync), which reads the IPluginDefinition and ICustomApiDefinition interfaces.
154+
155+
## Versioning
156+
157+
Version numbers are managed through CHANGELOG.md files:
158+
- `XrmPluginCore/CHANGELOG.md` for the main library
159+
- `XrmPluginCore.Abstractions/CHANGELOG.md` for abstractions
160+
161+
The `Set-VersionFromChangelog.ps1` script updates .csproj files from CHANGELOG during CI/CD.

XrmPluginCore.Tests/CustomAPITests.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace XrmPluginCore.Tests
1414
public class CustomAPITests
1515
{
1616
[Fact]
17-
public void Execute_NullServiceProvider_ShouldThrowArgumentNullException()
17+
public void ExecuteNullServiceProviderShouldThrowArgumentNullException()
1818
{
1919
// Arrange
2020
var customApi = new TestCustomAPI();
@@ -24,7 +24,7 @@ public void Execute_NullServiceProvider_ShouldThrowArgumentNullException()
2424
}
2525

2626
[Fact]
27-
public void Execute_ValidRegistration_ShouldExecuteActionWithLocalPluginContext()
27+
public void ExecuteValidRegistrationShouldExecuteActionWithLocalPluginContext()
2828
{
2929
// Arrange
3030
var customApi = new TestCustomAPI();
@@ -39,7 +39,7 @@ public void Execute_ValidRegistration_ShouldExecuteActionWithLocalPluginContext(
3939
}
4040

4141
[Fact]
42-
public void Execute_ValidRegistration_ShouldExecuteActionWithServiceProvider()
42+
public void ExecuteValidRegistrationShouldExecuteActionWithServiceProvider()
4343
{
4444
// Arrange
4545
var customApi = new TestCustomAPIServiceProvider();
@@ -54,7 +54,7 @@ public void Execute_ValidRegistration_ShouldExecuteActionWithServiceProvider()
5454
}
5555

5656
[Fact]
57-
public void Execute_NoRegistration_ShouldNotExecuteAction()
57+
public void ExecuteNoRegistrationShouldNotExecuteAction()
5858
{
5959
// Arrange
6060
var customApi = new TestNoRegistrationCustomAPI();
@@ -68,7 +68,7 @@ public void Execute_NoRegistration_ShouldNotExecuteAction()
6868
}
6969

7070
[Fact]
71-
public void Execute_FaultException_ShouldRethrow()
71+
public void ExecuteFaultExceptionShouldRethrow()
7272
{
7373
// Arrange
7474
var mockProvider = new MockServiceProvider();
@@ -86,7 +86,7 @@ public void Execute_FaultException_ShouldRethrow()
8686
}
8787

8888
[Fact]
89-
public void RegisterCustomAPI_MultipleRegistrations_ShouldThrowInvalidOperationException()
89+
public void RegisterCustomAPIMultipleRegistrationsShouldThrowInvalidOperationException()
9090
{
9191
// Arrange
9292
var customApi = new TestMultipleRegistrationCustomAPI();
@@ -96,7 +96,7 @@ public void RegisterCustomAPI_MultipleRegistrations_ShouldThrowInvalidOperationE
9696
}
9797

9898
[Fact]
99-
public void GetRegistration_ValidRegistration_ShouldReturnConfiguration()
99+
public void GetRegistrationValidRegistrationShouldReturnConfiguration()
100100
{
101101
// Arrange
102102
var customApi = new TestCustomAPI();
@@ -111,7 +111,7 @@ public void GetRegistration_ValidRegistration_ShouldReturnConfiguration()
111111
}
112112

113113
[Fact]
114-
public void GetRegistration_WithConfiguration_ShouldReturnFullConfiguration()
114+
public void GetRegistrationWithConfigurationShouldReturnFullConfiguration()
115115
{
116116
// Arrange
117117
var customApi = new TestCustomAPIWithConfig();
@@ -139,7 +139,7 @@ public void GetRegistration_WithConfiguration_ShouldReturnFullConfiguration()
139139
}
140140

141141
[Fact]
142-
public void OnBeforeBuildServiceProvider_ShouldAllowModification()
142+
public void OnBeforeBuildServiceProviderShouldAllowModification()
143143
{
144144
// Arrange
145145
var customApi = new TestServiceProviderModificationCustomAPI();
@@ -153,7 +153,7 @@ public void OnBeforeBuildServiceProvider_ShouldAllowModification()
153153
}
154154

155155
[Fact]
156-
public void Execute_ShouldTraceEntryAndExit()
156+
public void ExecuteShouldTraceEntryAndExit()
157157
{
158158
// Arrange
159159
var customApi = new TestCustomAPI();
@@ -188,7 +188,7 @@ public void Execute_ShouldTraceEntryAndExit()
188188
}
189189

190190
[Fact]
191-
public void Execute_ShouldTraceExecutionInfo()
191+
public void ExecuteShouldTraceExecutionInfo()
192192
{
193193
// Arrange
194194
var customApi = new TestCustomAPI();

XrmPluginCore.Tests/Integration/CustomAPIIntegrationTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace XrmPluginCore.Tests.Integration
1212
public class CustomAPIIntegrationTests
1313
{
1414
[Fact]
15-
public void FullCustomAPIWorkflow_ShouldExecuteCorrectly()
15+
public void FullCustomAPIWorkflowShouldExecuteCorrectly()
1616
{
1717
// Arrange
1818
var customApi = new IntegrationTestCustomAPI();
@@ -39,7 +39,7 @@ public void FullCustomAPIWorkflow_ShouldExecuteCorrectly()
3939
}
4040

4141
[Fact]
42-
public void CustomAPIRegistration_ShouldContainCorrectConfiguration()
42+
public void CustomAPIRegistrationShouldContainCorrectConfiguration()
4343
{
4444
// Arrange
4545
var customApi = new IntegrationTestCustomAPI();
@@ -66,7 +66,7 @@ public void CustomAPIRegistration_ShouldContainCorrectConfiguration()
6666
}
6767

6868
[Fact]
69-
public void BoundCustomAPI_ShouldConfigureBinding()
69+
public void BoundCustomAPIShouldConfigureBinding()
7070
{
7171
// Arrange
7272
var customApi = new BoundCustomAPI();
@@ -80,7 +80,7 @@ public void BoundCustomAPI_ShouldConfigureBinding()
8080
}
8181

8282
[Fact]
83-
public void PrivateCustomAPI_ShouldBePrivate()
83+
public void PrivateCustomAPIShouldBePrivate()
8484
{
8585
// Arrange
8686
var customApi = new PrivateCustomAPI();

XrmPluginCore.Tests/Integration/PluginIntegrationTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace XrmPluginCore.Tests.Integration
1313
public class PluginIntegrationTests
1414
{
1515
[Fact]
16-
public void FullPluginWorkflow_CreateAccount_ShouldExecuteCorrectly()
16+
public void FullPluginWorkflowCreateAccountShouldExecuteCorrectly()
1717
{
1818
// Arrange
1919
var plugin = new IntegrationTestPlugin();
@@ -51,7 +51,7 @@ public void FullPluginWorkflow_CreateAccount_ShouldExecuteCorrectly()
5151
}
5252

5353
[Fact]
54-
public void PluginWithMultipleImages_ShouldAccessAllImages()
54+
public void PluginWithMultipleImagesShouldAccessAllImages()
5555
{
5656
// Arrange
5757
var plugin = new MultipleImagesPlugin();
@@ -84,7 +84,7 @@ public void PluginWithMultipleImages_ShouldAccessAllImages()
8484
}
8585

8686
[Fact]
87-
public void PluginRegistrations_ShouldContainCorrectConfiguration()
87+
public void PluginRegistrationsShouldContainCorrectConfiguration()
8888
{
8989
// Arrange
9090
var plugin = new IntegrationTestPlugin();

0 commit comments

Comments
 (0)