SF

01 / Software engineer

Soltani Fedi

Full Stack .NET Developer

Multi-tenant SaaS architecture

I build the invisible layer of SaaS platforms — the one nobody notices, until it's missing.

Tunis, Tunisia

Open to opportunities

LinkedIn

02 / Approach

TENANT ATENANT CTENANT BGlobal Query Filter
Query stopped at the boundary

No query ever crosses a tenant boundary.

EF Core Global Query Filters and per-tenant JWT claims, enforced across every module. Isolation is not a check bolted onto each call: it is carried by the data access layer itself.

3+years of experience
100+CQRS handlers migrated
.NET 5→10versions shipped
6companies

03 / Expertise

Four grounds

I

Multi-tenant isolation

EF Core Global Query Filters, per-tenant JWT claims, entity-level RBAC across Admin, Manager and Collaborator. No data crosses a tenant boundary.

EF Core · JWT · RBAC

Read the case study
II

Security and integrity

Atomic refresh token rotation with reuse detection, session revocation, PostgreSQL optimistic concurrency via xmin with HTTP 409 conflicts, real-time editing presence over SignalR.

JWT · xmin · SignalR

Read the case study
III

Architecture and contracts

Clean Architecture, CQRS, a custom OSS dispatcher replacing 100+ handlers, Application Services, *Request contracts and a refetch-GET pattern.

Clean Architecture · CQRS

Read the case study
IV

Quality and delivery

Serilog with multi-tenant correlation, PostgreSQL integration tests under Testcontainers, GitHub Actions quality gates, Docker deployment on Azure App Service.

Serilog · Testcontainers · Docker

Read the case study

04 / Access control

Who sees what

Pick a role and a tenant. The same query returns different rows, and some links become unreachable. This is the access model shipped at TeamXtend, reproduced here on the client.

Role
Signed-in tenant

Navigation

  • ProjectsYes
  • TeamsYes
  • UsersNo
  • SettingsNo

WriteYes

GET /api/projects

  • PRJ-104Refonte portail client
  • PRJ-118Migration EF Core 10
  • PRJ-131Audit contrats API

3 rows visible · 2 out of scope

Two distinct mechanisms are at work. The Global Query Filter decides which rows exist for the query at all: a Manager on tenant North cannot read a row from tenant South, even by forging the identifier. Navigation flags decide what is clickable — they improve the experience but protect nothing, because the API re-checks on every call.

05 / Concurrency

The conflict you never see

Two users edit the same record. Without optimistic concurrency, the second silently overwrites the first and nobody finds out. Try it.

User Axmin 1042
User Bxmin 1042

db → "Refonte du portail client" · xmin 1042

PostgreSQL exposes a system version counter, xmin, incremented on every write. EF Core compares it before committing: if the value moved, the transaction is rejected rather than overwriting. The presence banner arrives over SignalR and warns before the collision even happens.

06 / Experience

Six roles, three years

  1. Full Stack .NET DeveloperPermanent · Tunis

    Time & Performance — multi-tenant SaaS platform for time tracking and performance management

    • Identified a licensing compliance risk with MediatR and AutoMapper (dual-licensed since 2025) and led the migration to a custom OSS dispatcher and Application Services: 100+ CQRS handlers replaced, 9 mapping profiles moved to Mapperly, and a Read/Write Repository separation with the Specification pattern across 5 domain aggregates.
    • Hardened the JWT lifecycle: atomic one-time refresh token rotation via compare-and-swap with reuse detection, revocation of every session on detected replay and on password change, and hot revalidation of Identity roles with token invalidation.
    • Implemented end-to-end optimistic concurrency: PostgreSQL xmin as the EF Core token with conflicts surfaced as HTTP 409, the token echoed from Angular detail screens and edit modals, and real-time Editing Presence over SignalR to warn before a collision happens.
    • Built the multi-tenant data isolation layer (EF Core Global Query Filters, per-tenant JWT claims) and hardened multi-role RBAC: entity-level permissions and navigation flags (canNavigate / canManage) exposed by the API and enforced front and back.
    • Standardised inbound API contracts (*Request) and lightweight mutation DTOs, with a refetch-GET pattern to reduce over-fetching; a contract audit across 4 modules delivered 11+ fixes, complemented on the frontend by lazy-loaded dropdowns and RxJS caching.
    • Centralised application logging with Serilog on the ASP.NET Core host (.NET 10): structured JSON logs, multi-tenant enrichment (TenantId, UserId, CorrelationId, X-Correlation-ID) and configuration-driven setup via appsettings.
    • Strengthened test coverage across the stack: PostgreSQL integration tests under Testcontainers (409 conflicts, tenant isolation), unit tests for API controllers, Application and Infrastructure layers (xUnit, Moq, FluentAssertions) and Angular (Karma/Jasmine), wired into GitHub Actions quality gates.
    • Delivered and operated the multi-tenant SaaS API packaged as a Docker container on Azure App Service via Azure Container Registry (Dev and Staging environments).

    Stack.NET 10 · Angular 18 · PostgreSQL · JWT · SignalR · Serilog · Testcontainers · Docker · Azure

  2. Full Stack .NET DeveloperFreelance · Tunis

    AutoCAD plugin for hydraulic structures and a licensing platform

    • Built a .NET 8 AutoCAD plugin automating culvert and technical sheet creation from an alignment and a surface, including geometric and hydraulic calculations and 3D model generation.
    • Integrated the plugin with HY-8 (FHWA) software via automated export of calculated data, to verify hydraulic results.
    • Built a custom licensing platform: hardware identification, a REST verification API and a heartbeat mechanism blocking access if the licence becomes invalid.

    Stack.NET 8 · AutoCAD .NET API · Razor Pages · Dapper · SQL Server

  3. Full Stack .NET DeveloperFreelance · Tunis

    GSL-MMM — ERP for delivery and customer management

    • Developed the invoice discharge management module, improving financial tracking of deliveries.
    • Built the inter-warehouse delivery module (planning, tracking and traceability of stock transfers between sites) and a customer return template, alongside targeted bug fixes across customer management workflows.

    Stack.NET 5 · Entity Framework · SQL Server · jQuery

  4. Full Stack .NET Core DeveloperConsultant, Quebec Ministry of Transport

    APVHN — permit management for oversized convoys

    • Designed the application architecture of a government-grade permit management system and developed the core business modules: expertise, vehicles, permit generation.
    • Managed user authentication and access controls, and integrated external services to automate data exchanges.
    • Led the progressive migration from .NET 6 to .NET 8 across the whole solution and implemented xUnit unit tests on the core modules.
    • Built Studio 44, a Pilates studio management app: classes, subscriptions, registrations, with a UX/UI redesign of the sign-up flow to reduce drop-off.

    Stack.NET 6/8 · Razor Pages · SQL Server · xUnit · Azure DevOps

  5. Full Stack .NET DeveloperExternal consultant, Tunisie Telecom

    Workflow Backbone — national internet subscription management platform

    • Identified and resolved performance bottlenecks through SQL profiling and execution plan analysis on a nationally deployed platform.
    • Integrated third-party web services (REST, SOAP) to enrich request qualification and acceptance features, and improved stored procedures on SQL Server and PostgreSQL.

    Stack.NET Standard · SOAP · SQL Server · PostgreSQL

  6. Full Stack .NET DeveloperTunis

    Multichannel ticketing software

    • Developed new features (IT asset management) and automated business workflows: prioritisation, SLA, notifications.
    • Improved performance via caching and SQL query optimisation, contributed to the module migration to .NET Core and optimised CI/CD pipelines on TeamCity.
    • Delivered in Agile Scrum, mentoring junior developers through code reviews and pair programming sessions.

    Stack.NET Framework 4.8 · React · TypeScript · TeamCity

08 / Stack

Technical stack

Core

  • C#
  • .NET 10
  • ASP.NET Core
  • EF Core 10
  • TypeScript
  • Angular 18
  • RxJS

Architecture

  • Clean Architecture
  • CQRS
  • Custom Dispatcher
  • Application Services
  • Specification Pattern
  • Mapperly
  • FluentValidation

Data

  • PostgreSQL
  • SQL Server
  • xmin
  • Global Query Filters
  • T-SQL

Security and APIs

  • ASP.NET Core Identity
  • JWT
  • RBAC
  • SignalR
  • REST
  • Swagger / OpenAPI

Observability and testing

  • Serilog
  • xUnit
  • Moq
  • FluentAssertions
  • Testcontainers

Cloud and CI/CD

  • Docker
  • Azure App Service
  • Azure Container Registry
  • GitHub Actions
  • Azure DevOps
  • TeamCity

Working knowledge

  • React
  • Next.js
  • Node.js
  • Payload CMS
  • Blazor
  • Dapper
  • SOAP
  • AutoCAD .NET API

Hover for usage context

10 / Questions

Frequently asked

How do you isolate customer data in a .NET SaaS application?

By carrying isolation in the data access layer rather than in every query. With EF Core, a Global Query Filter applied to all relevant entities automatically appends the tenant condition to every LINQ query generated. The tenant comes from a validated JWT claim, never from the request body. A developer who forgets a WHERE clause then cannot expose another customer's data.

Related case studyMulti-tenant isolation: isolating without slowing the API
xmin or a version column: which to pick for optimistic concurrency?

On PostgreSQL, xmin is preferable in most cases. It is a system column the database already maintains on every write: no column to add, no migration, no risk of forgetting to increment it. An application-level version column keeps its value if you need to stay portable across several database engines.

Related case studyOptimistic concurrency: the conflict you never see
Should you leave MediatR now that it is commercially licensed?

It depends on actual usage. If you rely on pipelines, notifications and the library's full behaviour, paying for the licence is probably the sensible call. If you only ever call Send, as many teams do, the surface to replace fits in one interface and one dispatch class — a day's work against a recurring cost.

Related case studyGetting off MediatR and AutoMapper, in production
Is Fedi Soltani available for a role or an engagement?

Yes, open to opportunities around .NET, multi-tenant SaaS and backend architecture, remote or with relocation. Based in Tunis. Reach out by email at soltanifedi68@gmail.com or via LinkedIn.

11 / Contact

Let's talk architecture

Open to roles and engagements around .NET, multi-tenant SaaS and backend architecture.

Send an emailLinkedInDownload CV
Phone
+216 53 064 275 · +216 55 517 224
Based in
Tunis, Tunisia — open to remote and relocation
Education
National Engineering Degree in Computer Science — ESPRIT, Tunis (2022)
Languages
Arabic (native) · French (professional) · English (B2)