ASP.NET MVC was a pragmatic choice back in the day when building websites. It allowed DI , adding in light JS frameworks and bundle them together. The DI allowed for testing most the code with ease except for the Razor view code.
I was recently working on a .NET MVC project which had not aged well. It had fat Razor chain of views which would then have a whole bunch of JS frameworks which the developer needed to know. Adding new features reliably without breaking existing features was painful.
Some of the issues I noticed were:
I ain’t got time for that
We used Selenium to write UI tests. This added significant build time as they rose in number and having flaky false positives made you lose faith in those tests. This impacted our productivity as the feedback loop got longer.
Developer experience
The only way to test our app was to made code changes, manually reload the app and wait for IIS to load which makes it painfully slow. Having to wait a long time for feedback until the UI tests in your build go green impacts the development cycle. There is no proper way to version the various JavaScript frameworks we used in our project while ensuring that they play well with each other which left it to individual developers to test out the changes.
Yeah you can’t do that
In this day and age people expect much better user experience. The default Razor template engine allows server rendering the view model. If you want a bit more dynamic content you will have use JavaScript frameworks to make it dynamic but over time maintaining those frameworks is a problem with a lack of version checking and locking.
Yeah Nah
Reusing views was painful as you also needed to know which JavaScript files were required for partial views which made people create a new view even if it had minor differences. Also the code ended up being specific to the view which meant that you needed to create code all the way from the view to the back end for every route.
What did we do?
We ended up making the Razor views very dumb with minimal code. Its only job was to load a Vue.js app and pass some contextual data to the app like the id of the resource. This allowed us to reuse Vue.js components across pages and also build more complex UI.
We used TypeScript on Vue.js to strongly define the contracts with the services. This made development much easier as now a developer introducing changes to the server would be responsible for updating the client contracts manually. If the change in client contract caused issues the compilation would break. While not perfect these changes were cross checked by other members in pull requests.
The MVC controllers became services that served JSON instead of Razor views. This made us think more in a service oriented architecture and in the future if required that code could be pulled out easily.
Running UI unit tests in Vue.js was extremely fast. We could run hundreds of tests in seconds instead of a few a minute. Instead of developers waiting for close to half an hour for the CI to see if we broke something we could now run all our UI tests in less than a minute. This drastically reduced the time needed to introduce and check new changes.
Instead of the developer keeping track of many different JavaScript libraries that are needed for a page we only needed to load one library JavaScript file and one component JavaScript file for each component you wanted to render on the Razor page.
Caveats
We did make a trade off having faster tests with mocked data instead of writing Selenium tests. While that worked for us I think that’s something to call out. In our team we worked very closely and ensured on pull requests that we kept the server and client contract in sync.
Talk is cheap where is the code?
I have a basic example on how to achieve this but I will be putting it up in a later post in the interest of keeping this one concise.