From Web Forms to React Part 1
Thomas Heath, Lead Developer
This is the first post in a series of blog posts about how we went from working with a legacy Web Forms code base to the present where 95% of our developing time is spent working with a modern tech stack (.NET 6 and React). A very large part of our total codebase still runs in classic .NET Framework and Web Forms. However, it is not the part of the code we are actively working on and changing.
After we have been through this transition, we are in a place where we have a lot of the advantages of doing green field development without having been through a large, risky, and expensive rewrite of the code base. And we were able to deliver software to production quite often along the way.
In the last blog post, our head of development, Troels Richter, wrote about our considerations regarding the introduction of new technology in a legacy product. We chose an evolutionary rather than revolutionary approach and used a feature request from the business – the introduction of a completely redesigned application menu – as an incentive to start the change.
Initial Considerations
The first big question was which frontend tech we would introduce. React was a natural choice because, in its purest form, it is just a library that renders components in the DOM (and some state management as well). And that was exactly what we needed to get our new menu running. We had an assumption that in the future we would base more of our feature development on React, and so we would need more than just rendering – e.g., routing and management of ”server state”. But until then, it seemed sensible to only take in the React core library and concentrate on getting that working and shipped to our users.
We wanted a modern and effective development experience, so being able to work on the React app without being forced to run the Web Forms application was important. Create React App helped us in that regard. Additionally, we wanted the entire setup working with how Create React App comes out of the box – that is without having to “eject” and apply custom configurations.
The overall goal: To have the Web Forms application show a new application menu rendered in React in a lightweight fashion with as few code changes to the existing application as possible and release that to production quickly.
React in Web Forms
The first step is to create a folder for the React app and set up the fundamental infrastructure such as a build. Create React App is run from the root of the solution:
npx create-react-app web-client --template typescript
This creates a new folder ”web-client” at the solution root:
web-client (new React app)
WebSite (Web Forms project)
…
Pactius.sln
Now the React application can be started via:
web-client/npm start
This starts a Node-based development webserver featuring automatic reloading on save and gives an overall much nicer development experience than can be had in the Web Forms world.
But this is only useful for development as the React components developed is not available in the Web Forms project. So, the new React app needs to be integrated into Web Forms.
The first step is to make the output of the React build available from Web Forms.
We expand the NPM build script (package.json) from Create React App a bit, so the output from the build is copied to the Web Forms project on each build:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && npm run delete && npm run copy",
"delete": "del-cli --force ..\\WebSite\\ReactBuild",
"copy": "copyfiles -u 1 build/**/* ..\\WebSite\\ReactBuild",
...
}
Here the NPM packages "del-cli" and ”copyfiles” are used.
Next, we make the Web Form pages render the scripts from the build output. To do that, a ”Literal” is added to the master page in Web Forms:
<asp:Literal runat="server" ID="ReactJs"/>
This is added to the bottom of the page right before the closing </body> tag.
The NPM script above places the JS files from React in the folder ”ReactBuild”. So, this simple method called from ”Page_Load” in the master page code behind reads all .js files from that folder and renders them as script tags in the ”ReactJs” literal:
private void LoadReactJs()
{
var relativeJsFolder = "/ReactBuild/static/js";
var jsAbsoluteFolder = Server.MapPath($"~{relativeJsFolder}");
var di = new DirectoryInfo(jsAbsoluteFolder);
var files = di.GetFiles();
var jsString = string.Join(Environment.NewLine, files.Select(f =>
$"<script src='{relativeJsFolder}/{f.Name}'></script>")
);
ReactJs.Text = jsString;
}
Now if ”npm build” is run and the Web Forms project is started, the React app will be available on all the pages in Web Forms.
Then all there is left to do is make React render a component in a div from Web Forms. Because we are making a new application menu, the master page needs to be edited again:
<div style="display: flex">
<div id="react-menu"></div>
<div style="flex: 1">
Page content here…
</div>
</div>
A new empty div ”react-menu” is added and this is where React will take over and render the new menu.
In React, ”index.tsx” is changed to bootstrap the rendering:
const reactMenu = document.getElementById("react-menu")
if (reactMenu) {
ReactDOM.render(<Menu />, reactMenu);
}
let root = document.getElementById("root");
if (root) {
ReactDOM.render(<App/>, root);
}
After this change, the React app has two ”roots” – one used while developing via ”npm start” and the local development server and one used when the entire Web Forms application is running.
At this stage of the React app, the only thing happening in the ”App” component is the rendering of the ”Menu” component.
Dynamic Menu
The menu we need to render in React is dynamic in the sense that the menu items need to be shown or hidden according to the logged-in user’s permissions. There is a need for dynamic sub-menu items as well.
Thus, the ”Menu” React component needs data from the backend. As mentioned, we wanted to start out with a solution as simple as possible. So instead of introducing AJAX HTTP calls for fetching this data, we chose to embed the data in the DOM and have React retrieve it from there. In Web Forms, the menu is rendered for each page view so this would also perform better than having an additional HTTP request per page view.
On the master page, this div was introduced:
<div id="react-data"
data-customerid='<%= customerId %>'
data-menuviewmodel='<%= reactMenuViewModelJson %>'
data-userid='<%= userprofileId %>'
data-language='<%= language %>'
data-userfullname='<%= fullName %>'>
</div>
HTML data attributes are used to carry the data needed by the React app to the frontend. ”index.tsx” is then modified to get the data from the DOM and forward it to the ”Menu” component:
let menuViewModel: MenuViewModel;
const reactData = document.getElementById("react-data");
if (reactData) {
const menuViewModelString = reactData.dataset.menuviewmodel;
if (menuViewModelString) {
menuViewModel = JSON.parse(menuViewModelString);
}
}
const reactMenu = document.getElementById("react-menu")
if (reactMenu) {
ReactDOM.render(
<Menu menuViewModel={menuViewModel}/>,
reactMenu
);
}
Note that it is not the responsibility of the ”Menu” component to retrieve the data – it is injected into the component as a prop. This decouples the rendering of the menu from the data retrieval allowing us to reuse the menu in other contexts – for instance in pages rendered only in React.
Conclusion
This post outlines how we took the first step towards a new tech stack featuring React on the frontend. In our specific case, the existing software was based on Web Forms but the overall principles would be the same if it had been based on other web technologies, for instance, ASP.NET MVC.
The application menu was a well-suited component to start off with. With that working in React, it opens the possibility of using that same component both in Web Forms and in stand-alone React pages (that is pages fully rendered in React outside of Web Forms). Therefore, expanding the React part of the application with independent React pages became the next step in the process of modernizing our tech stack. More about that in the next blog post...
Psst!
If you have read this far, I just want to mention that we are looking for an experienced full-stack developer with capabilities within C# and React.JS. Moreover, if you believe it could be interesting to utilise your capabilities in a reality as the one described above, then hurry up and reach out to us!