OFQ-00006 VIPER-Style Architecture in C# Front-Ends
Last updated: 28 August 2025
From August 2025, Ofqual now implements a flavour of ‘VIPER’ Architecture for C# Front-ends. This comprises of:
- Views, which is the presentational component
- Interactors, which manipulate raw data and data from the view and serve as the business logic layer. In Ofqual, these comprise of Services, Mappers and Repositories
- Presenters, which bridge the View and Interactor components. In Ofqual, we name these as ViewModels, as they effectively serve as the model used within the View
- Entities, which contain raw data from data sources; In Ofqual, we name these as Models
- Routers, which control navigation on the site. In Ofqual, we name these as Controllers
The adjustments in conventions are to smooth onboarding on to this kind of architecture, as it is generally more modern (and thus less familiar) to developers; most legacy applications use a simpler MVC architecture. Legacy applications should not be ported over to VIPER architecture unless a full rewrite is permitted.
The benefits of this architecture include:
- Clear separation of concerns; typically data coming in from an API needs to be manipulated to be ideal for presenting to a user/view. This architecture means we can do this without muddying data models with presentational items too much, and vice versa
- The architecture aligns much with how we prefer to architect C# APIs, as these also have a separation between raw data and data sent out to the client, as well as similar patterns such as Controllers, Services and Clients. This is why some of the naming conventions for VIPER have been changed, to ensure there is a level of familiarity maintained when switching between Front and Back-end, as we work as full-stack teams
Requirement(s)
- C Sharp Front-ends MUST use Views
- C Sharp Front-ends MUST use Interactors (Services, Mappers and Clients)
- C Sharp Front-ends MUST use Presenters (ViewModels)
- C Sharp Front-ends MUST use Entities (Models)
- C Sharp Front-ends MUST use Routers (Controllers)
C Sharp Front-ends MUST use Views
Views are part of the presentational layer of the front-end and work much in the same way as MVC Views. Important aspects are:
- Views are served by Routers (Controllers), typically as a return from a Router function
- Views that require data should have a Presenter (ViewModel) passed in
- Views should be stored in the Web project of the solution in a Views folder
- Views are .cshtml files; these get added in Program.cs using “AddControllersWithViews”, instead of using Razor Pages
- Partial Views, which represent fragments that can then be imported into another view, should have a leading underscore in their name to indicate that they are a partial
C Sharp Front-ends MUST use Interactors (Services, Mappers and Clients)
Interactors are the heart of the system and control the interaction between the data layer and the presentational layer. Unlike in standard VIPER, which uses singular Interactors, we split this out into three core components.
- Clients, which are used to directly communicate with a data persistence source, such as another API. This is the closest thing to the I/O layer of the application, and should be specified to communicate with a specific API. To reduce boilerplate, we mainly use HttpClientFactory for API Clients and have a singular function, GetClientAsync, to configure the HttpClient appropriately for a given API
- Mappers, which are static classes that Map Entities (Models) to Presenters (ViewModels) and vice versa. The only logic in these should be from converting items to other items, and should only contain atomic functions that do not mutate other objects outside the function.
- Services, which are not static classes, are mainly used to process business logic and co-ordinate the usage of Clients and Mappers. For instance, a service may be used to obtain some data via a client, and then use a mapper to convert that data from an Entity (Model) to a Presenter (ViewModel)
All interactors should obey the single-responsibility principle; do not write monolithic interactors that try to handle all tasks for that type of interactor (e.g. a Mapper class that handled mapping all of a singular front-ends mapping needs).
Both Clients and Services may be held in the Infrastructure project; Mappers should be held in the Web project as for scoping reasons, as Presenters (ViewModels) are held in this project too (putting the Mapper in the Infrastructure project usually results in circular dependency problems)
C Sharp Front-ends MUST use Presenters (ViewModels)
Presenters are used to hold the presentational data for a given view and are typically translations of raw Entity (Model) data; as a result, Ofqual names these as ViewModels, as they are literally models of the View to be presented. ViewModels should:
- Do not have to be named after the View they are used in if appropriate, but nearly always map on to either a View or a Partial View
- Be broken down into submodels if needed; this is particularly relevant when JSON Data is being used to dynamically drive the content for the View
- Be held in the Web project of the solution in a ViewModels folder
C Sharp Front-ends MUST use Entities (Models)
Entities named Models in Ofqual, as they are effectively synonymous. These Models are for storing and sending out the raw data to and from data persistence stores and are likely to be formatted differently to a Presenter (ViewModel) as a result. For instance, a Model may store JSON Data for defining some content, which then gets mapped out to various different ViewModels. Models should:
- Be held within the Core project of the solution
- Avoid binding themselves to infrastructure implementation; aim to be as close to a POCO class as possible. This allows for flexibility of data persistence implementation in the future
C Sharp Front-ends MUST use Routers (Controllers)
Routers, named as Controllers in Ofqual, handle the navigation of the system. These should:
- With few exceptions (mainly around handling authentication depending upon authentication implementation), return a View, Redirect or appropriate Error result for each Route specified
- Aim to contain as little business logic as possible, instead forwarding such tasks on to Interactors. A Router may handle some basic co-ordination of Interactors, but anything beyond this likely should be delegated to a Service
You should have a plan for how to appropriately handle errors to be propogated out to a user in a usable manner; for instance, if a NotFound result is raised, then this should display a Not Found type page (typically done using a StatusCodePages middleware that redirects to an Error Controller, that then displays a View)