1
0
Форкнуть 0
This commit is contained in:
simonssspirit 2021-11-30 11:54:48 +02:00
Родитель eb5be5cf53
Коммит 648cc4569b
40 изменённых файлов: 3984 добавлений и 1 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -348,3 +348,7 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# React App node_modules and package-lock.json
**/**/package-lock.json
**/**/node_modules

Просмотреть файл

@ -1,2 +1,6 @@
# kendo-react-datagrid-asp-net-core
# Kendo React Data Grid ASP.NET Core
This repository contains a sample application showcasing how to implement common data operations for the KendoReact Data Grid with an ASP.NET Core backend. The project focuses on how data operations like paging, sorting, filtering, and grouping are triggered on the client and can be handled on the sever-side of things. The ultimate goal is to showcase a full-stack project and demystify the client and server-side interactions with an advanced UI component like the data grid.
This example is made with .NET version 6.
We have another example made with a older version of .NET and ASP.NET Core which is also showing a Grid with editing. You can find it here: https://github.com/telerik/kendo-react-demo-aspnetcore-data

25
kendo-react-grid-crud.sln Normal file
Просмотреть файл

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.31903.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kendo-react-grid-crud", "kendo-react-grid-crud\kendo-react-grid-crud.csproj", "{FFC169F6-AC82-4854-9CC2-0CCEB1CD6D68}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FFC169F6-AC82-4854-9CC2-0CCEB1CD6D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFC169F6-AC82-4854-9CC2-0CCEB1CD6D68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFC169F6-AC82-4854-9CC2-0CCEB1CD6D68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFC169F6-AC82-4854-9CC2-0CCEB1CD6D68}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {926949BE-7D53-4FC2-AFE3-6EC17FC532C9}
EndGlobalSection
EndGlobal

232
kendo-react-grid-crud/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,232 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
bin/
Bin/
obj/
Obj/
# Visual Studio 2015 cache/options directory
.vs/
/wwwroot/dist/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
orleans.codegen.cs
/node_modules
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

Просмотреть файл

@ -0,0 +1 @@
BROWSER=none

Просмотреть файл

@ -0,0 +1,3 @@
PORT=44487
HTTPS=true

21
kendo-react-grid-crud/ClientApp/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,33 @@
// This script sets up HTTPS for the application using the ASP.NET Core HTTPS certificate
const fs = require('fs');
const spawn = require('child_process').spawn;
const path = require('path');
const baseFolder =
process.env.APPDATA !== undefined && process.env.APPDATA !== ''
? `${process.env.APPDATA}/ASP.NET/https`
: `${process.env.HOME}/.aspnet/https`;
const certificateArg = process.argv.map(arg => arg.match(/--name=(?<value>.+)/i)).filter(Boolean)[0];
const certificateName = certificateArg ? certificateArg.groups.value : process.env.npm_package_name;
if (!certificateName) {
console.error('Invalid certificate name. Run this script in the context of an npm/yarn script or pass --name=<<app>> explicitly.')
process.exit(-1);
}
const certFilePath = path.join(baseFolder, `${certificateName}.pem`);
const keyFilePath = path.join(baseFolder, `${certificateName}.key`);
if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) {
spawn('dotnet', [
'dev-certs',
'https',
'--export-path',
certFilePath,
'--format',
'Pem',
'--no-password',
], { stdio: 'inherit', })
.on('exit', (code) => process.exit(code));
}

Просмотреть файл

@ -0,0 +1,55 @@
// This script configures the .env.development.local file with additional environment variables to configure HTTPS using the ASP.NET Core
// development certificate in the webpack development proxy.
const fs = require('fs');
const path = require('path');
const baseFolder =
process.env.APPDATA !== undefined && process.env.APPDATA !== ''
? `${process.env.APPDATA}/ASP.NET/https`
: `${process.env.HOME}/.aspnet/https`;
const certificateArg = process.argv.map(arg => arg.match(/--name=(?<value>.+)/i)).filter(Boolean)[0];
const certificateName = certificateArg ? certificateArg.groups.value : process.env.npm_package_name;
if (!certificateName) {
console.error('Invalid certificate name. Run this script in the context of an npm/yarn script or pass --name=<<app>> explicitly.')
process.exit(-1);
}
const certFilePath = path.join(baseFolder, `${certificateName}.pem`);
const keyFilePath = path.join(baseFolder, `${certificateName}.key`);
if (!fs.existsSync('.env.development.local')) {
fs.writeFileSync(
'.env.development.local',
`SSL_CRT_FILE=${certFilePath}
SSL_KEY_FILE=${keyFilePath}`
);
} else {
let lines = fs.readFileSync('.env.development.local')
.toString()
.split('\n');
let hasCert, hasCertKey = false;
for (const line of lines) {
if (/SSL_CRT_FILE=.*/i.test(line)) {
hasCert = true;
}
if (/SSL_KEY_FILE=.*/i.test(line)) {
hasCertKey = true;
}
}
if (!hasCert) {
fs.appendFileSync(
'.env.development.local',
`\nSSL_CRT_FILE=${certFilePath}`
);
}
if (!hasCertKey) {
fs.appendFileSync(
'.env.development.local',
`\nSSL_KEY_FILE=${keyFilePath}`
);
}
}

Просмотреть файл

@ -0,0 +1,83 @@
{
"name": "kendo_react_grid_crud",
"version": "0.1.0",
"private": true,
"dependencies": {
"@progress/kendo-data-query": "^1.5.5",
"@progress/kendo-drawing": "^1.15.0",
"@progress/kendo-licensing": "^1.2.1",
"@progress/kendo-react-animation": "^4.10.0",
"@progress/kendo-react-data-tools": "^4.10.0",
"@progress/kendo-react-dateinputs": "^4.10.0",
"@progress/kendo-react-dropdowns": "^4.10.0",
"@progress/kendo-react-grid": "^4.10.0",
"@progress/kendo-react-inputs": "^4.10.0",
"@progress/kendo-react-intl": "^4.10.0",
"@progress/kendo-react-treelist": "^4.10.0",
"@progress/kendo-react-treeview": "^4.10.0",
"@progress/kendo-theme-default": "^4.42.0",
"axios": "^0.24.0",
"bootstrap": "^5.1.0",
"http-proxy-middleware": "^0.19.1",
"jquery": "^3.5.1",
"merge": "^2.1.1",
"oidc-client": "^1.11.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3",
"reactstrap": "^8.9.0",
"rimraf": "^2.6.2",
"web-vitals": "^0.2.4",
"workbox-background-sync": "^5.1.3",
"workbox-broadcast-update": "^5.1.3",
"workbox-cacheable-response": "^5.1.3",
"workbox-core": "^5.1.3",
"workbox-expiration": "^5.1.3",
"workbox-google-analytics": "^5.1.3",
"workbox-navigation-preload": "^5.1.3",
"workbox-precaching": "^5.1.3",
"workbox-range-requests": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"workbox-streams": "^5.1.3"
},
"devDependencies": {
"ajv": "^6.9.1",
"cross-env": "^7.0.3",
"eslint": "^7.25.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-flowtype": "^5.7.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.2",
"nan": "^2.14.2",
"typescript": "^4.2.4"
},
"scripts": {
"prestart": "node aspnetcore-https && node aspnetcore-react",
"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/"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Двоичные данные
kendo-react-grid-crud/ClientApp/public/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.3 KiB

Просмотреть файл

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<base href="%PUBLIC_URL%/" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>kendo_react_grid_crud</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
{
"short_name": "kendo_react_grid_crud",
"name": "kendo_react_grid_crud",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

Просмотреть файл

@ -0,0 +1,20 @@
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { Counter } from './components/Counter';
import './custom.css'
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
</Layout>
);
}
}

Просмотреть файл

@ -0,0 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
it('renders without crashing', async () => {
const div = document.createElement('div');
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>, div);
await new Promise(resolve => setTimeout(resolve, 1000));
});

Просмотреть файл

@ -0,0 +1,31 @@
import React, { Component } from 'react';
export class Counter extends Component {
static displayName = Counter.name;
constructor(props) {
super(props);
this.state = { currentCount: 0 };
this.incrementCounter = this.incrementCounter.bind(this);
}
incrementCounter() {
this.setState({
currentCount: this.state.currentCount + 1
});
}
render() {
return (
<div>
<h1>Counter</h1>
<p>This is a simple example of a React component.</p>
<p aria-live="polite">Current count: <strong>{this.state.currentCount}</strong></p>
<button className="k-button" onClick={this.incrementCounter}>Increment</button>
</div>
);
}
}

Просмотреть файл

@ -0,0 +1,271 @@
import '@progress/kendo-theme-default/dist/all.css'
import { useState, useEffect } from "react";
import { Grid, GridColumn, GridToolbar } from '@progress/kendo-react-grid';
import { mapTree } from "@progress/kendo-react-treelist";
import { clone } from '@progress/kendo-react-common';
/**
* Import a custom command cell responsible for rendering the Edit, Remove, Update and Cancel commands.
*/
import MyCommandCell from './cells/CommandCell';
/**
* Import a custom cell responsible for rendering and editing complext fields with a DropDownList.
*/
import DropDownCell from './cells/DropDownCell';
/**
* Import the React.Context use to pass extra props to the custom cell.
*/
import DataContext from '../contexts/data-context';
import Axios from "axios";
import { toDataSourceRequestString, translateDataSourceResultGroups } from '@progress/kendo-data-query';
const Home = (props) => {
const [data, setData] = useState([]);
const [itemBeforeEdit, setItemBeforeEdit] = useState({})
const [dataState, setDataState] = useState({ take: 5, skip: 0, group: [] })
const [total, setTotal] = useState(0);
// Check if the Grid is grouped.
const hasGroups = dataState.group && dataState.group.length;
// Make request to the server for server side data operations.
useEffect(() => {
Axios.post("https://localhost:44487/products", toDataSourceRequestString(dataState)).then((response) => {
let parsedDataNew = mapTree(response.data.data, 'items', (product) => {
product.firstOrderedOn = product.firstOrderedOn !== null ? new Date(product.firstOrderedOn) : null;
return product
})
parsedDataNew = hasGroups ? translateDataSourceResultGroups(parsedDataNew) : parsedDataNew
setTotal(response.data.total)
setData([...parsedDataNew]);
});
}, [dataState]) // We make the request initially and everytime the dataState is changed.
/**
* Add a new empty item only to the local data.
*/
const addRecord = () => {
let newDate = new Date();
// We use this to remvoe time porting of the date based on the timezone. In this example we edit and filter only the date portion.
var myToday = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), 0, 0, 0);
let offsetMiliseconds = new Date().getTimezoneOffset() * 60000;
let dateWitnNotimeZone = new Date(
myToday.getTime() - offsetMiliseconds
);
let newRecord = { productID: null, firstOrderedOn: dateWitnNotimeZone, category: { categoryID: 1, categoryName: "Beverages"}, inEdit: true }
let newData = [...data];
newData.unshift(newRecord);
setData(newData)
}
/**
* Update the local data when the user edits a field.
*/
const handleItemChange = (event) => {
let newData = mapTree(data, 'items', item => {
if (event.dataItem.productID === item.productID) {
item[event.field] = event.value;
}
return item;
})
setData(newData);
}
/**
* Put the row in edit mode.
*/
const enterEdit = (dataItem) => {
let newData = mapTree(data, "items", (item) => {
dataItem.productID === item.productID ? item.inEdit = true : item.inEdit = false;
return item;
});
setItemBeforeEdit(clone(dataItem));
setData(newData);
}
/**
* Make a request to the server to delete a specific item.
*/
const remove = (dataItem) => {
// We process the dataState to make it compatible with the server requirements.
const data = {
filter: toDataSourceRequestString({ filter: dataState.filter }).replace("filter=", ""),
sort: toDataSourceRequestString({ sort: dataState.sort }).replace("sort=", ""),
group: toDataSourceRequestString({ group: dataState.group }).replace("group=", ""),
take: dataState.take,
skip: dataState.skip
}
Axios.delete("https://localhost:44487/products", { data: { ...data, product: dataItem }}).then(
(response) => {
let parsedDataNew = mapTree(response.data.data, 'items', item => {
item.firstOrderedOn = new Date(item.firstOrderedOn);
return item;
})
parsedDataNew = hasGroups ? translateDataSourceResultGroups(parsedDataNew) : parsedDataNew
setData(parsedDataNew);
setTotal(response.data.total);
}
);
}
/**
* Make a request to the server to create a new item.
*/
const add = (dataItem) => {
let headers = {
headers: {
'Content-Type': 'application/json',
}
}
// We process the dataState to make it compatible with the server requirements.
const data = {
filter: toDataSourceRequestString({ filter: dataState.filter }).replace("filter=", ""),
sort: toDataSourceRequestString({ sort: dataState.sort }).replace("sort=", ""),
group: toDataSourceRequestString({ group: dataState.group }).replace("group=", ""),
take: dataState.take,
skip: dataState.skip
}
Axios.put("https://localhost:44487/products", { ...data, product: dataItem }, headers).then(
(response) => {
let parsedDataNew = mapTree(response.data.data, 'items', item => {
item.firstOrderedOn = new Date(item.firstOrderedOn);
return item;
})
parsedDataNew = hasGroups ? translateDataSourceResultGroups(parsedDataNew) : parsedDataNew
setData(parsedDataNew);
setTotal(response.data.total);
}
);
}
/**
* Remove a new item that has not been still saved on the server.
*/
const discard = () => {
let hasGroup = dataState.group.length > 0 ? true : false
let newData = []
hasGroup ? newData = data.filter(item => item.value !== undefined) : newData = data.filter(item => item.productID !== null)
setData(newData);
}
/**
* Make a request to the server to update a specific item.
*/
const update = (dataItem) => {
// We use this to remvoe time porting of the date based on the timezone. In this example we edit and filter only the date portion.
var myToday = new Date(dataItem.firstOrderedOn.getFullYear(), dataItem.firstOrderedOn.getMonth(), dataItem.firstOrderedOn.getDate(), 0, 0, 0);
let offsetMiliseconds = new Date().getTimezoneOffset() * 60000;
let dateWitnNotimeZone = new Date(
myToday.getTime() - offsetMiliseconds
);
dataItem.firstOrderedOn = dateWitnNotimeZone;
let headers = {
headers: {
'Content-Type': 'application/json',
}
}
// We process the dataState to make it compatible with the server requirements.
const data = {
filter: toDataSourceRequestString({ filter: dataState.filter }).replace("filter=", ""),
sort: toDataSourceRequestString({ sort: dataState.sort }).replace("sort=", ""),
group: toDataSourceRequestString({ group: dataState.group }).replace("group=", ""),
take: dataState.take,
skip: dataState.skip
}
Axios.put("https://localhost:44487/products", { ...data, product: dataItem }, headers).then(
(response) => {
let parsedDataNew = mapTree(response.data.data, 'items', item => {
item.firstOrderedOn = new Date(item.firstOrderedOn);
return item;
})
parsedDataNew = hasGroups ? translateDataSourceResultGroups(parsedDataNew) : parsedDataNew
setData(parsedDataNew);
}
);
}
/**
* Cancel the changes made to an item before they were saved on the server.
*/
const cancel = () => {
let newData = mapTree(data, 'items', item => {
if (item.productID === itemBeforeEdit.productID) {
item = itemBeforeEdit;
item.inEdit = false;
}
return item;
})
setData(newData);
}
const handleDataStateChange = (event) => {
setDataState(event.dataState);
}
return (
<DataContext.Provider
value={{
enterEdit: enterEdit,
remove: remove,
add: add,
discard: discard,
update: update,
cancel: cancel
}}
>
<Grid
style={{
height: "520px",
}}
data={data}
editField="inEdit"
onItemChange={handleItemChange}
onDataStateChange={handleDataStateChange}
{...dataState}
pageable
sortable
filterable
groupable
total={total}
>
<GridToolbar>
<div>
<button
title="Add new"
className="k-button k-primary"
onClick={addRecord}
>
Add new
</button>
</div>
</GridToolbar>
<GridColumn field="productID" title="Id" width="100px" editable={false} filterable={false} />
<GridColumn field="productName" title="Name" />
<GridColumn field="category.categoryName" title="Category" cell={DropDownCell} />
<GridColumn field="firstOrderedOn" title="First Ordered On" editor="date" filter='date' format={'{0:d}'} />
<GridColumn
field="unitsInStock"
title="Units"
width="150px"
editor="numeric"
filter="numeric"
/>
<GridColumn field="discontinued" title="Discontinued" editor="boolean" filter="boolean" />
<GridColumn width="200px" cell={MyCommandCell} />
</Grid>
</DataContext.Provider>
)
}
export { Home };

Просмотреть файл

@ -0,0 +1,18 @@
import React, { Component } from 'react';
import { Container } from 'reactstrap';
import { NavMenu } from './NavMenu';
export class Layout extends Component {
static displayName = Layout.name;
render () {
return (
<div>
<NavMenu />
<Container>
{this.props.children}
</Container>
</div>
);
}
}

Просмотреть файл

@ -0,0 +1,18 @@
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}

Просмотреть файл

@ -0,0 +1,49 @@
import React, { Component } from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';
export class NavMenu extends Component {
static displayName = NavMenu.name;
constructor (props) {
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.state = {
collapsed: true
};
}
toggleNavbar () {
this.setState({
collapsed: !this.state.collapsed
});
}
render () {
return (
<header>
<Navbar className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3" light>
<Container>
<NavbarBrand tag={Link} to="/">kendo_react_grid_crud</NavbarBrand>
<NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={!this.state.collapsed} navbar>
<ul className="navbar-nav flex-grow">
<NavItem>
<NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
</ul>
</Collapse>
</Container>
</Navbar>
</header>
);
}
}

Просмотреть файл

@ -0,0 +1,57 @@
import * as React from "react";
import DataContext from '../../contexts/data-context';
const MyCommandCell = props => {
const currentContext = React.useContext(DataContext);
const { dataItem } = props;
const isNewItem = dataItem.productID === null;
const inEdit = dataItem.inEdit;
const handleAddUpdate = React.useCallback(() =>{
if(isNewItem){
currentContext.add(dataItem)
} else {
currentContext.update(dataItem)
}
},[currentContext, dataItem, isNewItem])
const handleDiscardCancel = React.useCallback(()=>{
isNewItem ? currentContext.discard(dataItem) : currentContext.cancel()
},[currentContext, dataItem, isNewItem])
const handleEdit = React.useCallback(()=> {
currentContext.enterEdit(dataItem)
},[currentContext, dataItem])
const handleDelete = React.useCallback(()=> {
window.confirm("Confirm deleting: " + dataItem.productName) && currentContext.remove(dataItem)
}, [currentContext, dataItem])
/**
* Return null if this is a group header cell.
*/
if(props.rowType === 'groupHeader') return null;
return inEdit ?
(<td className="k-command-cell">
<button className="k-button k-grid-save-command" onClick={handleAddUpdate}>
{isNewItem ? "Add" : "Update"}
</button>
<button className="k-button k-grid-cancel-command" onClick={handleDiscardCancel}>
{isNewItem ? "Discard" : "Cancel"}
</button>
</td>) :
(<td className="k-command-cell">
<button className="k-primary k-button k-grid-edit-command" onClick={handleEdit}>
Edit
</button>
<button className="k-button k-grid-remove-command" onClick={handleDelete}>
Remove
</button>
</td>);
};
export default MyCommandCell;

Просмотреть файл

@ -0,0 +1,78 @@
import * as React from 'react';
import {
DropDownList,
} from '@progress/kendo-react-dropdowns';
// This data can also be passed from the Context if it is available in the main component.
const categoryData = [
{
categoryID: 1,
categoryName: 'Beverages'
},
{
categoryID: 2,
categoryName: 'Condiments'
},
{
categoryID: 6,
categoryName: 'Meat/Poultry'
},
{
categoryID: 7,
categoryName: 'Produce'
},
{
categoryID: 8,
categoryName: 'Seafood'
},
];
const DropDownCell = (props) => {
/**
* Return null if this is a group header cell.
*/
if (props.rowType === 'groupHeader') return null;
/**
* Get both keys for a complex field like Category.CategoryName.
*/
let fieldComplex = props.field.split('.');
const handleChange = (e) => {
if (props.onChange) {
props.onChange({
dataIndex: 0,
dataItem: props.dataItem,
field: fieldComplex[0],
syntheticEvent: e.syntheticEvent,
value: e.value,
});
}
};
const { dataItem } = props;
const dataValue =
dataItem[fieldComplex[0]] === null || dataItem[fieldComplex[0]][fieldComplex[1]] === null
? ''
: dataItem[fieldComplex[0]][fieldComplex[1]];
return (
<td>
{dataItem.inEdit ? (
<DropDownList
style={{ width: '100%' }}
onChange={handleChange}
value={dataItem[fieldComplex[0]]}
data={categoryData}
textField={fieldComplex[1]}
defaultItem={{ categoryID: 0, categoryName: 'Choose Category' }}
/>
) : (
dataValue.toString()
)}
</td>
);
};
export default DropDownCell;

Просмотреть файл

@ -0,0 +1,12 @@
import { createContext } from 'react';
const DataContext = createContext({
enterEdit: () => {},
remove: () => {},
add: () => {},
discard: () => {},
update: () => {},
cancel: () => {}
});
export default DataContext;

Просмотреть файл

@ -0,0 +1,14 @@
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
code {
color: #E01A76;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

Просмотреть файл

@ -0,0 +1,26 @@
import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const rootElement = document.getElementById('root');
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App />
</BrowserRouter>,
rootElement);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.unregister();
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Просмотреть файл

@ -0,0 +1,13 @@
const reportWebVitals = (onPerfEntry) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

Просмотреть файл

@ -0,0 +1,72 @@
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.

Просмотреть файл

@ -0,0 +1,137 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://cra.link/PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.');
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}

Просмотреть файл

@ -0,0 +1,18 @@
const createProxyMiddleware = require('http-proxy-middleware');
const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:58261';
const context = [
"/products",
];
module.exports = function(app) {
const appProxy = createProxyMiddleware(context, {
target: target,
secure: false
});
app.use(appProxy);
};

Просмотреть файл

@ -0,0 +1,132 @@
using Kendo.Mvc;
using Kendo.Mvc.Examples.Models;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.Infrastructure;
using Kendo.Mvc.UI;
using Microsoft.AspNetCore.Mvc;
namespace kendo_react_grid_crud.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductsController : Controller
{
private static ProductViewModel[] ProductViewModelData;
public class PostModel
{
public ProductViewModel? Product { get; set; }
public int Take { get; set; }
public int Skip { get; set; }
public string? Sort { get; set; }
public string? Filter { get; set; }
public string? Group { get; set; }
}
private readonly ILogger<ProductsController> _logger;
public ProductsController(ILogger<ProductsController> logger)
{
if (ProductViewModelData == null) {
ProductViewModelData = new ProductViewModel[]
{
new ProductViewModel(){ProductID = 1, ProductName = "Chai", FirstOrderedOn = DateTime.Today, UnitsInStock = 18, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 1, CategoryName = "Beverages"} },
new ProductViewModel(){ProductID = 2, ProductName = "Chang", FirstOrderedOn = DateTime.Today, UnitsInStock = 19, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 1, CategoryName = "Beverages"} },
new ProductViewModel(){ProductID = 3, ProductName = "Aniseed Syrup", FirstOrderedOn = DateTime.Today, UnitsInStock = 10, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 2, CategoryName = "Condiments"} },
new ProductViewModel(){ProductID = 4, ProductName = "Chef Anton's Cajun Seasoning", FirstOrderedOn = DateTime.Today, UnitsInStock = 22, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 2, CategoryName = "Condiments"} },
new ProductViewModel(){ProductID = 5, ProductName = "Chef Anton's Gumbo Mix", FirstOrderedOn = DateTime.Today, UnitsInStock = 23, Discontinued = true, Category = new CategoryViewModel(){ CategoryID = 2, CategoryName = "Condiments"} },
new ProductViewModel(){ProductID = 6, ProductName = "Grandma's Boysenberry Spread", FirstOrderedOn = DateTime.Today, UnitsInStock = 25, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 2, CategoryName = "Condiments"} },
new ProductViewModel(){ProductID = 7, ProductName = "Uncle Bob's Organic Dried Pears", FirstOrderedOn = DateTime.Today, UnitsInStock = 30, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 7, CategoryName = "Produce"} },
new ProductViewModel(){ProductID = 8, ProductName = "Northwoods Cranberry Sauce", FirstOrderedOn = DateTime.Today, UnitsInStock = 40, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 2, CategoryName = "Condiments"} },
new ProductViewModel(){ProductID = 9, ProductName = "Mishi Kobe Niku", FirstOrderedOn = DateTime.Today, UnitsInStock = 97, Discontinued = true, Category = new CategoryViewModel(){ CategoryID = 6, CategoryName = "Meat/Poultry"} },
new ProductViewModel(){ProductID = 10, ProductName = "Ikura", FirstOrderedOn = DateTime.Today, UnitsInStock = 31, Discontinued = false, Category = new CategoryViewModel(){ CategoryID = 8, CategoryName = "Seafood"} },
};
}
_logger = logger;
}
[HttpGet]
public IEnumerable<ProductViewModel> Get()
{
return ProductViewModelData.ToArray();
}
[HttpPost]
public DataSourceResult Post([DataSourceRequest] DataSourceRequest request)
{
return ProductViewModelData.ToDataSourceResult(request);
}
[HttpPut]
public JsonResult Put([FromBody] PostModel postModel)
{
var dsRequest = new DataSourceRequest();
dsRequest.Take = postModel.Take;
dsRequest.Skip = postModel.Skip;
dsRequest.PageSize = postModel.Take;
// we check if this is an existing or a new item
if(postModel.Product != null && postModel.Product.ProductID != null)
{
// We update the local data, but in a real Application here is the place to update the database;
var productToBeChanged = ProductViewModelData.Where(x => x.ProductID == postModel.Product.ProductID).FirstOrDefault();
productToBeChanged.ProductName = postModel.Product.ProductName;
productToBeChanged.Discontinued = postModel.Product.Discontinued;
productToBeChanged.Category = postModel.Product.Category;
productToBeChanged.UnitsInStock = postModel.Product.UnitsInStock;
productToBeChanged.FirstOrderedOn = postModel.Product.FirstOrderedOn;
} else
{
postModel.Product.ProductID = DateTime.Now.Millisecond;
ProductViewModelData = ProductViewModelData.Prepend(postModel.Product).ToArray();
}
if (!string.IsNullOrEmpty(postModel.Filter))
{
dsRequest.Filters = FilterDescriptorFactory.Create(postModel.Filter);
}
if (!string.IsNullOrEmpty(postModel.Sort))
{
dsRequest.Sorts = DataSourceDescriptorSerializer.Deserialize<SortDescriptor>(postModel.Sort);
}
if (!string.IsNullOrEmpty(postModel.Group))
{
dsRequest.Groups = DataSourceDescriptorSerializer.Deserialize<GroupDescriptor>(postModel.Group);
}
var result = Json(ProductViewModelData.ToDataSourceResult(dsRequest));
return result;
}
[HttpDelete]
public JsonResult Delete([FromBody] PostModel postModel)
{
var dsRequest = new DataSourceRequest();
dsRequest.Take = postModel.Take;
dsRequest.Skip = postModel.Skip;
dsRequest.PageSize = postModel.Take;
// We Delete the local data, but in a real Application here is the place to mark it as detele it the database;
ProductViewModelData = ProductViewModelData.Where(val => val.ProductID != postModel.Product.ProductID).ToArray();
if (!string.IsNullOrEmpty(postModel.Filter))
{
dsRequest.Filters = FilterDescriptorFactory.Create(postModel.Filter);
}
if (!string.IsNullOrEmpty(postModel.Sort))
{
dsRequest.Sorts = DataSourceDescriptorSerializer.Deserialize<SortDescriptor>(postModel.Sort);
}
if (!string.IsNullOrEmpty(postModel.Group))
{
dsRequest.Groups = DataSourceDescriptorSerializer.Deserialize<GroupDescriptor>(postModel.Group);
}
var result = Json(ProductViewModelData.ToDataSourceResult(dsRequest));
return result;
}
}
}

Просмотреть файл

@ -0,0 +1,70 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System;
namespace Kendo.Mvc.Examples.Models
{
public class CategoryViewModel
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
public class ProductViewModel
{
public int? ProductID
{
get;
set;
}
[Required]
[Display(Name = "Product name")]
public string? ProductName
{
get;
set;
}
[Display(Name = "Unit price")]
public decimal? UnitPrice
{
get;
set;
}
[Display(Name = "Units in stock")]
[DataType("Integer")]
public int? UnitsInStock
{
get;
set;
}
public bool? Discontinued
{
get;
set;
}
[Display(Name = "First Ordered On")]
[DataType(DataType.Date)]
public DateTime FirstOrderedOn
{
get;
set;
}
[DataType("Integer")]
public int? UnitsOnOrder
{
get;
set;
}
public CategoryViewModel? Category
{
get;
set;
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

Просмотреть файл

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace kendo_react_grid_crud.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

Просмотреть файл

@ -0,0 +1,3 @@
@using kendo_react_grid_crud
@namespace kendo_react_grid_crud.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Просмотреть файл

@ -0,0 +1,29 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews().AddNewtonsoftJson();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseDefaultFiles();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html"); ;
app.Run();

Просмотреть файл

@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58261",
"sslPort": 44387
}
},
"profiles": {
"kendo_react_grid_crud": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:7266;http://localhost:5266",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.AspNetCore.SpaProxy": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Просмотреть файл

@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<SpaProxyServerUrl>https://localhost:44487</SpaProxyServerUrl>
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
<RootNamespace>kendo_react_grid_crud</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="6.0.0" />
<PackageReference Include="Telerik.DataSource" Version="2.1.0" />
<PackageReference Include="Telerik.UI.for.AspNet.Core" Version="2021.3.1109" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>