When Several People Have to Agree Before Anyone Gets In: The RCP Fellowship Portal
Author
David Johnson
Date Published

Stack: Umbraco 8 · ASP.NET MVC 5 · .NET Framework 4.7.2 · Vue 2 · Dynamics 365 (SOAP) · OpenID Connect · PayPal SDK · uSync · Serilog · webpack · Bootstrap 4
Becoming a Fellow of the Royal College of Physicians isn't a form you fill in. It's a process. There's a candidate. A proposer. A sponsor. Two endorsers, potentially including a College Tutor. A grader. Possibly several. And somewhere at the end of it all, a ceremony and an acceptance fee. Every one of those people has something to do. Every one of those actions has to happen in the right order.
The brief was deceptively simple: build a portal. What we actually built was a state machine dressed in a CMS, orchestrating four external SOAP services, with a different face for every role that touched it.
The first thing we learned - the thing we always learn, frankly - is that the application is never the hardest part. The hardest part is the process it has to encode.
Fellowship proposals at the RCP come in two flavours. A candidate can self-propose, shepherding their own application through submission, collecting a sponsor statement, and reaching final review. Or an existing Fellow can propose someone else, entering the candidate's details, handing off to them to complete supporting sections, and then reclaiming the wheel for final submission. Same destination. Completely different road.
And nested inside both of those journeys: a proposal isn't just "in progress" or "done." It moves through stages - New, First Submission, Second Submission, Final Submission - and carries a status alongside that. The combination of stage and status is what tells the UI whether a field should be editable, a button visible, a section locked. Get that matrix wrong and you either let someone edit a submitted application, or you trap them in a form they can't complete. Neither makes for a good day.
The integration picture was equally honest about its complexity.
Dynamics 365 sat at the centre of the RCP's data world, and it spoke SOAP. Not GraphQL. Not REST. SOAP. The protocol that time forgot but enterprise never did. We wrapped four separate services: CRM, Integration, Events, and Transactions. Proposals, candidates, contacts, grading assignments, ceremony dates, payment records, all of it routed through service proxies, mapped through a translation layer that converted CRM types into something the application could reason about, and back again on submission.
The mapping layer deserves a mention here, because it's the kind of work that looks invisible until it isn't. We built a service that translates between internal models and CRM SOAP types in both directions, and it's where the enumerated workflow states become concrete UI decisions. That translation is where a lot of enterprise application projects go wrong; they either scatter the mapping logic across controllers, or they skip it entirely and just pass CRM objects into views like a dare. We didn't do that.
On the identity side: OpenID Connect, with claims enriched post-authentication from the RCP's endpoint. On first login, the portal provisions an Umbraco member record, annotating it with the user's Dynamics ID, RCP code, and membership grade. That membership grade is what determines which dashboard tiles you see. Graders see grading. Candidates see proposals. Fellows see sponsorship. Personalisation baked in.
We chose Umbraco 8 as the foundation, which is worth explaining rather than apologising for.
The RCP needed content editors to be able to update copy, manage navigation, and control the communication around the process without a developer involved every time someone wanted to change a heading. Umbraco gave them that. The schema went into source control via uSync, so environment promotion was repeatable and deployable, not a manual export-import operation that someone might forget.
But Umbraco alone doesn't handle live workflow lists well. For the pages where users needed to see searchable, paginated data, we layered in Vue 2 components backed by dedicated Umbraco API controllers. Server-rendered pages for structure. Client-rendered for data. It's a pragmatic hybrid, not a philosophical position, and it worked cleanly.
The final stage of the workflow, Fellowship acceptance and payment, connected to PayPal via SDK, with transaction records created in the RCP's transaction service layer and confirmed on the payment return. Ceremony dates for the acceptance form were loaded dynamically from the Events service. The configuration surface was substantial: payment metadata, VAT identifiers, method of payment codes, event type IDs. All of it environment-specific, all of it transformed per deployment target.
Seven roles. Two proposal journeys. Four external services. One source of truth for workflow state.
The RCP Fellowship Portal isn't a CRUD app. It's a coordination layer for a core process. One that, if it goes wrong, doesn't just inconvenience someone, it blocks a clinician's professional recognition. The code has to be boring in the right ways. Centralised state logic. Clean mapping layers. Repeatable deployments. Honest documentation.
That's what we shipped.
