Introduction
In my previous post, I explained how to perform CRUD Operation using ASP.NET CORE 2 and Angular 4 with Entity Framework Core (Database First Approach), primeng component and toastr-ng2 which you can find here. In this article, we will see how to create a web application using ASP.NET Core 2.2 and React Redux with the help of Entity Framework Core database first approach.
Before we begin with our practical implementation, let's understand the below points which tells what exactly will be covered in this article.
- Create ASP.NET CORE React Redux Project using new Visual Studio 2019
- Create the database and table
- Create class libraries for application’s business logic and data access layer
- Generate the entity model classes using Entity Framework Core Database first approach
- Create Service class to hold the business logic for CRUD operation
- Set up the Dependency Injection
- Create a Controller and Create API Calls
- Adding PrimeReact components in package.json file
- React Redux
- Run the application
Prerequisites
Make sure you have installed all the prerequisites in your computer. If not, then download and install all, one by one.
First, download and install Visual Studio 2019 from this link. Download and install NET Core 2.2 SDK.
1. Create New AspNetCoreReactRedux Project using Visual Studio 2019
Open VS 2019 (here, I am using Community Edition for this article) and create a new project using the new .NET Core React Redux template (please see the following step by step screenshots).
On the right hand side, you will see a new panel for creating new project (see the highlighted section). Please click on it.
After selecting the above panel, the following new window is open. Please select "ASP.NET Core Web Application" for creating the SPA application.
After selecting the above highlighted panel, a new window will open. Please enter the name of the project and location path for your project.
Once done with the above settings, a new window is open and here, we need to select the “.NET Core” and "ASP.NET Core 2.2" from the highlighted dropdowns and the "React.js and Redux" template to create ASP.NET Core 2.2 application with React.js and Redux.
As we can see, Visual Studio 2019 gives us an inbuilt-template to create React.js and Redux application. It creates a new folder named “ClientApp” where we have an actual React.js and Redux project and “wwwroot”, which is a special folder used to hold all of our live web files.
2. Create the Database and Table
In this article, I am using Entity Framework Core using database first approach. To use this approach, we need to create one sample database table in SQL Server. Please run the following script in your SQL Server:
USE [master]
GO
CREATE DATABASE [ContactDB]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'Contact', FILENAME = N'C:\Program Files _
(x86)\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\Contact.mdf' , _
SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON
( NAME = N'Contact_log', FILENAME = N'C:\Program Files _
(x86)\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\Contact_log.ldf' , _
SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO
ALTER DATABASE [ContactDB] SET COMPATIBILITY_LEVEL = 110
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [ContactDB].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [ContactDB] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [ContactDB] SET ANSI_NULLS OFF
GO
ALTER DATABASE [ContactDB] SET ANSI_PADDING OFF
GO
ALTER DATABASE [ContactDB] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [ContactDB] SET ARITHABORT OFF
GO
ALTER DATABASE [ContactDB] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [ContactDB] SET AUTO_CREATE_STATISTICS ON
GO
ALTER DATABASE [ContactDB] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [ContactDB] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [ContactDB] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [ContactDB] SET CURSOR_DEFAULT GLOBAL
GO
ALTER DATABASE [ContactDB] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [ContactDB] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [ContactDB] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [ContactDB] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [ContactDB] SET DISABLE_BROKER
GO
ALTER DATABASE [ContactDB] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [ContactDB] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [ContactDB] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [ContactDB] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE [ContactDB] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [ContactDB] SET READ_COMMITTED_SNAPSHOT OFF
GO
ALTER DATABASE [ContactDB] SET HONOR_BROKER_PRIORITY OFF
GO
ALTER DATABASE [ContactDB] SET RECOVERY SIMPLE
GO
ALTER DATABASE [ContactDB] SET MULTI_USER
GO
ALTER DATABASE [ContactDB] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [ContactDB] SET DB_CHAINING OFF
GO
ALTER DATABASE [ContactDB] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )
GO
ALTER DATABASE [ContactDB] SET TARGET_RECOVERY_TIME = 0 SECONDS
GO
USE [ContactDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Contacts](
[ContactId] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](50) NULL,
[LastName] [nvarchar](50) NULL,
[Email] [nvarchar](50) NULL,
[Phone] [nvarchar](50) NULL,
CONSTRAINT [PK_Contact] PRIMARY KEY CLUSTERED
(
[ContactId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, _
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
USE [master]
GO
ALTER DATABASE [ContactDB] SET READ_WRITE
GO
3. Create Class Library Projects for Application's Business Logic and Data Access Layer
We can use .NET Core class library projects in this web application. First, you need to create two .NET core library projects - one for business logic and another for data access layer. Just right click on project solution and add a new .NET Core library project (please see the following screenshots):
After selecting the above ".NET Core" class library, a new window appears. Please enter name of your business library project.
Follow the same step for creating a data access layer library project, then add your data access library reference in business layer project and add your business layer reference in your web project. Finally, our project will be as follows:
4. Generate the Entity Model Class Using Entity Framework Core Database First Approach
The next step is to install Microsoft.EntityFrameworkCore.SqlServer
(Microsoft SQL Server database provider for Entity Framework Core), Microsoft.EntityFrameworkCore.Tools
and Microsoft.EntityFrameworkCore.Design
which will help us to go further with Entity Framework operations.
After adding all NuGet Packages, our data access library structure will be as follows:
Run the following command in our data access library project to create an entity model class from the existing database (please see the following screenshot):
Please refer to the below link to create models from existing database in more details:
Please make sure you need to mention your SQL Server instance instead of "yourservername
" in order to execute the command successfully. Once the above command is successfully executed, then one folder name “EntityModels
” is created in our data access layer project which contains the database entities. (See the following screenshot.)
ContactDBContext.cs
namespace DataAccessLibrary.EntityModels
{
public partial class ContactDBContext : DbContext
{
public ContactDBContext()
{
}
public ContactDBContext(DbContextOptions<ContactDBContext> options)
: base(options)
{
}
public virtual DbSet<Contacts> Contacts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
//#warning To protect potentially sensitive information
//in your connection string, you should move it out of source code.
//See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance
//on storing connection strings.
optionsBuilder.UseSqlServer("Server=yourservername ;
Database=ContactDB;Trusted_Connection=True;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.2-servicing-10034");
modelBuilder.Entity<Contacts>(entity =>
{
entity.HasKey(e => e.ContactId)
.HasName("PK_Contact");
entity.Property(e => e.Email).HasMaxLength(50);
entity.Property(e => e.FirstName).HasMaxLength(50);
entity.Property(e => e.LastName).HasMaxLength(50);
entity.Property(e => e.Phone).HasMaxLength(50);
});
}
}
}
Contacts.cs
namespace DataAccessLibrary.EntityModels
{
public partial class Contacts
{
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
}
5. Create Service Class to Hold the Business Logic for the CRUD Operation
Now we are going to write the business logic for our CRUD operation. So let's create two new folders (i.e., "Model" and "Service") inside business layer project and create one interface
(i.e., "IContactService.cs") and one class (i.e. "ContactService
") within that folder which contains few methods for our CRUD operation.
ContactModel.cs
namespace BusinessLibrary.Model
{
public class ContactModel
{
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
}
First of all, we need to create one class as ContactModel
. This is nothing but a model
class which is responsible for getting/passing the data from source as we have to show/insert the data.
IContactService.cs
namespace BusinessLibrary.Service
{
public interface IContactService
{
Task<List<ContactModel>> GetContacts();
Task<bool> SaveContact(ContactModel contact);
Task<bool> DeleteContact(int contactId);
}
}
ContactService.cs
namespace BusinessLibrary.Service
{
public class ContactService : IContactService
{
public async Task<List<ContactModel>> GetContacts()
{
using (ContactDBContext db = new ContactDBContext())
{
return await (from a in db.Contacts.AsNoTracking()
select new ContactModel
{
ContactId = a.ContactId,
FirstName = a.FirstName,
LastName = a.LastName,
Email = a.Email,
Phone = a.Phone
}).ToListAsync();
}
}
public async Task<bool> SaveContact(ContactModel contactModel)
{
using (ContactDBContext db = new ContactDBContext())
{
DataAccessLibrary.EntityModels.Contacts contact = db.Contacts.Where
(x => x.ContactId == contactModel.ContactId).FirstOrDefault();
if (contact == null)
{
contact = new Contacts()
{
FirstName = contactModel.FirstName,
LastName = contactModel.LastName,
Email = contactModel.Email,
Phone = contactModel.Phone
};
db.Contacts.Add(contact);
}
else
{
contact.FirstName = contactModel.FirstName;
contact.LastName = contactModel.LastName;
contact.Email = contactModel.Email;
contact.Phone = contactModel.Phone;
}
return await db.SaveChangesAsync() >= 1;
}
}
public async Task<bool> DeleteContact(int contactId)
{
using (ContactDBContext db = new ContactDBContext())
{
DataAccessLibrary.EntityModels.Contacts contact =
db.Contacts.Where(x => x.ContactId == contactId).FirstOrDefault();
if (contact != null)
{
db.Contacts.Remove(contact);
}
return await db.SaveChangesAsync() >= 1;
}
}
}
}
So, we can see with the above IContactService
interface and its method implementation in ContactService
class, we have defined different methods for a different purpose for our CRUD operations. Here, we will interact with the database using Entity Framework Core and perform the CRUD operations. Also, we are dealing with Task-specific data, it means, we can get/save/delete the data asynchronously. We are done with our business layer project. Now, we need to register the above service class in our web project's startup.cs class.
6. Set Up the Dependency Injection
Let's open Startup.cs class and add dependency injection for ContactService
using the following code::
services.AddTransient<IContactService, ContactService>();
The whole structure of startup.cs class will be as follows:
Startup.cs
using BusinessLibrary.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace AspNetCoreReactRedux
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IContactService, ContactService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
}
7. Create a Controller and API Calls
Add a new controller and name it as "ContactController
". For adding a new controller, you need to right click on "Controllers" folder and choose Add->New Item. Just injected our service class as a dependency, so that we can perform CRUD operations. Please see the below ContactController
class structure.
using BusinessLibrary.Model;
using BusinessLibrary.Service;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace AspNetCoreReactRedux.Controllers
{
[Route("api/[controller]")]
public class ContactController : Controller
{
private readonly IContactService _contactService;
public ContactController(IContactService contactService)
{
_contactService = contactService;
}
[HttpGet]
[Route("Contacts")]
public async Task<IActionResult> Contacts()
{
return Ok(await _contactService.GetContacts());
}
[HttpPost]
[Route("SaveContact")]
public async Task<IActionResult> SaveContact([FromBody] ContactModel model)
{
return Ok(await _contactService.SaveContact(model));
}
[HttpDelete]
[Route("DeleteContact/{contactId}")]
public async Task<IActionResult> DeleteContact(int contactId)
{
return Ok(await _contactService.DeleteContact(contactId));
}
}
}
8. Adding PrimeReact Components in Package.json File
Now, we need to modify the "package.json" file for adding PrimeReact
components for UI purpose by adding the following dependencies.
"primereact": "3.1.2"
Package.json
{
"name": "AspNetCoreReactRedux",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^4.1.3",
"jquery": "3.3.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-redux": "^5.0.6",
"react-router-bootstrap": "^0.24.4",
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.8",
"react-scripts": "^1.1.5",
"reactstrap": "^6.3.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"rimraf": "^2.6.2",
"primereact": "3.1.2"
},
"devDependencies": {
"ajv": "^6.0.0",
"babel-eslint": "^7.2.3",
"cross-env": "^5.2.0",
"eslint": "^4.1.1",
"eslint-config-react-app": "^2.1.0",
"eslint-plugin-flowtype": "^2.50.3",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-react": "^7.11.1"
},
"eslintConfig": {
"extends": "react-app"
},
"scripts": {
"start": "rimraf ./build && react-scripts start",
"build": "react-scripts build",
"test": "cross-env CI=true react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint ./src/"
}
}
PrimeReact Components
PrimeReact
is a collection of rich UI components for React. All widgets are open source and free to use under MIT License. PrimeReact
is developed by PrimeTek Informatics, a vendor with years of expertise in developing open source UI solutions.
In the above package.json file, we are using primereact components (such as datatable
which is used to display the data in tabular format) which is a collection of rich UI components and it is an open source component. Please see the following link for more details:
9. React.js and Redux
The purpose of react-redux
library is to integrate redux’s state management into a React application, however Redux and React are two separate libraries which can and have been used completely independent of each other.
To understand the Redux concepts, you need to know the concept of “Store” which is where the state of the application lives. Basically, every stateful React component carries its own state and these states are used to hold the data and the component is render using that data to the user.
The state could also change in response to actions and events. In React, you can update the local component’s state with “setState
”. You can keep the state within a single parent component, but the things are getting more complex when you add more behavior (for example, if multiple React components need to access the same state but do not have any parent/child relationship) to your component so in order to maintain all those complex states, Redux will come into the picture. Redux holds up the state within a single location.
In Redux, the state must return entirely from reducers. In our example, we’ll be creating a simple reducer taking the initial state as the first parameter. As a second parameter, we’ll provide action, I will explain this concept to you in the following steps but before that, we need to create our store so let's begin with that first.
For adding a new store, you need to right click on "store" folder which is inside the "ClientApp" folder and choose Add->New Item
-> add a JavaScript file and named it as "Contact.js".
Contact.js
const initialState = {
contacts: [],
loading: false,
errors: {},
forceReload: false
}
export const actionCreators = {
requestContacts: () => async (dispatch, getState) => {
const url = 'api/Contact/Contacts';
const response = await fetch(url);
const contacts = await response.json();
dispatch({ type: 'FETCH_CONTACTS', contacts });
},
saveContact: contact => async (dispatch, getState) => {
const url = 'api/Contact/SaveContact';
const headers = new Headers();
headers.append('Content-Type', 'application/json');
const requestOptions = {
method: 'POST',
headers,
body: JSON.stringify(contact)
};
const request = new Request(url, requestOptions);
await fetch(request);
dispatch({ type: 'SAVE_CONTACT', contact });
},
deleteContact: contactId => async (dispatch, getState) => {
const url = 'api/Contact/DeleteContact/' + contactId;
const requestOptions = {
method: 'DELETE',
};
const request = new Request(url, requestOptions);
await fetch(request);
dispatch({ type: 'DELETE_CONTACT', contactId });
}
};
export const reducer = (state, action) => {
state = state || initialState;
switch (action.type) {
case 'FETCH_CONTACTS': {
return {
...state,
contacts: action.contacts,
loading: false,
errors: {},
forceReload: false
}
}
case 'SAVE_CONTACT': {
return {
...state,
contacts: Object.assign({}, action.contact),
forceReload: true
}
}
case 'DELETE_CONTACT': {
return {
...state,
contactId: action.contactId,
forceReload: true
}
}
default:
return state;
}
};
In the above file, we have created our initial state with some default parameters, redux reducer which is the most important concept in Redux. This reducer produces the state of the application and here, we need to mention the state and appropriate CRUD action.
export const reducer = (state, action) => {
state = state || initialState;
We also need to define the action creator where every action needs a type
property for describing how the state should change and it also consumes the API calls for performing the crud operations.To change the state in Redux, we need to dispatch an action. To dispatch an action, you have to call the dispatch
method. (Please see the below piece of code for fetching all the contact information.)
dispatch({ type: 'FETCH_CONTACTS', contacts });
ContactList.js
Now, we need to define our contact component along with its view details which is responsible for handling all the crud operations in our application. (Please see the below code in more details.) Here, you will see a most important method "connect
" which is responsible to connect the react component with our redux store. For this point, I am passing the two arguments (i.e., "mapStateToProps
" and "bindActionCreators
") to connect to redux store.
I have also added the primereact components in this "ContactList
" component for handling all the CRUD related activities (for example, displaying the data in grid using primereact's datatable
, InputText
component to enter the values in the textboxes, etc.). The structure of ContactList
component is as follows:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Dialog } from 'primereact/dialog';
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { Growl } from 'primereact/growl';
import { actionCreators } from '../store/Contact';
class ContactList extends Component {
constructor() {
super();
this.state = {};
this.onContactSelect = this.onContactSelect.bind(this);
this.dialogHide = this.dialogHide.bind(this);
this.addNew = this.addNew.bind(this);
this.save = this.save.bind(this);
this.delete = this.delete.bind(this);
}
componentDidMount() {
this.fetchData();
}
componentDidUpdate() {
if (this.props.forceReload) {
this.fetchData();
}
}
fetchData() {
this.props.requestContacts();
}
updateProperty(property, value) {
let contact = this.state.contact;
contact[property] = value;
this.setState({ contact: contact });
}
onContactSelect(e) {
this.newContact = false;
this.setState({
displayDialog: true,
contact: Object.assign({}, e.data)
});
}
dialogHide() {
this.setState({ displayDialog: false });
}
addNew() {
this.newContact = true;
this.setState({
contact: { firstName: '', lastName: '', email: '', phone: '' },
displayDialog: true
});
}
save() {
this.props.saveContact(this.state.contact);
this.dialogHide();
this.growl.show({ severity: 'success', detail: this.newContact ?
"Data Saved Successfully" : "Data Updated Successfully" });
}
delete() {
this.props.deleteContact(this.state.contact.contactId);
this.dialogHide();
this.growl.show({ severity: 'error', detail: "Data Deleted Successfully" });
}
render() {
let header = <div className="p-clearfix"
style={{ lineHeight: '1.87em' }}>CRUD for Contacts </div>;
let footer = <div className="p-clearfix" style={{ width: '100%' }}>
<Button style={{ float: 'left' }} label="Add"
icon="pi pi-plus" onClick={this.addNew} />
</div>;
let dialogFooter = <div className="ui-dialog-buttonpane p-clearfix">
<Button label="Close" icon="pi pi-times" onClick={this.dialogHide} />
<Button label="Delete" disabled={this.newContact ? true : false}
icon="pi pi-times" onClick={this.delete} />
<Button label={this.newContact ? "Save" : "Update"} icon="pi pi-check"
onClick={this.save} />
</div>;
return (
<div>
<Growl ref={(el) => this.growl = el} />
<DataTable value={this.props.contacts} selectionMode="single"
header={header} footer={footer}
selection={this.state.selectedContact}
onSelectionChange={e => this.setState
({ selectedContact: e.value })} onRowSelect={this.onContactSelect}>
<Column field="contactId" header="ID" />
<Column field="firstName" header="FirstName" />
<Column field="lastName" header="LastName" />
<Column field="email" header="Email" />
<Column field="phone" header="Phone" />
</DataTable>
<Dialog visible={this.state.displayDialog} style={{ 'width': '380px' }}
header="Contact Details" modal={true} footer={dialogFooter}
onHide={() => this.setState({ displayDialog: false })}>
{
this.state.contact &&
<div className="p-grid p-fluid">
<div><label htmlFor="firstName">First Name</label></div>
<div>
<InputText id="firstName" onChange={(e) =>
{ this.updateProperty('firstName', e.target.value) }}
value={this.state.contact.firstName} />
</div>
<div style={{ paddingTop: '10px' }}>
<label htmlFor="lastName">Last Name</label></div>
<div>
<InputText id="lastName" onChange={(e) =>
{ this.updateProperty('lastName', e.target.value) }}
value={this.state.contact.lastName} />
</div>
<div style={{ paddingTop: '10px' }}>
<label htmlFor="lastName">Email</label></div>
<div>
<InputText id="email" onChange={(e) =>
{ this.updateProperty('email', e.target.value) }}
value={this.state.contact.email} />
</div>
<div style={{ paddingTop: '10px' }}>
<label htmlFor="lastName">Phone</label></div>
<div>
<InputText id="phone" onChange={(e) =>
{ this.updateProperty('phone', e.target.value) }}
value={this.state.contact.phone} />
</div>
</div>
}
</Dialog>
</div>
)
}
}
function mapStateToProps(state) {
return {
contacts: state.contacts.contacts,
loading: state.contacts.loading,
errors: state.contacts.errors,
forceReload:state.contacts.forceReload
}
}
export default connect(
mapStateToProps,
dispatch => bindActionCreators(actionCreators, dispatch)
)(ContactList);
ConfigureStore.js
Open "configureStore.js" that was already present inside the store folder and import our "Contact
" store in this file.
import * as Contact from './Contact';
Here is the structure of configureStore.js file.
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Contact from './Contact';
export default function configureStore (history, initialState) {
const reducers = {
contacts: Contact.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
In this code, we pass our reducers to the Redux createStore
function, which returns a store
object. We then pass this object to the react-redux Provider component.
Layout.js
Now open the "Layout.js" file which is already present inside the component folder and import your custom css for UI look and feel prospective. Please see the below structure of Layout.js file where import our primereact
custom css files.
import React from 'react';
import { Container } from 'reactstrap';
import NavMenu from './NavMenu';
import '../../node_modules/primereact/resources/primereact.css';
import '../../node_modules/primereact/resources/themes/nova-dark/theme.css';
export default props => (
<div>
<NavMenu />
<Container>
{props.children}
</Container>
</div>
);
App.js
Now open "App.js" file and import our contact component in it. Generally "App.js" is used for the routing purpose. Please see the below structure for the same.
import React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import ContactList from './components/ContactList';
export default () => (
<Layout>
<Route exact path='/' component={ContactList} />
</Layout>
);
NavMenu.js
NavMenu.js file is used to show your navigation link in the header section of the web application. For this post, I have slight modified NavMenu.js just for the routing purpose:
<NavLink tag={Link} className="text-dark" to="/contacts">Contact</NavLink>
Here is the structure of our "NavMenu.js" file.
import React from 'react';
import { Collapse, Container, Navbar, NavbarBrand,
NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';
export default class NavMenu extends React.Component {
constructor (props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false
};
}
toggle () {
this.setState({
isOpen: !this.state.isOpen
});
}
render () {
return (
<header>
<Navbar className="navbar-expand-sm navbar-toggleable-sm
border-bottom box-shadow mb-3" light >
<Container>
<NavbarBrand tag={Link} to="/">AspNetCoreReactRedux</NavbarBrand>
<NavbarToggler onClick={this.toggle} className="mr-2" />
<Collapse className="d-sm-inline-flex flex-sm-row-reverse"
isOpen={this.state.isOpen} navbar>
<ul className="navbar-nav flex-grow">
<NavItem>
<NavLink tag={Link} className="text-dark" to="/contacts">Contact</NavLink>
</NavItem>
</ul>
</Collapse>
</Container>
</Navbar>
</header>
);
}
}
10. Run the Application
Finally, here we are done with the implementation of CRUD operations with ASP.NET Core 2.2 and React-Redux using Entity Framework Core. Now we are ready to run our application using IIS Express. Please see the below screenshot for the first look of our application.
Let's create a new contact first by clicking the "Add" button. When we click on Add button, the following pop-up will display with the relevant input fields with few action buttons. Here, at first, "Delete" button is disabled since we are adding a new contact entry.
Now I entered all the input fields information and click on save button.
After Clicking on Save button, the information is saved successfully in database and it shows a successful toast message to user with a message "Data Saved Successfully".
Let's update the above information on selecting the particular grid row. When we click on the grid row, the same model dialog will appear with the save information. Here, I have changed a little information such as email and phone details and click on update button.
It shows the updated information with a updated toast message "Data Updated Successfully" (please see the following screenshot):
For delete operation, we need to select the grid row. When we click on the grid row, the same model dialog will appear with the save information but at this time, the "Delete" button is enabled for deleting the information.
When we click on "Delete" button, then the information will be deleted from the database and it will show a delete toast to the user with a message "Data Deleted Successfully". (Please see the following screenshot.)
That's all! We are done with our CRUD operation using ASP.NET Core 2.2 and React-Redux.
Hope you liked the article.
Happy coding!!