使用 ASP.NET CORE 2.2 和 React Redux、EntityFramework Core(数据库优先方法)及 PrimeReact 组件实现 CRUD 操作






4.74/5 (13投票s)
使用 ASP.NET CORE 2.2 和 React Redux、EntityFramework Core(数据库优先方法)及 PrimeReact 组件实现 CRUD 操作
引言
在我上一篇文章中,我解释了如何使用 ASP.NET CORE 2 和 Angular 4、Entity Framework Core(数据库优先方法)、primeng 组件和 toastr-ng2 执行 CRUD 操作,您可以在此处找到。在本文中,我们将了解如何使用 Entity Framework Core 数据库优先方法,通过 ASP.NET Core 2.2 和 React Redux 创建一个 Web 应用程序。
在我们开始实际操作之前,让我们先了解以下几点,它们会详细说明本文将涵盖的内容。
- 使用新的 Visual Studio 2019 创建 ASP.NET CORE React Redux 项目
- 创建数据库和表
- 为应用程序的业务逻辑和数据访问层创建类库
- 使用 Entity Framework Core 数据库优先方法生成实体模型类
- 创建 Service 类来处理 CRUD 操作的业务逻辑
- 设置依赖注入
- 创建 Controller 和 API 调用
- 在 package.json 文件中添加 PrimeReact 组件
- React Redux
- 运行应用程序
必备组件
请确保您已在计算机上安装了所有先决条件。如果没有,请逐一下载并安装所有软件。
首先,请从此链接下载并安装 Visual Studio 2019。下载并安装 .NET Core 2.2 SDK。
1. 使用 Visual Studio 2019 创建新的 AspNetCoreReactRedux 项目
打开 VS 2019(在本文中,我使用的是 Community Edition)并使用新的 .NET Core React Redux 模板创建新项目(请参阅以下分步截图)。
在右侧,您将看到一个用于创建新项目的新面板(请参阅高亮部分)。请点击它。
选择上述面板后,将打开以下新窗口。请选择“ASP.NET Core Web Application”来创建 SPA 应用程序。
选择上述高亮面板后,将打开一个新窗口。请为您的项目输入项目名称和位置路径。
完成上述设置后,将打开一个新窗口,在这里,我们需要从高亮下拉列表中选择“.NET Core”和“ASP.NET Core 2.2”,然后选择“React.js and Redux”模板来创建带有 React.js 和 Redux 的 ASP.NET Core 2.2 应用程序。
正如我们所见,Visual Studio 2019 为我们提供了一个内置模板来创建 React.js 和 Redux 应用程序。它会创建一个名为“ClientApp”的新文件夹,其中包含实际的 React.js 和 Redux 项目,还有一个名为“wwwroot”的特殊文件夹,用于存放我们所有的实时 Web 文件。
2. 创建数据库和表
在本文中,我将使用 Entity Framework Core 数据库优先方法。要使用此方法,我们需要在 SQL Server 中创建一个示例数据库表。请在您的 SQL Server 中运行以下脚本
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. 为应用程序的业务逻辑和数据访问层创建类库项目
我们可以在此 Web 应用程序中使用 .NET Core 类库项目。首先,您需要创建两个 .NET Core 库项目——一个用于业务逻辑,另一个用于数据访问层。只需右键单击项目解决方案并添加一个新的 .NET Core 库项目(请参阅以下截图)
选择上述“.NET Core”类库后,将出现一个新窗口。请为此业务库项目输入名称。
按照相同的步骤创建数据访问层库项目,然后在业务层项目中添加数据访问库引用,并在 Web 项目中添加业务层引用。最后,我们的项目将如下所示
4. 使用 Entity Framework Core 数据库优先方法生成实体模型类
下一步是安装 Microsoft.EntityFrameworkCore.SqlServer
(Entity Framework Core 的 Microsoft SQL Server 数据库提供程序)、Microsoft.EntityFrameworkCore.Tools
和 Microsoft.EntityFrameworkCore.Design
,这将帮助我们进一步进行 Entity Framework 操作。
添加所有 NuGet 包后,我们的数据访问库结构将如下所示
在我们的数据访问库项目中运行以下命令,从现有数据库创建实体模型类(请参阅以下截图)
请参考以下链接,了解有关从现有数据库创建模型的更多详细信息
请确保您需要将“yourservername
”替换为您自己的 SQL Server 实例名称,以便成功执行该命令。上述命令成功执行后,我们的数据访问层项目中将创建一个名为“EntityModels
”的文件夹,其中包含数据库实体。(请参阅以下截图。)
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. 创建 Service 类来处理 CRUD 操作的业务逻辑
现在我们将编写 CRUD 操作的业务逻辑。因此,让我们在业务层项目内创建两个新文件夹(即“Model”和“Service”),并在该文件夹内创建一个 interface
(即“IContactService.cs”)和一个类(即“ContactService
”),其中包含几个用于 CRUD 操作的方法。
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; }
}
}
首先,我们需要创建一个名为 ContactModel
的类。这只不过是一个 model
类,负责从源获取/传递数据,因为我们需要显示/插入数据。
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;
}
}
}
}
因此,我们可以看到,通过上述 IContactService
接口及其在 ContactService
类中的方法实现,我们为 CRUD 操作的不同目的定义了不同的方法。在这里,我们将使用 Entity Framework Core 与数据库进行交互并执行 CRUD 操作。此外,我们处理的是任务相关数据,这意味着我们可以异步地获取/保存/删除数据。我们的业务层项目已完成。现在,我们需要在 Web 项目的 startup.cs 类中注册上述服务类。
6. 设置依赖注入
让我们打开 Startup.cs 类,并使用以下代码为 ContactService
添加依赖注入:
services.AddTransient<IContactService, ContactService>();
startup.cs 类的整体结构将如下所示
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; }
// 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. 创建 Controller 和 API 调用
添加一个新控制器并命名为“ContactController
”。要添加新控制器,您需要右键单击“Controllers”文件夹并选择 Add->New Item。我们已经注入了服务类作为依赖项,以便我们可以执行 CRUD 操作。请参阅下面的 ContactController
类结构。
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. 在 Package.json 文件中添加 PrimeReact 组件
现在,我们需要修改“package.json”文件,通过添加以下依赖项来添加 PrimeReact
组件用于 UI:
"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 组件
PrimeReact
是 React 的一套丰富的 UI 组件。所有小部件均为开源,可根据 MIT 许可证免费使用。PrimeReact
由 PrimeTek Informatics 开发,该公司在开发开源 UI 解决方案方面拥有多年的经验。
在上述 package.json 文件中,我们使用了 primereact 组件(例如用于以表格形式显示数据的 datatable
),它是一套丰富的 UI 组件,并且是一个开源组件。请参阅以下链接了解更多详细信息
9. React.js 和 Redux
react-redux
库的目的是将 redux 的状态管理集成到 React 应用程序中,然而 Redux 和 React 是两个独立的库,它们可以并且已经被完全独立使用。
要理解 Redux 的概念,您需要了解“Store”的概念,它是应用程序状态的存储位置。基本上,每个有状态的 React 组件都带有自己的状态,这些状态用于存储数据,组件使用这些数据呈现给用户。
状态也可以响应操作和事件而改变。在 React 中,您可以使用“setState
”更新组件的本地状态。您可以将状态保留在单个父组件中,但当您添加更多行为时(例如,当多个 React 组件需要访问相同状态但它们之间没有父/子关系时),事情会变得更复杂,因此为了维护所有这些复杂状态,Redux 将发挥作用。Redux 将状态保存在一个位置。
在 Redux 中,状态必须完全从 reducers 返回。在我们的示例中,我们将创建一个简单的 reducer,将初始状态作为第一个参数。作为第二个参数,我们将提供 action,我将在接下来的步骤中向您解释这个概念,但在那之前,我们需要创建我们的 store,所以我们先从这里开始。
要添加新 store,您需要右键单击“ClientApp”文件夹内的“store”文件夹,然后选择 Add->New Item
-> 添加一个 JavaScript 文件并命名为“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;
}
};
在上面的文件中,我们创建了具有一些默认参数的初始状态,以及 redux reducer,这是 Redux 中最重要的概念。此 reducer 生成应用程序的状态,在这里,我们需要指定状态和适当的 CRUD 操作。
export const reducer = (state, action) => {
state = state || initialState;
我们还需要定义 action creator,其中每个 action 都需要一个 type
属性来描述状态应如何改变,它还会消耗 API 调用来执行 crud 操作。要更改 Redux 中的状态,我们需要 dispatch 一个 action。要 dispatch 一个 action,您必须调用 dispatch
方法。(请参阅以下代码片段以获取所有联系人信息的检索。)
dispatch({ type: 'FETCH_CONTACTS', contacts });
ContactList.js
现在,我们需要定义我们的联系人组件及其视图详细信息,它负责处理我们应用程序中的所有 crud 操作。(请参阅以下代码了解更多详细信息。)在这里,您将看到一个最重要的“connect
”方法,它负责将 react 组件与我们的 redux store 连接起来。对于这一点,我将传递两个参数(即“mapStateToProps
”和“bindActionCreators
”)来连接到 redux store。
我还在此“ContactList
”组件中添加了 primereact 组件来处理所有 CRUD 相关活动(例如,使用 primereact 的 datatable
以网格形式显示数据,使用 InputText
组件在文本框中输入值等)。ContactList
组件的结构如下
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
打开store文件夹中已存在的“configureStore.js”文件,并将我们的“Contact
”store 导入到此文件中。
import * as Contact from './Contact';
这是 configureStore.js 文件的结构。
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)
);
}
在此代码中,我们将 reducers 传递给 Redux createStore
函数,该函数返回一个 store
对象。然后,我们将此对象传递给 react-redux Provider 组件。
Layout.js
现在打开component文件夹中已存在的“Layout.js”文件,并导入自定义 CSS 以获得 UI 外观和风格。请参阅下面的 Layout.js 文件结构,其中导入了我们的 primereact
自定义 CSS 文件。
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
现在打开“App.js”文件并导入我们的联系人组件。通常,“App.js”用于路由目的。请参阅下面的结构。
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 文件用于在 Web 应用程序的标题部分显示导航链接。对于本文,我稍微修改了 NavMenu.js 以仅用于路由目的
<NavLink tag={Link} className="text-dark" to="/contacts">Contact</NavLink>
这是我们“NavMenu.js”文件的结构。
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. 运行应用程序
最后,我们完成了使用 Entity Framework Core 的 ASP.NET Core 2.2 和 React-Redux 的 CRUD 操作的实现。现在我们可以使用 IIS Express 运行我们的应用程序了。请参阅以下截图,了解我们应用程序的初始外观。
让我们先创建一个新联系人,方法是单击“添加”按钮。当我们单击 添加 按钮时,将显示以下弹出窗口,其中包含相关的输入字段和一些操作按钮。在这里,最初,“删除”按钮是禁用的,因为我们正在添加一个新的联系人条目。
现在我已填写所有输入字段信息,然后单击保存按钮。
单击保存按钮后,信息将成功保存到数据库,并向用户显示成功的 toast 消息,消息为“数据已成功保存”。
让我们通过选择特定网格行来更新上述信息。当我们单击网格行时,将出现相同的模型对话框,其中包含保存的信息。在这里,我更改了一些信息,例如电子邮件和电话详细信息,然后单击更新按钮。
它显示更新后的信息,并显示更新的 toast 消息“数据已成功更新”(请参阅以下截图)
对于删除操作,我们需要选择网格行。当我们单击网格行时,将出现相同的模型对话框,其中包含保存的信息,但此时,“删除”按钮已启用,用于删除信息。
当我们单击“删除”按钮时,信息将从数据库中删除,并向用户显示一条删除 toast 消息,消息为“数据已成功删除”。(请参阅以下截图。)
就这样!我们完成了使用 ASP.NET Core 2.2 和 React-Redux 的 CRUD 操作。
希望您喜欢这篇文章。
祝您编码愉快!!