ASP.NET MVC isomorfic arhitecture with React.js
Goals for creating isomorfic architecture
First of all I would like to explain my goals for creating this arhitecture:
- Less code duplication
- Less bugs
- Much easier maintainance of the web page
- Separation of the concerns between backend and frontend
- Completely server backend framework agnostic
- SEO
- Performance and UX
There are lot of things here, and lot of them colide or used to colide. But before going any further let’s explain how things were used to be done in older projects, in company I currently work for.
SEO and performance needed to be first on the list but other things mentioned on this list suffered. Huge amount of time was wasted just to make redesign of one more complex page. I remember that few years ago designer needed more than one month to make redesign of the web listing page with paginated content and filtering feature – typical page you will find in any web shop. One of the reasons for that was code quality. As an example of bad quality I will only mention html made using string concatenation.
var html="<div><span>"+someJsVariable"+"</span>"+otherJsVariable+"</div>"
If you can imagine how thousands lines of such code looks like they you can probably guess that designer was not very happy. That led to :
- HTML that is not valid, and has to semantics
- Lots of bugs
- Huge amount of time designers needed to make changes
One great solution for tackling lot of this problems was introducing javascript templating engines. It was huge in my eyes because all of the sudden we had HTML that looks like HTML any not javascipt string, we had syntax highlighting for HTML, much less time needed to make new features or make changes on old ones. As you already know, by using Handlebars, Mustache, JsRender or any templating engine example from above would look something like this:
<div> <span>{{:someJsVariable}}</span> {{otherJsVariable}} </div>
However, there were few things that can be better today. Let’s mention again example from before again – listing page with paginated content. For SEO reasons we wanted:
- First page rendered on the server
Rendering on the server only first 10 items of thousand elements long list means that our content is visible to the search engines. Altought only 10 items are showed this is enough content for Google and we also have performance benefit by loading only 10 instead of 1000 items. Other pages are loaded asyncronously and rendered on the client. This means that when user clicks on page 2 or page 3, data will be fetched from web service and JSON will be returned. Once JSON comes back to te client, we will fill javascipt with data.
<div id="peopleList"></div> <script id="personTmpl" type="text/x-jsrender"> <label>Name:</label> {{:name}}<br/> </script> <script> var myTemplate = $.templates("#personTmpl"); var people = [ { name: "Adriana" }, { name: "Robert" } ];
- Better User experience
As already said, we don’t want to load all that CSS, HTML and Javascipt and other assets just because we switched to the second page of our listing. What was really changed are items inside list.
This way, by loading subsequent pages asynchronously user experience is improved. Instead of loading whole CSS, HTML, and Javascript again for each page only required data needed to render page is loaded. Other than that second page is competely the same as first, and third is completely the same as second one. This also means less worry about SEO as duplicate content is one thing that can harm your ranking on Google badly. If all your listing pages were rendered on the server and have same content Google will probably rank only one of them. So, there is no any benefit to render all those pages on the server except the fact that it is tehnically easier. But again, SEO and UX were and still are first priority.
Let’s see now drawbacks of this architecture. Having server side and client side rendered content led to code duplication. We have to maintain two same things in different technologies. One solution to this problem could be rendering everything on the server. Sebsequent pages will still be loaded asyncronously but web service would return HTML instead of JSON. This is not ideal solution because if templates are built in ASP.NET Web Forms they can’t be reused inside ASP.NET MVC. Also, by returning JSON your API can be used to run other web and mobile applications. Ideally there would be code written once, most likely in Javascript, and renderer on the server and rendered on the client. Same code.
Isomorphic JavaScript to the rescue!
Isomorfic Javascript means combination of client and server side rendering. In node.js applications we write javascript on client and server. Buidling backend using node.js was not solution for me because whole backend in company I currently work is built on .NET platform. I started exploring what would be best solution for UI code. There were several solutions. First one was ofcourse Razor templates. Here is what I didn’t like about that:
- Code that doesn’t belong to view inside MVC view .Razor templates add ability for easily adding any custom logic using just C# code. When there is something to be rendered as dynamic list plain C# foreach method will be used. Problem is that at some point logic inside Razor templates becomes to complex. Also, while HTML helpers are probably great for some simple things they become overused and large amount of HTML ends up written as concatenated C# string. Horrible.
- Not language agnostic. I wanted UI that can be reused between projects built using different frameworks. Altought both Microsoft’s ASP.NET Web Forms and ASP.NET MVC are very much different. I wanted that most of code I built on ASP.NET MVC based project can be reused in another older but important ASP.NET Web Forms project.
- Partial views – Similary to User Controls in ASP.NET Web Forms MVC has way of reusing code in form of partially views. However, I find that passing data to partial view and receiving data from it is much more elegant in React for example.
- Is not client rendering engine – Razor is only server rendering engine, so any template built in Razor has to be rendered on the server. That means that anything asyncronously fetched on the page via ajax has to bre rendered on server and returned in form of HTML. I wanted that my api return XML or even better -JSON.
This is how it compares to isomorfic React nature:
- Any view logic remains inside view. Every data, every variable has to be assigned in MVC controller, or some layer above. Data inside viewmodel object is then given to the View. Once there is data inside view, we are inside React world. There is no call to MVC helpers, or any other backend logic. One-directional data flow.
- Language and framework agnostic
Because it is built in React, UI code doesn’t care to much if we are building website in ASP.NET MVC, ASP.NET Web Forms or even some php framework. We pass data to the view. The rest is React’s concern.
- React components
React has concepts of components. In broad sense they can be compared to ASP.NET MVC partial views. But, they are more powerfull as easier to use. For example, React component looks like HTML tag and we pass variables like HTML attributes
<MyReactComponent firstTagToPass="1" anotherThing={{name:"MyName", surname:"Surname"}}/>
- Can be rendered on client and on the server
React can be rendered on the client and on the server. I used this library to render my components on the server. What is great is that code remains completely the same. You have to be little bit carefull not to use some variables that exist only inside browsers (like window) inside component constructor or any other React’s lifecycle method that can be called on the server. When we want to do something when component is inside browser we can do that inside componentDidMount method.
Is separate article I will explain how to put together whole infrastructure but I will show code without going in some details just in sake of better explanation and better understanding.
This is how typical ASP.NET MVC controller will look like:
public ActionResult Contact() { return View(new ContactPageViewModel { PageElements = this.Elements, Root = UrlParameters.Root(Request).Url, IsMobile = base.IsMobileDeviceGlobal, H1 = this.H1, H2 = this.H2, ContentItems = this.ContentPage?.Content, Localization = new ViewModels.Localization.DetailsPageLocale().GetAll(), }); }
Job of the controller is simply to pass all data it gets to the view. In this case I am populating ContactPageViewModel object with same data and that data is then given to the view. In fact usual MVC stuff is going on here. MVC view is incredebly simple. It doesn’t have to have any HTML. But we need to pass data to the main React component of given MVC page.
<div id="root-------"> @Html.React("Components.ContactPage", new { pageElements = Model.PageElements, isMobile = Model.IsMobile, ROOT = Model.Root, h1 = Model.H1, h2 = Model.H2, contentItems = Model.ContentItems, localization = Model.Localization }, containerId: "root2") </div>
This is how Contact component can look like:
import * as React from "react"; const ReactStrap = require('reactstrap'); const Container = ReactStrap.Container; const Col = ReactStrap.Col; const Row = ReactStrap.Row; import * as extensions from '../helpers/Extensions' import ContactForm from '../components/Forms/ContactForm' import ContentFromApp from '../components/Common/ContentFromApp' export default class ContactPage extends React.Component<IContactPageProps, IContactPageState>{ constructor(props) { super(props); } render() { return ( <Container className="tb-height"> <Row className="p-y-3"> <Col xs="24" md="14"> <h1>{this.props.h1}</h1> {this.props.h2 ? <h2> {this.props.h2}</h2> : <div></div>} <ContentFromApp contentItems={this.props.contentItems} order={1} /> </Col> <Col xs="24" md="10"> <div className="card p-x-1 p-y-2 clearfix"> <div className="m-b-1"> <ContentFromApp contentItems={this.props.contentItems} order={2} /> </div> <ContactForm ROOT={this.props.ROOT} localization={this.props.localization} /> </div> </Col> </Row> </Container> ) } } interface IContactPageProps { pageElements: extensions.ElementItem[]; ROOT: string; isMobile: boolean; settings: server.Settings; h1?: string; h2?: string; contentItems: server.ContentItem[]; localization: server.DetailsPageLocale; } interface IContactPageState { }
Chris
Great men! I also tried this architecture and it makes code so much more reusable. Lot of .NET developers stick to Razor and try to make even HTML with C# which makes pages so hard to maintaine.