Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C#

CRUD Operation using ASP.NET CORE 2.2 and React Redux with EntityFramework Core (Database First Approach) and PrimeReact Components

Rate me:
Please Sign up or sign in to vote.
4.74/5 (13 votes)
24 Apr 2019CPOL11 min read 48.1K   2.2K   24   18
CRUD Operation using ASP.NET CORE 2.2 and React Redux with EntityFramework Core (Database First Approach) and PrimeReact Components

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.

  1. Create ASP.NET CORE React Redux Project using new Visual Studio 2019
  2. Create the database and table
  3. Create class libraries for application’s business logic and data access layer
  4. Generate the entity model classes using Entity Framework Core Database first approach
  5. Create Service class to hold the business logic for CRUD operation
  6. Set up the Dependency Injection
  7. Create a Controller and Create API Calls
  8. Adding PrimeReact components in package.json file
  9. React Redux
  10. 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).

Image 1

On the right hand side, you will see a new panel for creating new project (see the highlighted section). Please click on it.

Image 2

After selecting the above panel, the following new window is open. Please select "ASP.NET Core Web Application" for creating the SPA application.

Image 3

After selecting the above highlighted panel, a new window will open. Please enter the name of the project and location path for your project.

Image 4

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.

Image 5

Image 6

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:

SQL
USE [master]
GO
/****** Object:  Database [ContactDB]    Script Date: 4/21/2019 4:11:09 PM ******/
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
/****** Object:  Table [dbo].[Contacts]    Script Date: 4/21/2019 4:11:09 PM ******/
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):

Image 7

After selecting the above ".NET Core" class library, a new window appears. Please enter name of your business library project.

Image 8

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:

Image 9

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.

Image 10

After adding all NuGet Packages, our data access library structure will be as follows:

Image 11

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:

Image 12

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.)

Image 13

ContactDBContext.cs

SQL
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

SQL
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

C++
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

C++
namespace BusinessLibrary.Service
{
    public interface IContactService
    {
        Task<List<ContactModel>> GetContacts();
        Task<bool> SaveContact(ContactModel contact);
        Task<bool> DeleteContact(int contactId);
    }
}

ContactService.cs

C#
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::

C++
services.AddTransient<IContactService, ContactService>();

The whole structure of startup.cs class will be as follows:

Startup.cs

C#
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; }

        // This method gets called by the runtime. 
        // Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IContactService, ContactService>();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });
        }

        // This method gets called by the runtime. 
        // Use this method to configure the HTTP request pipeline.
        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.

C#
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.

JavaScript
"primereact": "3.1.2"

Package.json

JavaScript
{
  "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".

Image 14

Contact.js

JavaScript
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.

C++
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.)

JavaScript
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:

C#
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() {
        // This method is called when the route parameters change
        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>
        )
    }
}

// Make contacts array available in  props
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.

JavaScript
import * as Contact from './Contact';

Here is the structure of configureStore.js file.

SQL
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.

SQL
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.

SQL
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:

XML
<NavLink tag={Link} className="text-dark" to="/contacts">Contact</NavLink>

Here is the structure of our "NavMenu.js" file.

SQL
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.

Image 15

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.

Image 16

Now I entered all the input fields information and click on save button.

Image 17

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".

Image 18

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.

Image 19

It shows the updated information with a updated toast message "Data Updated Successfully" (please see the following screenshot):

Image 20

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.

Image 21

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.)

Image 22

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!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
India India
My name is Prashant Ramteke. I have 11+ years of experience in Microsoft technologies. Currently working as a Senior Team Lead.
Currently focusing on C#, ASP.NET Core, EntityFramework core, JavaScript, Angular,React.js,Redux and Microservices.

Comments and Discussions

 
QuestionGetting Error Pin
Member 862847919-Jun-20 3:37
Member 862847919-Jun-20 3:37 
QuestionCan you provide the ts version of this Pin
sivateja donthukurthi21-Jan-20 18:52
professionalsivateja donthukurthi21-Jan-20 18:52 
Questionre-crud in TS Pin
ROPU_0505197129-Dec-19 0:36
ROPU_0505197129-Dec-19 0:36 
Questionmissing www.root Pin
ROPU_0505197111-Nov-19 19:14
ROPU_0505197111-Nov-19 19:14 
QuestionCan`t run application Pin
Ondra Fejtek30-Sep-19 1:56
Ondra Fejtek30-Sep-19 1:56 
QuestionHow to do step 2. Create the Database and Table Pin
erikh30-Jun-19 22:26
erikh30-Jun-19 22:26 
QuestionGuide Pin
mina mohammadzadeh Rasaa20-Jun-19 2:10
mina mohammadzadeh Rasaa20-Jun-19 2:10 
QuestionHow to pass connection string from main app? Pin
Win32nipuh30-May-19 2:38
professionalWin32nipuh30-May-19 2:38 
I'd like to avoid connection string hardcoding in assembly but
read it from appsettings.json and pass like this:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
...
 services.AddDbContext<ContactDBContext>(options => 
 options.UseSqlServer(Configuration.GetConnectionString(nameof(ContactDBContext))));
..
}

but in any case this string is not passed (?) and used this:
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;");
            }
        }


How to pass the connection string from the app?

Solved: edited the code and added some additional code.

modified 30-May-19 11:51am.

AnswerRe: How to pass connection string from main app? Pin
melbir30-May-19 4:43
professionalmelbir30-May-19 4:43 
GeneralRe: How to pass connection string from main app? Pin
PrashantRamteke31-May-19 3:43
PrashantRamteke31-May-19 3:43 
QuestionForm İnput Validation Pin
melbir29-May-19 22:13
professionalmelbir29-May-19 22:13 
QuestionThanks a great post. Pin
SidbarPronto29-May-19 17:46
SidbarPronto29-May-19 17:46 
QuestionGreat easy to follow!! Pin
asmsoftware26-May-19 0:14
asmsoftware26-May-19 0:14 
AnswerRe: Great easy to follow!! Pin
PrashantRamteke28-May-19 22:53
PrashantRamteke28-May-19 22:53 
QuestionInternet Explorer Pin
Member 143581796-May-19 21:42
Member 143581796-May-19 21:42 
PraiseExcellent Article.... Pin
Mukesh Sagar2-May-19 20:21
Mukesh Sagar2-May-19 20:21 
GeneralRe: Excellent Article.... Pin
PrashantRamteke2-May-19 22:15
PrashantRamteke2-May-19 22:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.