Mpt.Framework is the opinionated set of building blocks the SoftwareOne Marketplace uses to ship platform services in .NET. It gives you a coherent answer to the questions every backend hits on day one:
- How do I model entities with stable identity and optimistic concurrency?
- How do I accept HTTP PATCH payloads and tell omitted apart from explicitly null — and validate them cleanly?
- How do I orchestrate long-running work without rewriting saga plumbing in every service?
- How do I publish events between modules with server-side filtering, and emit lifecycle events automatically when entities change?
- How do I wire RQL queries, policy-enforced writes, and lifecycle hooks into a single repository?
Each capability ships as its own NuGet package so you can adopt one piece without buying the whole stack. Domain code talks to small *.Abstractions packages; infrastructure choices (MassTransit, EF Core, Azure Service Bus, FluentValidation) live in the implementation packages so they never leak into your application layer.
The family follows a consistent layout: every capability has an Abstractions package (interfaces, POCOs, zero infra deps), a main package (the engine), and — where it makes sense — an EFCore add-on.
| Package | What it does |
|---|---|
Mpt.Framework.Abstractions |
Foundational identity & versioning contracts: IPlatformObject, IPlatformEntity, IRevisable. Every other package references this. |
Mpt.Framework.Delta |
Strongly-typed JSON partial-update model. Delta<T> and DeltaBuilder distinguish absent from explicitly null so HTTP PATCH actually behaves like PATCH. Includes ASP.NET Core binding. |
Mpt.Framework.Delta.Validation |
FluentValidation integration for Delta<T>. DeltaValidator<T>, RuleForDelta, MustBeDefined, WhenDefined, with JSON-path-aware error messages. |
Mpt.Framework.Operation.Abstractions |
Contracts for long-running async work: Operation<TContract, TTask>, IOperationDispatcher, context interfaces, result POCOs. No MassTransit or EF Core dependency. |
Mpt.Framework.Operation |
MassTransit-based operation engine. Start → produce tasks → process in parallel → finish, with in-memory and Azure Service Bus transports out of the box. |
Mpt.Framework.Operation.EFCore |
SQL Server saga persistence for the Operation engine via EF Core, with optimistic concurrency and a fluent UseSqlServerPersistence() helper. |
Mpt.Framework.MessageHub.Abstractions |
Pub/sub contracts: EventMessage, IMessageHubPublisher, InputStreamProvider, InputStreamFilter. Use these from your application/domain layer. |
Mpt.Framework.MessageHub |
Module-to-module event bus on MassTransit. Server-side SQL filtering on Azure Service Bus, in-memory transport for tests, optional IPlatformEventEmitter for lifecycle events. |
Mpt.Framework.Mapping.Abstractions |
Mapper contracts: IDynamicEntityMapper, IInMemoryEntityMapper. Application layers depend on these without referencing RQL. |
Mpt.Framework.Mapping |
Reflection-driven, RQL-aware mapper that updates persistence entities in place from view models — collections, nested objects, references — and reports the changed-property count. |
Mpt.Framework.Mapping.EFCore |
EF Core flavour of the mapper: looks up platform-entity references through the DbContext (FK assignment, navigation loading, removal tracking). |
Mpt.Framework.Persistence.Abstractions |
Repository / unit-of-work / query / policy contracts and lifecycle-hook interfaces. Pure abstractions — no EF Core, no RQL. |
Mpt.Framework.Persistence |
The repository engine: RQL-driven reads, policy-enforced writes, lifecycle hooks (OnCreatingAsync, OnUpdatingAsync, OnDeletingAsync), and automatic after-save events through MessageHub. |
Mpt.Framework.Persistence.EFCore |
EF Core flavour: EfCoreRepository<TDbEntity, TEntity> over a user-supplied DbContext, transactional saves, composed with the EF Core mapper. |
- Clean architecture by default. Abstractions packages let domain code depend on interfaces; MassTransit, EF Core and Azure Service Bus stay quarantined in implementation packages.
- PATCH that actually patches.
Delta<T>preserves which fields the client sent, so updates do not accidentally null out unspecified properties. - RQL-native. Reads and mappings are powered by Mpt.Rql, so filtering, sorting, paging and projections come for free.
- Eventing without ceremony. A repository save automatically emits
GenericCreatedEvent<TEntity>/GenericUpdatedEvent<TEntity>/GenericDeletedEvent<TEntity>through MessageHub — opt in per entity, no boilerplate. - Long-running work that survives restarts. Operations are MassTransit sagas; pair
Mpt.Framework.OperationwithMpt.Framework.Operation.EFCorefor durable SQL Server state. - In-memory variants for tests. Every package that talks to infrastructure ships an in-memory implementation so unit tests stay fast.
You rarely install everything at once. Most services start with Persistence, MessageHub, and Delta:
dotnet add package Mpt.Framework.Persistence.EFCore
dotnet add package Mpt.Framework.MessageHub
dotnet add package Mpt.Framework.Delta
dotnet add package Mpt.Framework.Delta.Validationusing Mpt.Framework;
public sealed class Invoice : IPlatformEntity
{
public string Id { get; set; } = default!;
public int Revision { get; set; }
public string Number { get; set; } = default!;
public decimal Amount { get; set; }
}// Program.cs
builder.Services.AddRql();
builder.Services.AddMessageHub(opts => opts.UseInMemoryTransport());
builder.Services.AddPersistence()
.AddEntity<InvoiceDbEntity, Invoice>(entity =>
{
entity.Configure<InvoiceConfiguration>();
});
builder.Services.AddDbContext<AppDbContext>(/* ... */);public sealed class UpdateInvoice
{
public Delta<string> Number { get; set; }
public Delta<decimal> Amount { get; set; }
}
public sealed class UpdateInvoiceValidator : DeltaValidator<UpdateInvoice>
{
public UpdateInvoiceValidator()
{
RuleForDelta(x => x.Number).WhenDefined(rule => rule.NotEmpty());
RuleForDelta(x => x.Amount).WhenDefined(rule => rule.GreaterThan(0));
}
}Once the request lands, the repository, mapper, lifecycle hooks and event emitter cooperate to apply only the fields the client actually sent, run the configured update policy, bump the Revision, and publish an InvoiceUpdated event downstream.
For end-to-end recipes — including operation orchestration, EF Core composition, and custom event producers — see the per-package READMEs linked in the table above.
Requires .NET 10 SDK (10.0.x).
git clone https://github.com/softwareone/mpt-framework-net.git
cd mpt-framework-net
dotnet restore Mpt.Framework.slnx
dotnet build Mpt.Framework.slnx --no-restore --configuration Release
dotnet test Mpt.Framework.slnx --no-build --configuration Release \
--results-directory ./TestResults/ \
--collect:"XPlat Code Coverage" --settings coverlet.runsettingsThe same three commands run in CI on every push and pull request — see .github/workflows/sonar.yaml. Coverage is reported in OpenCover format and uploaded to SonarCloud.
src/
abstractions/ Mpt.Framework.Abstractions
delta/ Mpt.Framework.Delta, Mpt.Framework.Delta.Validation
operation/ Mpt.Framework.Operation{,.Abstractions,.EFCore}
messagehub/ Mpt.Framework.MessageHub{,.Abstractions}
mapping/ Mpt.Framework.Mapping{,.Abstractions,.EFCore}
persistence/ Mpt.Framework.Persistence{,.Abstractions,.EFCore}
tests/
<one folder per family, mirroring src/>
The solution file is Mpt.Framework.slnx (the new XML solution format introduced in .NET 9).
We welcome contributions to enhance the framework. To get started:
- Fork the repository
- Create your feature branch (
git checkout -b feature/your-capability) - Commit your changes (
git commit -m 'Add your capability') - Push to the branch (
git push origin feature/your-capability) - Open a Pull Request
Please run dotnet test locally before opening the PR; CI runs the same suite on Ubuntu under .NET 10.
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
- The SoftwareOne Marketplace team for creating and maintaining this framework
- All contributors who have helped improve it
Developed by the SWO Marketplace team