Introduction
I have found “React” in job requirements many times in the past and being developing in Angular, I always wonder how different these two single page application frameworks are. Well, I guess I couldn’t wait longer when in an interview, they asked me to develop a simple React page. It took me by surprise but we should never say “I cannot" in an interview.
The first thing to mention is the project definition that was something like this: Make a simple one-page application with a car part entry form using React.
- Car part entry form must have the following fields:
- ID (unique identifier, can be automatically generated)
- Part Number (random 5-digit number starting with
00001
) - Part name (name of car part)
- Description (describe the car part)
- Car Manufacturer Name (Chrysler, Dodge, Ford, etc.)
- App must be able to save the car part entries to a database
- App must be able to edit car part entries
- App must be able to remove car part entries
To complete the project, I decided to use Visual Studio 2017 and explore the react framework with these requirements in mind. Fortunately, the internet offers numberless resources in any topic. So by steps, I proceeded as follows:
Step 1: Create a React Project
This is a very well known step by any .NET developer. But here is the process:
- Open Visual Studio 2017 and select create new project.
- In the window that pops up, select the project type which in this case is ASP.NET Core Web Application. This is part of the .NET Core group of templates.
- Select the folder location for the project, assign a project name (React Demo) and solution name (React Demo).
- Check the “Create Directory for Solution” option.
- If you have a GIT account and you want to have this project persisted in GIT, then check that option too.
- After clicking OK, the next window shows different project templates. Select ReactJS template and click OK.
- The first window should be like this. The second window is pretty straight forward so I did not show it here but it requires selecting the ReactJS option and click ok.
And the solution explorer will be like this:
And this is what we get when running the project:
Great! An error. If we select the project name and click on Show all files, we will notice that there is no node_modules folder. So our first step will be to open a command prompt in the project file level and run NPM Install. And the second time we run the project…. Bingo!
This is a good starting point for a project with some extra stuff that we may not need. In our case, we don’t need the Counter and Fetch Data menu items and the related pages. And we need the Home but we may need to change that text. For my purposes of submitting a React Demo, I decided to replace the text in the Home page with the project requirements. The result will be something like this:
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
export class Home extends React.Component<RouteComponentProps<{}>, {}> {
public render() {
return <div>
<h1>React Demo</h1>
<p><u>Tasks:</u></p>
<p>Make a simple one-page application with a car part entry form.</p>
<p>Car part entry form must have the following fields:</p>
<ul>
<li>ID (unique identifier, can be automatically generated).</li>
<li>Part Number (random 5-digit number starting with 00001.</li>
<li>Part name (name of car part. </li>
<li>Description (describe the car part).</li>
<li>Car Manufacturer Name (Chrysler, Dodge, Ford, etc.)</li>
</ul>
<p>App must be able to save the car part entries to a database</p>
<p>App must be able to edit car part entries</p>
<p>App must be able to remove car part entries</p>
</div>;
}
}
Also, I decided to remove Counter and Fetch Data and add React Demo. The first step is an easy one, only open the Home.tsx file and replace the text. The second step needs you to open the NavMenu.tsx file. In that file, remove these lines:
<li>
<NavLink to={ '/counter' }
activeClassName='active'>
<span className='glyphicon
glyphicon-education'></span> Counter
</NavLink>
</li>
<li>
<NavLink to={ '/fetchdata' }
activeClassName='active'>
<span className='glyphicon
glyphicon-th-list'></span> Fetch data
</NavLink>
</li>
And replace them with these lines:
<li>
<NavLink to={ '/reactdemo' }
activeClassName='active'>
<span className='glyphicon
glyphicon-education'></span> React Demo
</NavLink>
</li>
Also in the routes.tsx file, remove the counter and fetchdata routes and replace them with the “carpart
” route like this:
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';;
import { CarPart } from './components/ CarPart;
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/ carpart component={ CarPart } />
</Layout>;
We can delete the Counter.tsx and FetchData.tsx and add a new carPart.tsx. To add the carPart tsx file, I just added a typescript file and changed its extension to tsx. The following code shows the details of the CarPart
component.
This component has two event methods, HandleSave
and HandleDelete
that are triggered when clicking on the respective buttons. What we need to observe in these methods is the call of the react Fetch
. In case of the HandleSave
, it includes the headers data and passes the object as part of the headers as JSON object, that way, in the controller, the argument carPart
of type CarPartModel
in the SavePartData
method will be decorated with [FromModel]
meaning that there is no necessity to map anything to pass the and object instance.
In the case of HandleDelete
, the only argument that is necessary is the part id that will be deleted from the database.
import 'bootstrap/dist/css/bootstrap.min.css';
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import Link, { LinkedComponent } from 'valuelink';
interface CarPartModel {
ID: number;
PartNumber: number;
PartName: string;
Description: string;
CarManufacturer: string;
}
const carManufacturers = [
{ label: "Ford", value: 1 },
{ label: "Chevrolett", value: 2 },
{ label: "Tesla", value: 3 },
{ label: "Chrysler", value: 4 },
{ label: "Honda", value: 5 },
{ label: "Toyota", value: 6 },
];
export class CarPart extends React.Component<RouteComponentProps<{}>, CarPartModel> {
state: CarPartModel = {
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
constructor() {
super();
this.state = {
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
this.handleSave = this.handleSave.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
handleSave = () => {
fetch('api/CarParts/SavePartData', {
method: 'post',
headers: new Headers({
"Content-Type": "application/json",
Accept: "application/json"
}),
body: JSON.stringify(this.state)
}).then(function (response) {
if (response.status !== 200) {
console.log('fetch returned not ok' + response.status);
}
}).catch(function (err) {
console.log(`error: ${err}`);
})
}
handleDelete = () => {
if (!confirm("Do you want to delete employee with Id: " + this.state.ID))
return;
else {
fetch('api/CarParts/Delete/' + this.state.ID, {
method: 'delete'
}).then(data => {
this.state =
{
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
});
}
}
public handleIdChange(event: any): void {
this.state.ID = event.target.value;
}
public handlePartNumberChange(event: any): void {
this.state.PartNumber = event.target.value;
}
public handlePartNameChange(event: any): void {
this.state.PartName = event.target.value;
}
public handleDescriptionChange(event: any): void {
this.state.Description = event.target.value;
}
public handleManufacturerChange(event: any): void {
this.state.CarManufacturer = event.target.value;
}
public render() {
return <div>
<h1>Car Parts</h1>
<div className='row'>
<div className='col-md-2'>
<label>Part Id: </label>
</div>
<div className='col-md-2'>
<input className="form-control" type="text"
name="partId"
onChange={e => this.handleIdChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Part Number: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text " name="partNumber"
onChange={e => this.handlePartNumberChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Part Name: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text" name="partName"
onChange={e => this.handlePartNameChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Description: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text" name="description"
onChange={e => this.handleDescriptionChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Car Manufacturer:</label>
</div>
<div className='col-md-2'>
<select onChange={e => this.handleManufacturerChange(e)}>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="chrysler">Chrysler
</option><option value="saab">Saab</option>
<option value="dodge">Dodge</option>
<option value="ford">Ford</option>
</select>
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row mt-3'> </div>
<div className='row'>
<div className='col-md-1'>
<button onClick={this.handleSave} className='btn
btn-info btn-lg'><span className="glyphicon glyphicon-ok"></span>
Save
</button>
</div>
<div className='col-md-1'>
<button onClick={this.handleDelete}
className='btn btn-info btn-lg'>
<span className="glyphicon glyphicon-remove"></span>
Delete
</button>
</div>
<div className='col-md-9'>
</div>
</div>
</div>
;
}
}
The last items I added are the project services. To do that, I added a services folder and added a class called CarPartService.cs and ICarPartService.cs. The service
class has the actual code in C# that will interact with the Data Access Layer (DAL) to perform CRUD operations. I did not include that in the demo since it was out of the scope of the project purpose which was to show the ability to use React code. But for testing purposes, add breakpoints in the service methods and run the application in debug.
The code of the ICarPartInterface.cs is as follows:
using ReactSample.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReactSample.Services
{
public interface ICarPartService
{
CarPartViewModel GetCartPart(int carPartId);
bool SaveCartPart(CarPartViewModel carPart);
bool UpdateCarPart(CarPartViewModel carPart);
bool Delete(int carPartId);
}
}
The code of the CarPartService.cs is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ReactSample.ViewModels;
namespace ReactSample.Services
{
public class CarPartsService : ICarPartService
{
public bool Delete(int carPartId)
{
try
{
return true;
}
catch(Exception ex)
{
}
return false;
}
public CarPartViewModel GetCartPart(int carPartId)
{
var carPart = new CarPartViewModel();
try
{
return carPart;
}
catch (Exception ex)
{
}
return null;
}
public bool SaveCartPart(CarPartViewModel carPart)
{
try
{
return true;
}
catch (Exception ex)
{
}
return false;
}
public bool UpdateCarPart(CarPartViewModel carPart)
{
try
{
return true;
}
catch (Exception ex)
{
}
return false;
}
}
}
To use the service layer, I decided to inject it in a controller. So I created a CarPartsController
in the Controllers folder and added the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReactSample.Services;
using ReactSample.ViewModels;
namespace ReactSample.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class CarPartsController : Controller
{
ICarPartService _carPartService;
private readonly string[] CarManufacaturers = new[]
{
"Chryssler", "Dodge", "Ford",
"Jeep", "Chevrolett", "Honda",
"Toyota", "Subaru", "Nisan", "Kia"
};
public CarPartsController(
ICarPartService carPartService
)
{
_carPartService = carPartService;
}
[HttpGet("{id}", Name = "Get")]
public CarPartViewModel Get(int id)
{
return _carPartService.GetCartPart(id);
}
[HttpPost("SavePartData")]
public void SavePartData([FromBody]CarPartViewModel carPart)
{
_carPartService.SaveCartPart(carPart);
}
[HttpPut("{id}")]
public void Update(int id, [FromBody]CarPartViewModel carPart)
{
_carPartService.UpdateCarPart(carPart);
}
[HttpDelete("Delete/{id}")]
public void Delete(int id)
{
_carPartService.Delete(id);
}
}
}
You can notice that in the demo, I am using two methods in this controller, SavePartData
and Delete
. I left the other methods just in case I decide to add more functionality to this project… just for fun. If you notice this controller constructor has the carPartService
argument, that is assigned to a _carPartService
variable. In order to make this work, we need to add the following lines to the Startup.cs file, ConfigureServices
method that will look like this at the end:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICarPartService, CarPartsService>();
services.AddMvc();
}
After adding bootstrap and running the solution, we get this:
Happy coding!