One of the key trends in software architecture over the past decade is the emergence of Single Page Applications (SPAs). This is an important trend to consider when we look at modernizing older Web Form applications written using .NET Framework 4.x with a lot of logic in the form code-behind. In this blog, I describe how we implemented SPA into our core product and some of our big decisions in the journey.
Simply replacing existing web forms with a lot of CRUD logic in the code-behind with a modern equivalent on a field-by-field basis can be an expensive, risky, and time-consuming process. To help get the best ROI from our modernization process, we can implement a Single Page Application (SPA) that will enable us to eliminate a lot of this CRUD logic; reducing the effort, risk, and time to market.
When we addressed this problem at Great Ideaz with our product, trellispark, we went back to the basic definition of a form-based user experience that could display any record. What we found as the most common and versatile forms-based design was when forms break down into a set of tabs, each tab contained a list of rows, and each row provided place holders for a set of fields. Considering the typical CRUD logic on the code-behind, we determined that the following questions needed to be answered:
- Can the current user even access the record in its current state? This drives the need for row-level security.
- Given that the current user can access the record in its current state: can they save changes to it, or delete it?
- Assuming that the current user has access to the record, what can they see and do with the data?
This breaks down the user’s experience of a form into a hierarchic set of visual components (tabs, rows, and fields) that we chose to implement with Microsoft Blazor/WASM.
We subtyped our field components based on the data type to be represented. For each field, we determined:
- What the user can do with the field?
- What type of data will be presented by the field?
- How does the displayed data map back to the record data field?
- Can the user see the field at all?
- Can the user interact with the field (modify its contents or interact with it)?
This led to the definition of a set of permissions defined at the form, tab and field level
- Form permissions determine where the user can save or delete the record.
- Tab permissions determine whether the user can see the Tab.
- Field permissions determine whether the user can see, modify or interact with the Field.
When considering whether the permission should be applied, we had to consider:
- The roles assigned to the current user.
- The current state of the record being displayed and possible state transitions.
- Custom business rules. For example, this could include the existence of fields within the record, the value of record fields, and complex calculated rules.
The final user experience is the net of all of the above permissions that can then be rendered to show the user their allowed tabs, rows, and fields with appropriate limits on what the user can do (modify/interact).
After reading through all of the above considerations you can appreciate why there’s often so much logic in Web Form code-behind files. Indeed, this complexity in older applications sometimes lead us to create multiple forms for the same record data for different types of users, or the same record in different states with all the maintenance and code duplication issues this implied.
NOTE that this doesn’t even begin to consider embedded workflow and state transition logic.
To modernize our applications toward a Single Page Application (SPA) user experience, there are two paths:
- We can recreate specific forms and update the code-behind to hard code the user experience with form specific logic as we did before, or
- We can replace the code-behind with a data agnostic page building framework that optimizes the Single Page Application (SPA) approach.
At Great Ideaz, we implemented a Data Agnostic Record Storage solution, and we investigated a few different options for building a modern SPA user experience. After a lot of effort, we determined that the optimal approach was to divide the user experience into three phases:
- A data driven Form Definition (FD) that provides a complete description of the form including all: tabs, rows, fields, commands and state transitions with a comprehensive set of permissions
- A server-based REST API that creates a platform agnostic User Experience Definition (UXD)
- A platform specific client rendering component that will present the user experience using the returned UXD and the contents of the record
The desired Form Definition (FD) user experience is defined and maintained using a simple user experience designer app (UX Creator) that captures all possible characteristics of the form including the workflow and state transitions.
The server-side REST API dynamic page builder combines an agnostic record (provided as XML or JSON) with a baseline Form Definition (also provided as XML or JSON) to create a User Experience Definition that can be passed to a client application. Running this part of the user experience generation process on the server gives us a secure, scalable, and high-performance solution. It should be noted that generating the UXD on the server means that only information that the user can see is sent to the client device.
The client applications are then responsible for rendering the actual User Experience Definition using an appropriate technology. This approach works for any client platform, we just need to create a platform specific rendering engine that uses a common user experience definition from the REST API. At Great Ideaz, we developed a hybrid MAUI/Blazor approach to create desktop, tablet, and mobile applications that all share the same page builder REST API.
You can access all our developer documentation for free at the trellispark Academy.
You can also download a free copy of our latest trellispark platform if you sign up to our free portal.