Replacing Backbone.js views with React.js views in a ASP.NET MVC 5 Application and using ASP.NET WEB API 2 as a RESTful service for Backbone.js

Overview

RESTful service: ASP.NET Web API can be used for building RESTful services on top of the .NET Framework.

Maintainable client side code with RESTful persistence: Backbone.js provides structure to client side code. Backbone.js makes client side code more maintainable in the long term. Backbone.js supports RESTful persistence through its fetch() and create() methods on Collections and fetch(), save(), and destroy() methods on Models.

Efficient DOM updates with component based structure: React.js library deals with the view layer of the application & provides a component based structure to the application. In React.js, each component decides how it should be rendered. React.js efficiently updates and render just the right components when data changes.

News Application using ASP.NET MVC 5, ASP.NET WEB API 2 (RESTful service), Backbone.js (RESTful persistence) & React.js (view layer)

The following code demonstrates how we can use Backbone.js models & collections along with React.js views. Also, in this application, ASP.NET WEB API 2 is used as a RESTful service for Backbone.js.

Step 1: Create a ASP.NET WEB API 2 service (RESTful service) which will be used by Backbone.js. This will be used to perform CRUD operations.

News Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SampleApplication.Models
{
    public class News
    {
        public int Id { get; set; }
        public string Headline { get; set; }
        public string Detail { get; set; }
    }
}

News Web API for performing CRUD operations:

using SampleApplication.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace SampleApplication.Controllers
{
    public class NewsApiController : ApiController
    {
        private static List<News> newsList;

        static NewsApiController()
        {
            newsList = new List<News>
            {
                new News
                {
                    Id= 1,
                    Headline = "Nice terror attack LIVE",
                    Detail = "The attack that took place..."
                },
                new News
                {
                    Id= 2,
                    Headline = "Truck 'terror' in France's Nice",
                    Detail = "A terrorist gunman killed..."
                },
                new News
                {
                    Id= 3,
                    Headline = "Shocking footages from terror-struck Nice",
                    Detail = "Nice: Who knew France`s national day celebrations..."
                },
                new News
                {
                    Id= 4,
                    Headline = "Congress names Sheila Dikshit as chief ministerial candidate",
                    Detail = "New Delhi: Ending speculation, the Congress announced..."
                },
                new News
                {
                    Id= 5,
                    Headline = "Pokemon Go: This is why you should strictly avoid playing this game",
                    Detail = "New Delhi: Pokémon Go, a mobile game has rocked..."
                }
            };
        }

        // GET: api/NewsApi
        public IEnumerable<News> Get()
        {
            // Modifying first headline to include timestamp.
            newsList.First().Headline = $"React.js In Action: {DateTime.Now.ToString("yyyyMMddHHmmssfff")}";
            return newsList;
        }

        // GET: api/NewsApi/5
        public News Get(int id)
        {
            return newsList.Find(p => p.Id == id);
        }

        // POST: api/NewsApi
        public void Post(News news)
        {
            news.Id = newsList.Count + 1;
            newsList.Add(news);
        }

        // PUT: api/NewsApi/5
        public void Put(int id, News news)
        {
            var newsToUpdate = newsList.FirstOrDefault(x => x.Id == id);

            if (newsToUpdate != null)
            {
                newsToUpdate.Headline = news.Headline;
                newsToUpdate.Detail = news.Detail;
            }
        }

        // DELETE: api/NewsApi/5
        public void Delete(int id)
        {
            newsList.RemoveAll(x => x.Id == id);
        }
    }
}

Step 2: Install following Nuget packages in ASP.NET MVC 5 Application:

Install Backbone.js, react.js, React.Web Nuget packages

BackboneJS Nuget Package ReactJS Nuget Packages

Step 3: Create Backbone.js model & collection:

Folder structure

Backbone.js With React.js Views

 

 Backbone.js model – newsItem.js

var app = app || {};

$(function () {
    'use strict';

    app.NewsItemModel = Backbone.Model.extend({
        urlRoot: '/api/NewsApi',
        idAttribute: "Id",
        defaults: {
            date: new Date().toDateString()
        },
        parse: function (data) {
            return data;
        }
    });
});

 Backbone.js collection – newsList.js

var app = app || {};

$(function () {
    'use strict';

    app.NewsCollection = Backbone.Collection.extend({
        model: app.NewsItemModel,
        url: '/api/NewsApi',
        parse: function (data) {
            return data;
        }
    });
});

Step 4: Replacing Backbone.js views with React.js views:

React.js views – NewsListing.jsx

var app = app || {};

$(function () {
    'use strict';

    var DisplaySingleNews = React.createClass({
        render: function () {
            return (
      <div className="news" style={{height: 150}}>
        <h2>{this.props.data.Headline}</h2>
         <div>{this.props.data.Detail}</div>
      </div>
    );
        }
    });

    var DisplayNewsList = React.createClass({
        render: function () {
            var newsList = this.props.data.map(function (news) {
                return (
                  <DisplaySingleNews key={news.Id} data={news}>
                  </DisplaySingleNews>
              );
            });
            return (
              <div className="newsList">
                  {newsList}
              </div>
                );
        }
    });

    var NewsForm = React.createClass({
        getInitialState: function () {
            return { Headline: '', Detail: '' };
        },
        handleHeadlineChange: function (e) {
            this.setState({ Headline: e.target.value });
        },
        handleDetailChange: function (e) {
            this.setState({ Detail: e.target.value });
        },
        handleSubmit: function (e) {
            e.preventDefault();
            var headline = this.state.Headline.trim();
            var detail = this.state.Detail.trim();
            if (!detail || !headline) {
                return;
            }
            this.props.onNewsSubmit({ Headline: headline, Detail: detail });
            this.setState({ Headline: '', Detail: '' });
        },
        render: function () {
            return (
            <div className="form-group">
                <form onSubmit={this.handleSubmit}>
                <textarea value={this.state.Headline}
                          onChange={this.handleHeadlineChange}
                          className="form-control" rows="5" type="text" id="Headline" placeholder="Headline" ref="headline" />
                <textarea value={this.state.Detail}
                          onChange={this.handleDetailChange}
                          className="form-control" rows="5" type="text" id="Detail" placeholder="Details..." ref="text" />
                <input type="submit" className="btn btn-success addnew" value="Add News" />
                </form>
            </div>
        );
        }
    });

    app.NewsControl = React.createClass({
        getInitialState: function () {
            return { data: [] };
        },

        loadNewsListFromServer: function () {
            var that = this;
            var newsCollection = new app.NewsCollection();

            newsCollection.fetch({
                success: function (response) {
                    that.setState({ data: response.toJSON() });
                }
            });
        },

        handleNewsSubmit: function (news) {
            var that = this;
            var newsModel = new app.NewsItemModel(news);
            newsModel.save({}, {
                success: function (model, respose, options) {
                    that.loadNewsListFromServer();
                },
                error: function (model, xhr, options) {
                    console.log("Save Failed.");
                }
            });
        },

        componentDidMount: function () {
            window.setInterval(this.loadNewsListFromServer, this.props.pollInterval);
            this.setState({ data: this.props.collection });
        },

        render: function () {
            return (
                <div>
               <DisplayNewsList data={this.state.data} />
                    <div className="panel panel-default">
            <div className="panel-heading">Post News</div>
            <div id="newsListing" className="panel-body">
                <NewsForm onNewsSubmit={this.handleNewsSubmit}/>
            </div>
                    </div>
                </div>
                );
        }
    });

    app.LatestNewsUpdate = React.createClass({
        getInitialState: function () {
            return { data: { Headline: '', Detail: '' } };
        },

        loadLatestNewsFromServer: function () {
            var that = this;
            var randomId = Math.floor((Math.random() * 5) + 1);
            var newsModel = new app.NewsItemModel({ Id: randomId });

            newsModel.fetch({
                success: function (response) {
                    that.setState({ data: response.toJSON() });
                }
            });
        },

        componentDidMount: function () {
            window.setInterval(this.loadLatestNewsFromServer, this.props.pollInterval);
            this.setState({ data: this.props.model });
        },

        render: function () {
            return (
               <DisplaySingleNews data={this.state.data} />
            );
        }
    });
});

news-view.js

var app = app || {};

$(function () {
    'use strict';

    app.LatestNewsUpdateView = Backbone.View.extend({
        el: '#latestNewsUpdate',
        initialize: function () {
            
        },
        render: function (response) {
            this.reactView = React.createElement(app.LatestNewsUpdate, { model: response, pollInterval: 4000 });
            ReactDOM.unmountComponentAtNode(this.el);
            ReactDOM.render(this.reactView, this.el);
        }
    });

    app.NewsControlView = Backbone.View.extend({
        el: '#newsListing',
        initialize: function () {

        },
        render: function (response) {
            this.reactView = React.createElement(app.NewsControl, { collection: response, submitUrl: this.submitUrl, pollInterval: 2000 });
            ReactDOM.unmountComponentAtNode(this.el);
            ReactDOM.render(this.reactView, this.el);
        }
    });

});

Step 5: app.js contents

var app = app || {};

$(function () {
    'use strict';
    
    var randomId = Math.floor((Math.random() * 5) + 1);
    var newsModel = new app.NewsItemModel({ Id: randomId });
	newsModel.fetch({
	    success: function (response) {
	        var latestNewsUpdateView = new app.LatestNewsUpdateView({ model: newsModel });
	        latestNewsUpdateView.render(response.toJSON());
	    }
	});

	var newsCollection = new app.NewsCollection();
	newsCollection.fetch({
	    success: function (response) {
	        var newsControlView = new app.NewsControlView({ collection: newsCollection });
            newsControlView.render(response.toJSON());
        }
    });
});

Step 6: ASP.NET MVC 5 Application, setting up Controller & Views:

Home Controller:

using SampleApplication.ProcessUtility;
using System;
using System.Web.Mvc;

namespace SampleApplication.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult News()
        {
            return View();
        }
    }
}

News.cshtml view under “Views/Home” directory:

@{

    ViewBag.Title = "News Application";
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="@Url.Content("~/Content/bootstrap.min.css")" rel="stylesheet" />
    <title>News Application - TechCartNow.com</title>
</head>
<body>
    <section class="main-content" style="width:90%; align-content:center; margin-left:25px;">
        <h1>News Application</h1>
        <div class="panel panel-default">
            <div class="panel-heading">Latest Update</div>
            <div id="latestNewsUpdate" class="panel-body"></div>
        </div>
        <div class="panel panel-default">
            <div class="panel-heading">News List</div>
            <div id="newsListing" class="panel-body"></div>
        </div>
    </section>
    @section Scripts{
        <script src="@Url.Content("~/scripts/underscore.js")"></script>
        <script src="@Url.Content("~/scripts/backbone.js")"></script>
        <script src="@Url.Content("~/scripts/react/react.js")"></script>
        <script src="@Url.Content("~/scripts/react/react-dom.min.js")"></script>
        <script src="@Url.Content("~/scripts/news-app/backbone/models/newsItem.js")"></script>
        <script src="@Url.Content("~/scripts/news-app/backbone/collections/newsList.js")"></script>
        <script src="@Url.Content("~/scripts/news-app/react-components/NewsListing.jsx")"></script>
        <script src="@Url.Content("~/scripts/news-app/backbone/views/news-view.js")"></script>
        <script src="@Url.Content("~/scripts/news-app/backbone/app.js")"></script>
    }
</body>
</html>

Step 7: React in action in React Developer Tools

React JS In Action In React Developer Tools

That’s All.

 

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *