DEV Community

Cover image for Stop Writing Plugins Like It’s 2011: Modern Architecture Guide
Mohamed ELgharably
Mohamed ELgharably

Posted on

Stop Writing Plugins Like It’s 2011: Modern Architecture Guide

🌟 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
Enter fullscreen mode Exit fullscreen mode

πŸ— 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 Models

Just 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; }
    }
}
Enter fullscreen mode Exit fullscreen mode
πŸ“¦ Core: Interface
namespace PluginTemplate.Core.Interfaces
{
    public interface IEntityValidationService
    {
        void Validate(EntityModel model, Guid userId);
    }
}
Enter fullscreen mode Exit fullscreen mode
πŸ“¦ 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"));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
πŸ“¦ 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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
βš™οΈ 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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
πŸ”Œ 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“ Architecture Diagram

+-----------------------+
|        Plugins        |
|  (thin orchestration) |
+-----------+-----------+
            |
            v
+-----------+-----------+
|        Factory         |
|   (minimal DI layer)  |
+-----------+-----------+
            |
            v
+-----------+-----------+
|     Infrastructure    |
| Repositories/Services |
+-----------+-----------+
            |
            v
+-----------+-----------+
|      Core Layer       |
|  Interfaces + Models  |
+-----------------------+
Enter fullscreen mode Exit fullscreen mode

✨ 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

Top comments (1)

Collapse
 
mohamed_elgharably_9f7168 profile image
Mohamed ELgharably

If you have questions about DI, plugin packaging, or Dataverse architecture, drop them here β€” I’ll answer everything.