This content originally appeared on DEV Community and was authored by Mohamed ELgharably
๐ Modern Dataverse Plugin Architecture (2025 Edition)
A Clean, Testable, Maintainable, and DI-Friendly Template for Power Platform Developers
A complete, ready-to-use architecture template you can drop into your next Dataverse / Dynamics 365 project.
๐ฅ Why This Article?
Most Dataverse plugins still follow the old 2011 pattern:
- Logic inside
Execute() - Hard-coded field names
- No testability
- Zero separation of concerns
- Hard to extend
- Not reusable outside plugins
- Difficult to maintain
This article gives you a modern, scalable, testable plugin architecture with:
โ Clean separation
โ Supports multi-project structure
โ Minimal DI (no heavy libraries)
โ Test-friendly
โ Reusable in Azure Functions / Custom APIs
โ NuGet-based plugin deployment
โ No system-specific logic
โ Perfect as a starter template
๐งฑ Architecture Overview
/PluginSolution
โ
โโโ Core
โ โโโ Interfaces
โ โโโ Models
โ โโโ Enums
โ
โโโ Infrastructure
โ โโโ Repositories
โ โโโ Services
โ
โโโ Plugins
โ โโโ PluginBase.cs (from CLI template)
โ โโโ SamplePlugin.cs
โ
โโโ Startup
โโโ PluginFactory.cs
๐ Layer Explanation
1. Core Layer (Pure, CRM-agnostic)
Contains:
- Interfaces
- Lightweight models
- Enums
- Zero dependency on Microsoft.Xrm.Sdk Benefits:
- 100% testable
- Reusable in Azure Functions / Custom APIs
- Pure C# domain layer
2. Infrastructure Layer
Contains:
- Repositories
- Dataverse operations
- FetchXML logic
- Business services This layer knows about Dataverse so the rest of the system doesnโt have to.
3. Plugins Layer
Responsible for:
- Orchestration
- Extracting context
- Mapping
Entity โ Core Model - Calling services The plugin stays thin and easy to reason about.
4. Startup / Factory Layer (Minimal DI)
Instead of heavy DI (which causes sandbox issues), we use a simple factory pattern:
- No dependency conflicts
- No BCL async interface issues
- No slow DI container startup
- No Microsoft.Extensions.* packages needed Small. Fast. Compatible with Sandbox.
โก Modern Deployment: PAC CLI + .nupkg Package
In 2025, plugins should not be deployed as DLLs.
Microsoft now provides:
pac plugin init --outputDirectory . --skip-signing
This command:
- Creates a structured plugin project
- Includes PluginBase + ILocalPluginContext
- Supports NuGet packaging
- Removes need for manual DLL signing
๐ฏ Why --skip-signing?
Because NuGet-based plugin deployment does not require strong naming.
Benefits:
- No shared signing keys
- No assembly conflicts
- Smooth CI/CD
- Faster team collaboration
๐งฉ Minimal DI (Factory Pattern)
Heavy DI causes:
- Slow plugin execution
- Version conflicts
- Sandbox restrictions
- Hard-to-debug runtime errors
So we use:
Plugin โ Factory โ Services โ Repositories
This gives you DI benefits without DI overhead.
๐งฉ Optional: Using Early-Bound Classes (Highly Recommended)
Although the template in this article uses a lightweight EntityModel for simplicity,
the architecture is fully compatible with Early-Bound classes
Note:
The Power Platform CLI can now generate Early-Bound classes for you automatically using:pac modelbuilder build --outputDirectory ModelsJust drop the generated models into a separate project and reference it from your Plugin + Infrastructure layers.
๐ Template Code (Copy/Paste)
A completely generic, reusable template.
๐ฆ Core: Model
namespace PluginTemplate.Core.Models
{
public class EntityModel
{
public Guid Id { get; set; }
public string LogicalName { get; set; }
public IDictionary<string, object> Attributes { get; set; }
}
}
๐ฆ Core: Interface
namespace PluginTemplate.Core.Interfaces
{
public interface IEntityValidationService
{
void Validate(EntityModel model, Guid userId);
}
}
๐ฆ Infrastructure: Repository Template
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using PluginTemplate.Core.Interfaces;
namespace PluginTemplate.Infrastructure.Repositories
{
public interface ISampleRepository
{
Entity RetrieveEntity(Guid id);
}
public class SampleRepository : ISampleRepository
{
private readonly IOrganizationService _service;
public SampleRepository(IOrganizationService service)
{
_service = service;
}
public Entity RetrieveEntity(Guid id)
{
return _service.Retrieve(
"xyz_customtable",
id,
new ColumnSet("xyz_textfield"));
}
}
}
๐ฆ Infrastructure: Service Template
using PluginTemplate.Core.Interfaces;
using PluginTemplate.Core.Models;
namespace PluginTemplate.Infrastructure.Services
{
public class EntityValidationService : IEntityValidationService
{
public void Validate(EntityModel model, Guid userId)
{
// Add validation logic (optional)
}
}
}
โ๏ธ Factory (Minimal DI)
using Microsoft.Xrm.Sdk;
using PluginTemplate.Core.Interfaces;
using PluginTemplate.Infrastructure.Repositories;
using PluginTemplate.Infrastructure.Services;
namespace PluginTemplate.Startup
{
public static class PluginFactory
{
public static IEntityValidationService CreateValidationService(
IOrganizationService service,
ITracingService tracing)
{
var repository = new SampleRepository(service);
return new EntityValidationService();
}
}
}
๐ Plugin Template
using System;
using Microsoft.Xrm.Sdk;
using PluginTemplate.Startup;
using PluginTemplate.Core.Models;
using PluginTemplate.Core.Interfaces;
public class SamplePlugin : PluginBase
{
public SamplePlugin(string unsecure, string secure)
: base(typeof(SamplePlugin)) { }
protected override void ExecuteDataversePlugin(ILocalPluginContext ctx)
{
var context = ctx.PluginExecutionContext;
var tracing = ctx.TracingService;
var org = ctx.OrgSvcFactory.CreateOrganizationService(context.UserId);
if (!(context.InputParameters["Target"] is Entity target))
return;
var model = new EntityModel
{
Id = target.Id,
LogicalName = target.LogicalName,
Attributes = target.Attributes
};
var service = PluginFactory.CreateValidationService(org, tracing);
service.Validate(model, context.UserId);
}
}
๐ Architecture Diagram
+-----------------------+
| Plugins |
| (thin orchestration) |
+-----------+-----------+
|
v
+-----------+-----------+
| Factory |
| (minimal DI layer) |
+-----------+-----------+
|
v
+-----------+-----------+
| Infrastructure |
| Repositories/Services |
+-----------+-----------+
|
v
+-----------+-----------+
| Core Layer |
| Interfaces + Models |
+-----------------------+
โจ Benefits of This Architecture
๐น 1. Testable
Core + Infrastructure can reach 100% test coverage.
๐น 2. Clean Separation
Plugin โ Service โ Repository.
๐น 3. Reusable
The same services can be used in:
- Plugins
- Custom APIs
- Azure Functions
- Virtual Tables
๐น 4. Minimal Dependencies
No need for:
- Microsoft.Extensions.DependencyInjection
- Async Interfaces
- External DI frameworks
This content originally appeared on DEV Community and was authored by Mohamed ELgharably
Mohamed ELgharably | Sciencx (2025-11-28T13:03:54+00:00) Stop Writing Plugins Like Itโs 2011: Modern Architecture Guide. Retrieved from https://www.scien.cx/2025/11/28/stop-writing-plugins-like-its-2011-modern-architecture-guide-4/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.