add ASP.NET Core with Kendo Angular app (#4372)
* add ASP.NET Core with Kendo Angular app * remove readme from the client app * chore: add example to the build
This commit is contained in:
Родитель
30a4d913ce
Коммит
934194611c
|
@ -77,6 +77,11 @@ jobs:
|
|||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Build ASP.NET Core Integration app
|
||||
working-directory: ./examples-standalone/kendoangular-aspnetcore-integration/ClientApp
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
standalone-examples-node14:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"kendoangular_aspnetcore": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"progress": false,
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"src/polyfills.ts",
|
||||
"@angular/localize/init"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"allowedCommonJsDependencies": [
|
||||
"oidc-client"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"input": "node_modules/@progress/kendo-theme-default/dist/all.css"
|
||||
},
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1mb",
|
||||
"maximumError": "2mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "kendoangular_aspnetcore:build:production"
|
||||
},
|
||||
"development": {
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"buildTarget": "kendoangular_aspnetcore:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "kendoangular_aspnetcore:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": [
|
||||
"src/polyfills.ts",
|
||||
"@angular/localize/init"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"input": "node_modules/@progress/kendo-theme-default/dist/all.css"
|
||||
},
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist-server",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "tsconfig.server.json",
|
||||
"polyfills": [
|
||||
"@angular/localize/init"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"dev": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": true,
|
||||
"buildOptimizer": true
|
||||
},
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
|
@ -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,44 @@
|
|||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/angularapp'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
22955
examples-standalone/kendoangular-aspnetcore-integration/ClientApp/package-lock.json
сгенерированный
Normal file
22955
examples-standalone/kendoangular-aspnetcore-integration/ClientApp/package-lock.json
сгенерированный
Normal file
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"name": "kendoangular_aspnetcore",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"prestart": "node aspnetcore-https",
|
||||
"start": "run-script-os",
|
||||
"start:windows": "ng serve --port 44406 --ssl --ssl-cert \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.pem\" --ssl-key \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.key\"",
|
||||
"start:default": "ng serve --port 44406 --ssl --ssl-cert \"$HOME/.aspnet/https/${npm_package_name}.pem\" --ssl-key \"$HOME/.aspnet/https/${npm_package_name}.key\"",
|
||||
"build": "ng build",
|
||||
"build:ssr": "ng run kendoangular_aspnetcore:server:dev",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.0.5",
|
||||
"@angular/common": "^18.0.5",
|
||||
"@angular/compiler": "^18.0.5",
|
||||
"@angular/core": "^18.0.5",
|
||||
"@angular/forms": "^18.0.5",
|
||||
"@angular/localize": "^18.0.5",
|
||||
"@angular/platform-browser": "^18.0.5",
|
||||
"@angular/platform-browser-dynamic": "^18.0.5",
|
||||
"@angular/platform-server": "^18.0.5",
|
||||
"@angular/router": "^18.0.5",
|
||||
"@progress/kendo-angular-buttons": "16.3.0",
|
||||
"@progress/kendo-angular-common": "16.3.0",
|
||||
"@progress/kendo-angular-dateinputs": "16.3.0",
|
||||
"@progress/kendo-angular-dialog": "16.3.0",
|
||||
"@progress/kendo-angular-dropdowns": "16.3.0",
|
||||
"@progress/kendo-angular-excel-export": "16.3.0",
|
||||
"@progress/kendo-angular-grid": "^16.3.0",
|
||||
"@progress/kendo-angular-icons": "16.3.0",
|
||||
"@progress/kendo-angular-inputs": "16.3.0",
|
||||
"@progress/kendo-angular-intl": "16.3.0",
|
||||
"@progress/kendo-angular-l10n": "16.3.0",
|
||||
"@progress/kendo-angular-label": "16.3.0",
|
||||
"@progress/kendo-angular-layout": "16.3.0",
|
||||
"@progress/kendo-angular-navigation": "16.3.0",
|
||||
"@progress/kendo-angular-pdf-export": "16.3.0",
|
||||
"@progress/kendo-angular-popup": "16.3.0",
|
||||
"@progress/kendo-angular-progressbar": "16.3.0",
|
||||
"@progress/kendo-angular-treeview": "16.3.0",
|
||||
"@progress/kendo-angular-upload": "^16.3.0",
|
||||
"@progress/kendo-angular-utils": "16.3.0",
|
||||
"@progress/kendo-data-query": "^1.0.0",
|
||||
"@progress/kendo-drawing": "^1.19.0",
|
||||
"@progress/kendo-licensing": "^1.0.2",
|
||||
"@progress/kendo-svg-icons": "^3.0.0",
|
||||
"@progress/kendo-theme-default": "^8.0.1",
|
||||
"bootstrap": "^5.2.3",
|
||||
"jquery": "^3.6.4",
|
||||
"oidc-client": "^1.11.5",
|
||||
"popper.js": "^1.16.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"rxjs": "~7.8.1",
|
||||
"tslib": "^2.5.0",
|
||||
"zone.js": "~0.14.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.6",
|
||||
"@angular/cli": "^18.0.6",
|
||||
"@angular/compiler-cli": "^18.0.5",
|
||||
"@types/jasmine": "~4.3.1",
|
||||
"@types/jasminewd2": "~2.0.10",
|
||||
"@types/node": "^18.16.3",
|
||||
"jasmine-core": "~4.6.0",
|
||||
"karma": "~6.4.2",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.0.0",
|
||||
"typescript": "~5.4.5"
|
||||
},
|
||||
"overrides": {
|
||||
"autoprefixer": "10.4.5",
|
||||
"webpack": "5.81.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
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:61909';
|
||||
|
||||
const PROXY_CONFIG = [
|
||||
{
|
||||
context: [
|
||||
"/products",
|
||||
"products/get-products",
|
||||
"/products/create-product",
|
||||
"/products/update-product",
|
||||
"/products/delete-product",
|
||||
"/products/delete-product/:id",
|
||||
"/chunk/upload",
|
||||
"/chunk/remove",
|
||||
"/upload",
|
||||
"/remove",
|
||||
],
|
||||
proxyTimeout: 10000,
|
||||
target: target,
|
||||
secure: false,
|
||||
headers: {
|
||||
Connection: 'Keep-Alive'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
|
@ -0,0 +1,6 @@
|
|||
<body>
|
||||
<app-nav-menu></app-nav-menu>
|
||||
<main class="container">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
</body>
|
|
@ -0,0 +1,9 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
templateUrl: "./app.component.html",
|
||||
})
|
||||
export class AppComponent {
|
||||
title = "app";
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { APP_ID, NgModule } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { NavMenuComponent } from "./nav-menu/nav-menu.component";
|
||||
import { HomeComponent } from "./home/home.component";
|
||||
import { FetchDataComponent } from "./fetch-data/fetch-data.component";
|
||||
import { UploadComponent } from "./upload/upload.component";
|
||||
|
||||
import { GridModule } from "@progress/kendo-angular-grid";
|
||||
import { UploadsModule } from "@progress/kendo-angular-upload";
|
||||
import { ProductService } from "./fetch-data/products.service";
|
||||
|
||||
function getBaseUrl() {
|
||||
return document.getElementsByTagName("base")[0].href;
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [AppComponent, NavMenuComponent, HomeComponent, FetchDataComponent, UploadComponent],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
// Deprecated
|
||||
// BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
GridModule,
|
||||
UploadsModule,
|
||||
RouterModule.forRoot([
|
||||
{ path: "", component: HomeComponent, pathMatch: "full" },
|
||||
{ path: "upload", component: UploadComponent },
|
||||
{ path: "fetch-data", component: FetchDataComponent },
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
ProductService,
|
||||
[{ provide: APP_ID, useValue: "ng-cli-universal" }],
|
||||
{ provide: "BASE_URL", useFactory: getBaseUrl },
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
|
@ -0,0 +1,10 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { ServerModule } from '@angular/platform-server';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [AppModule, ServerModule],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppServerModule { }
|
|
@ -0,0 +1,42 @@
|
|||
<p>
|
||||
<a href="https://www.telerik.com/kendo-angular-ui/components/grid/">Grid component</a> demonstrating fetching data
|
||||
from the server with CRUD operations.
|
||||
</p>
|
||||
<kendo-grid
|
||||
[data]="gridData"
|
||||
[groupable]="true"
|
||||
[sortable]="true"
|
||||
[pageable]="true"
|
||||
[filterable]="true"
|
||||
[skip]="state.skip"
|
||||
[pageSize]="state.take"
|
||||
[group]="state.group"
|
||||
[sort]="state.sort"
|
||||
[filter]="state.filter"
|
||||
(edit)="editHandler($event)"
|
||||
(cancel)="cancelHandler($event)"
|
||||
(save)="saveHandler($event)"
|
||||
(remove)="removeHandler($event)"
|
||||
(add)="addHandler($event)"
|
||||
(dataStateChange)="dataStateChange($event)"
|
||||
style="margin-bottom: 10px"
|
||||
>
|
||||
<ng-template kendoGridToolbarTemplate>
|
||||
<button kendoGridAddCommand>Add new</button>
|
||||
</ng-template>
|
||||
<kendo-grid-column field="productID" title="ID" editor="numeric" filter="numeric"> </kendo-grid-column>
|
||||
<kendo-grid-column field="productName" title="Name" editor="text" filter="text"> </kendo-grid-column>
|
||||
<kendo-grid-column field="unitPrice" title="Price" editor="numeric" filter="numeric"> </kendo-grid-column>
|
||||
<kendo-grid-command-column title="command" [width]="220">
|
||||
<ng-template kendoGridCellTemplate let-isNew="isNew">
|
||||
<button kendoGridEditCommand [primary]="true">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
<button kendoGridSaveCommand [disabled]="formGroup?.invalid ?? false">
|
||||
{{ isNew ? "Add" : "Update" }}
|
||||
</button>
|
||||
<button kendoGridCancelCommand>
|
||||
{{ isNew ? "Discard changes" : "Cancel" }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
|
@ -0,0 +1,113 @@
|
|||
import { Component, inject, OnInit } from "@angular/core";
|
||||
import { FormGroup, FormControl, Validators } from "@angular/forms";
|
||||
|
||||
import {
|
||||
AddEvent,
|
||||
CancelEvent,
|
||||
DataStateChangeEvent,
|
||||
EditEvent,
|
||||
GridComponent,
|
||||
GridDataResult,
|
||||
RemoveEvent,
|
||||
SaveEvent,
|
||||
} from "@progress/kendo-angular-grid";
|
||||
import { State } from "@progress/kendo-data-query";
|
||||
|
||||
import { ProductService } from "./products.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-fetch-data",
|
||||
templateUrl: "./fetch-data.component.html",
|
||||
})
|
||||
export class FetchDataComponent implements OnInit {
|
||||
public gridData!: GridDataResult;
|
||||
public productService = inject(ProductService);
|
||||
public editedRowIndex: number | undefined = undefined;
|
||||
public formGroup?: FormGroup;
|
||||
public state: State = {
|
||||
skip: 0,
|
||||
take: 5,
|
||||
filter: { filters: [], logic: "or" },
|
||||
group: [],
|
||||
sort: [],
|
||||
};
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getProducts(this.state);
|
||||
}
|
||||
|
||||
public dataStateChange(state: DataStateChangeEvent): void {
|
||||
this.state = state;
|
||||
this.getProducts(state);
|
||||
}
|
||||
|
||||
public addHandler(addEvent: AddEvent): void {
|
||||
this.closeEditor(addEvent.sender);
|
||||
|
||||
this.formGroup = new FormGroup({
|
||||
productID: new FormControl(null, Validators.required),
|
||||
productName: new FormControl(null, Validators.required),
|
||||
unitPrice: new FormControl(null, [Validators.required, Validators.min(0)]),
|
||||
});
|
||||
|
||||
addEvent.sender.addRow(this.formGroup);
|
||||
}
|
||||
|
||||
public editHandler(editEvent: EditEvent): void {
|
||||
this.closeEditor(editEvent.sender);
|
||||
|
||||
const { productID, productName, unitPrice } = editEvent.dataItem;
|
||||
|
||||
this.formGroup = new FormGroup({
|
||||
productID: new FormControl(productID),
|
||||
productName: new FormControl(productName),
|
||||
unitPrice: new FormControl(unitPrice),
|
||||
});
|
||||
|
||||
this.editedRowIndex = editEvent.rowIndex;
|
||||
editEvent.sender.editRow(editEvent.rowIndex, this.formGroup);
|
||||
}
|
||||
|
||||
public cancelHandler(cancelEvent: CancelEvent): void {
|
||||
this.closeEditor(cancelEvent.sender, cancelEvent.rowIndex);
|
||||
}
|
||||
|
||||
public saveHandler(saveEvent: SaveEvent): void {
|
||||
const product = saveEvent.formGroup.value;
|
||||
if (saveEvent.isNew) {
|
||||
this.productService.createProduct(product).subscribe({
|
||||
next: () => this.getProducts(this.state),
|
||||
error: (error) => console.error(error),
|
||||
});
|
||||
} else {
|
||||
const productId = saveEvent.dataItem.productID;
|
||||
this.productService.updateProduct(productId, product).subscribe({
|
||||
next: () => this.getProducts(this.state),
|
||||
error: (error) => console.error(error),
|
||||
});
|
||||
}
|
||||
saveEvent.sender.closeRow(saveEvent.rowIndex);
|
||||
}
|
||||
|
||||
public removeHandler(removeEvent: RemoveEvent): void {
|
||||
this.productService.deleteProduct(removeEvent.dataItem.productID).subscribe({
|
||||
next: () => this.getProducts(this.state),
|
||||
error: (error) => console.error(error),
|
||||
});
|
||||
}
|
||||
|
||||
private closeEditor(grid: GridComponent, rowIndex: number | undefined = this.editedRowIndex): void {
|
||||
grid.closeRow(rowIndex);
|
||||
this.editedRowIndex = undefined;
|
||||
this.formGroup = undefined;
|
||||
}
|
||||
|
||||
private getProducts(state: State): void {
|
||||
this.productService.getProducts(state).subscribe({
|
||||
next: (result) => {
|
||||
this.gridData = result;
|
||||
},
|
||||
error: (error) => console.error(error),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { Inject, Injectable } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { map, Observable } from "rxjs";
|
||||
import { State, toDataSourceRequestString, translateDataSourceResultGroups } from "@progress/kendo-data-query";
|
||||
import { GridDataResult } from "@progress/kendo-angular-grid";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class ProductService {
|
||||
constructor(private http: HttpClient, @Inject("BASE_URL") private readonly baseUrl: string) {}
|
||||
|
||||
public getProducts(state: State): Observable<GridDataResult> {
|
||||
const url = `${this.baseUrl}products/get-products?${toDataSourceRequestString(state)}`;
|
||||
const hasGroups = state.group && state.group.length;
|
||||
|
||||
return this.http.post<GridDataResult>(url, {}).pipe(
|
||||
map(({ data, total }) => ({
|
||||
// If the data is grouped, translate it to a compatible format using the translateDataSourceResultGroups helper.
|
||||
data: hasGroups ? translateDataSourceResultGroups(data) : data,
|
||||
total: total,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
public createProduct(product: any): Observable<any> {
|
||||
const url = `${this.baseUrl}products/create-product`;
|
||||
return this.http.post(url, product);
|
||||
}
|
||||
|
||||
public updateProduct(productId: number, product: any): Observable<any> {
|
||||
const url = `${this.baseUrl}products/update-product/${productId}`;
|
||||
return this.http.put(url, product);
|
||||
}
|
||||
|
||||
public deleteProduct(productId: number): Observable<any> {
|
||||
const url = `${this.baseUrl}products/delete-product/${productId}`;
|
||||
return this.http.delete(url);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<app-fetch-data></app-fetch-data>
|
||||
<app-upload></app-upload>
|
|
@ -0,0 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
})
|
||||
export class HomeComponent {
|
||||
}
|
|
@ -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 0.25rem 0.75rem rgba(0, 0, 0, 0.05);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" [routerLink]="['/']"
|
||||
>Kendo UI for Angular Integrated with an ASP.NET Core Backend
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-toggle="collapse"
|
||||
data-target=".navbar-collapse"
|
||||
aria-label="Toggle navigation"
|
||||
[attr.aria-expanded]="isExpanded"
|
||||
(click)="toggle()"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-end" [ngClass]="{ show: isExpanded }">
|
||||
<ul class="navbar-nav flex-grow">
|
||||
<li
|
||||
class="nav-item"
|
||||
[routerLinkActive]="['link-active']"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<a class="nav-link text-dark" [routerLink]="['/']">Home</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']">
|
||||
<a class="nav-link text-dark" [routerLink]="['/upload']">Upload</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']">
|
||||
<a class="nav-link text-dark" [routerLink]="['/fetch-data']">Fetch Grid Data</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
|
@ -0,0 +1,18 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-nav-menu",
|
||||
templateUrl: "./nav-menu.component.html",
|
||||
styleUrls: ["./nav-menu.component.css"],
|
||||
})
|
||||
export class NavMenuComponent {
|
||||
isExpanded = false;
|
||||
|
||||
collapse() {
|
||||
this.isExpanded = false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<div class="row">
|
||||
<div class="info">
|
||||
<p>
|
||||
The Upload helps users send files from their file systems to dedicated server handlers which are configured
|
||||
to receive them.
|
||||
</p>
|
||||
<p>
|
||||
It is a richer version of an
|
||||
<code><![CDATA[<input type="file" />]]></code> element and supports model binding, templates, forms and
|
||||
more.
|
||||
</p>
|
||||
<p>
|
||||
For more information check the
|
||||
<a href="https://www.telerik.com/kendo-angular-ui/components/uploads/upload/">Upload documentation</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="component info">
|
||||
<kendo-upload [saveUrl]="'upload'" [removeUrl]="'/remove'"> </kendo-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="info">
|
||||
<p>
|
||||
The Chunk Upload allows splitting files into smaller chunks and sending them asynchronously through multiple
|
||||
requests to the server.
|
||||
</p>
|
||||
<p>
|
||||
The
|
||||
<a href="https://www.telerik.com/kendo-angular-ui/components/uploads/api/ChunkSettings/">ChunkSettings</a>
|
||||
parameter enables you to specify the size of each chunk, number of attempts to retry uploading a failed
|
||||
chunk, pause and later resume the process.
|
||||
</p>
|
||||
<p>
|
||||
For more information check the
|
||||
<a href="https://www.telerik.com/kendo-angular-ui/components/uploads/upload/chunk-upload/"
|
||||
>Chunk Upload documentation.</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="component info">
|
||||
<kendo-upload [saveUrl]="'/chunk/upload'" [removeUrl]="'/chunk/remove'" [chunkable]="chunkSettings">
|
||||
</kendo-upload>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { ChunkSettings } from "@progress/kendo-angular-upload";
|
||||
|
||||
@Component({
|
||||
selector: "app-upload",
|
||||
templateUrl: "./upload.component.html",
|
||||
styleUrl: "./upload.component.css",
|
||||
})
|
||||
export class UploadComponent {
|
||||
public chunkSettings: ChunkSettings = {
|
||||
size: 102400,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>kendoangular_aspnetcore</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root>Loading...</app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
/// <reference types="@angular/localize" />
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
export function getBaseUrl() {
|
||||
return document.getElementsByTagName('base')[0].href;
|
||||
}
|
||||
|
||||
const providers = [
|
||||
{ provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }
|
||||
];
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic(providers).bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/**
|
||||
* IE11 requires the following for NgClass support on SVG elements
|
||||
*/
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
/* Provide sufficient contrast against white background */
|
||||
a {
|
||||
color: #0366d6;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #e01a76;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
|
@ -0,0 +1,17 @@
|
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": [
|
||||
"@angular/localize"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2022",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
],
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node",
|
||||
"@angular/localize"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace aspnetcore_upload.Controllers
|
||||
{
|
||||
public class ChunkUploadController : Controller
|
||||
{
|
||||
private readonly IWebHostEnvironment _webHostingEnvironment;
|
||||
private readonly ILogger<ChunkUploadController> _logger;
|
||||
|
||||
public ChunkUploadController(IWebHostEnvironment webHostingEnvironment, ILogger<ChunkUploadController> logger)
|
||||
{
|
||||
_webHostingEnvironment = webHostingEnvironment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ActionResult Save(List<IFormFile> files)
|
||||
{
|
||||
if (files != null)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
_logger.LogDebug($"Save(): Processing {file.FileName}.");
|
||||
|
||||
// Some browsers send file names with full path. This needs to be stripped.
|
||||
var fileName = Path.GetFileName(file.FileName);
|
||||
// As an example, we are writing the file to the wwwroot directory. You should modify this to suit your needs.
|
||||
// var physicalPath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", fileName);
|
||||
|
||||
// using(var uploadedFile = file.OpenReadStream())
|
||||
// using (var stream = new FileStream(physicalPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
|
||||
// {
|
||||
// uploadedFile.CopyTo(stream);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Return Ok to signify success
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("chunk/upload")]
|
||||
[HttpPost]
|
||||
public ActionResult ChunkSave(List<IFormFile> files, string metaData)
|
||||
{
|
||||
// This is not a chunk upload, pass it off to the normal Save method
|
||||
if (metaData == null)
|
||||
{
|
||||
return Save(files);
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream(Encoding.UTF8.GetBytes(metaData));
|
||||
var serializer = new DataContractJsonSerializer(typeof(ChunkMetaData)); // 'ChunkMetaData' class is defined below
|
||||
var metadata = serializer.ReadObject(ms) as ChunkMetaData;
|
||||
|
||||
// The Name of the Upload component is "files"
|
||||
if (files != null)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
// ********************************************************************** //
|
||||
// ** Scenario 1: Concurrent=false - Supports Sequential Chunk Upload ** //
|
||||
// ********************************************************************** //
|
||||
|
||||
// If the upload is in sequential order, you can just append the chunks to what has already been written to disk
|
||||
//var path = Path.Combine(_root, somemetaData.FileName);
|
||||
//using (var uploadedChunkStream = file.OpenReadStream())
|
||||
//using(var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
|
||||
//{
|
||||
// uploadedChunkStream.CopyTo(stream);
|
||||
//}
|
||||
|
||||
|
||||
// ********************************************************************* //
|
||||
// ** Scenario 2: Concurrent=true - Supports Concurrent Chunk Upload ** //
|
||||
// ********************************************************************* //
|
||||
|
||||
// To support concurrent chunk upload, you need a system to keep track of each chunk and assemble them in the correct
|
||||
// order when all the chunks have been uploaded. The ChunkMetaData object gives you all the information you need.
|
||||
|
||||
// Step 1. Save the chunk
|
||||
// As an example, we are writing the file to the wwwroot directory. You should modify this to suit your needs.
|
||||
// var tempChunkFilePath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", $"{metadata.FileName}_{metadata.ChunkIndex}");
|
||||
// using (var uploadedChunkStream = file.OpenReadStream())
|
||||
// using (var chunkFileStream = System.IO.File.OpenWrite(tempChunkFilePath))
|
||||
// {
|
||||
// uploadedChunkStream.CopyTo(chunkFileStream);
|
||||
|
||||
// _logger.LogDebug($"ChunkSave(): Saved {file.FileName} to Chunk #{metadata.ChunkIndex + 1} of {metadata.TotalChunks}.");
|
||||
// }
|
||||
|
||||
// Step 2. Lets check to see if we have all the chunks
|
||||
// If we do, then we can assemble them all into a final destination file
|
||||
// CombineChunks(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// Return Ok to signify success
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("Chunk/Remove")]
|
||||
[HttpPost]
|
||||
public ActionResult Async_Remove(string[] fileNames)
|
||||
{
|
||||
if (fileNames != null)
|
||||
{
|
||||
foreach (var fullName in fileNames)
|
||||
{
|
||||
var fileName = Path.GetFileName(fullName);
|
||||
// As an example, we are deleting the file from the wwwroot directory. You should modify this to suit your needs.
|
||||
// var physicalPath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", fileName);
|
||||
|
||||
// TODO: Verify user permissions
|
||||
|
||||
// if (System.IO.File.Exists(physicalPath))
|
||||
// {
|
||||
// System.IO.File.Delete(physicalPath);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Return Ok to signify success
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
private void CombineChunks(ChunkMetaData metadata)
|
||||
{
|
||||
// ------------------- //
|
||||
// PHASE 1 - VERIFYING
|
||||
// ------------------- //
|
||||
|
||||
for (var chunkIndex = 0; chunkIndex <= metadata.TotalChunks - 1; chunkIndex++)
|
||||
{ // As an example, we are checking the existence of the file in the wwwroot directory. You should modify this to suit your needs.
|
||||
// var chunkFilePath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", $"{metadata.FileName}_{chunkIndex}");
|
||||
|
||||
// If any of these chunk files are missing, then we know we're not done, break and exit.
|
||||
// if (!System.IO.File.Exists(chunkFilePath))
|
||||
// {
|
||||
// // Exit the method entirely
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
// ------------------- //
|
||||
// PHASE 2 - COMBINING
|
||||
// ------------------- //
|
||||
|
||||
_logger.LogDebug($"Combining all chunks for {metadata.FileName}.");
|
||||
|
||||
// Create a single file to combine everything
|
||||
// As an example, we are writing the file to the wwwroot directory. You should modify this to suit your needs.
|
||||
// var tempFinalFilePath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", $"tmp_{metadata.FileName}");
|
||||
|
||||
// Open a file stream
|
||||
// using (var destStream = System.IO.File.Create(tempFinalFilePath))
|
||||
{
|
||||
// iterate over each chunk, in the order of the ChunkIndex
|
||||
for (var chunkIndex = 0; chunkIndex <= metadata.TotalChunks - 1; chunkIndex++)
|
||||
{
|
||||
// var chunkFileName = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", $"{metadata.FileName}_{chunkIndex}");
|
||||
|
||||
// _logger.LogDebug($"Appending ({chunkIndex + 1} of {metadata.TotalChunks}): {chunkFileName}.");
|
||||
|
||||
// Open the chunk's filestream
|
||||
// using var sourceStream = System.IO.File.OpenRead(chunkFileName);
|
||||
|
||||
// Copy the chunk to the end of the main file stream
|
||||
// sourceStream.CopyTo(destStream);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- //
|
||||
// PHASE 3 - FINALIZING
|
||||
// -------------------- //
|
||||
|
||||
// Now that we have a combined file, lets move it to the final destination
|
||||
// Some browsers send file names with full path. This needs to be stripped.
|
||||
var cleanFileName = Path.GetFileName(metadata.FileName);
|
||||
// As an example, we are writing the file to the wwwroot directory. You should modify this to suit your needs.
|
||||
// var filePath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", cleanFileName);
|
||||
|
||||
// if (System.IO.File.Exists(filePath))
|
||||
// System.IO.File.Delete(filePath);
|
||||
|
||||
// System.IO.File.Move(tempFinalFilePath, filePath);
|
||||
|
||||
// _logger.LogDebug($"Moved: {tempFinalFilePath} to {filePath}.");
|
||||
|
||||
// ----------------- //
|
||||
// PHASE 4 - CLEANUP
|
||||
// ----------------- //
|
||||
|
||||
// With the final final assembled and moved, we need to delete all the temporary chunk files
|
||||
|
||||
_logger.LogDebug($"Deleting temporary chunk files for {metadata.FileName}..");
|
||||
|
||||
for (var chunkIndex = 0; chunkIndex <= metadata.TotalChunks - 1; chunkIndex++)
|
||||
{
|
||||
// As an example, we are deleting the file from the wwwroot directory. You should modify this to suit your needs.
|
||||
// var chunkFileName = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", $"{metadata.FileName}_{chunkIndex}");
|
||||
|
||||
// if (System.IO.File.Exists(chunkFileName))
|
||||
// {
|
||||
// System.IO.File.Delete(chunkFileName);
|
||||
|
||||
// _logger.LogDebug($"Deleted chunk: {chunkFileName}.");
|
||||
// }
|
||||
}
|
||||
|
||||
_logger.LogDebug($"*** Upload and recombination complete! *** File: {cleanFileName}");
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ChunkMetaData
|
||||
{
|
||||
[DataMember(Name = "uploadUid")]
|
||||
public string? UploadUid { get; set; }
|
||||
[DataMember(Name = "fileName")]
|
||||
public string? FileName { get; set; }
|
||||
[DataMember(Name = "contentType")]
|
||||
public string? ContentType { get; set; }
|
||||
[DataMember(Name = "chunkIndex")]
|
||||
public long ChunkIndex { get; set; }
|
||||
[DataMember(Name = "totalChunks")]
|
||||
public long TotalChunks { get; set; }
|
||||
[DataMember(Name = "totalFileSize")]
|
||||
public long TotalFileSize { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Kendo.Mvc.Extensions;
|
||||
using Kendo.Mvc.UI;
|
||||
// To add Telerik NuGet packages, check the Telerik UI for ASP.NET Core documentation: https://docs.telerik.com/reporting/getting-started/installation/adding-private-nuget-feed
|
||||
|
||||
|
||||
namespace Kendo_ASP.NET_Core_Angular.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("products")]
|
||||
public class ProductController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ProductController> _logger;
|
||||
|
||||
public ProductController(ILogger<ProductController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static List<Product> Products = new List<Product>
|
||||
{
|
||||
new Product
|
||||
{
|
||||
ProductID = 1,
|
||||
ProductName = "Chai",
|
||||
UnitPrice = 18,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 2,
|
||||
ProductName = "Chang",
|
||||
UnitPrice = 19,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 3,
|
||||
ProductName = "Aniseed Syrup",
|
||||
UnitPrice = 10,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 4,
|
||||
ProductName = "Chef Anton's Cajun Seasoning",
|
||||
UnitPrice = 22,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 5,
|
||||
ProductName = "Chef Anton's Gumbo Mix",
|
||||
UnitPrice = 21.35M,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 6,
|
||||
ProductName = "Grandma's Boysenberry Spread",
|
||||
UnitPrice = 25,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 7,
|
||||
ProductName = "Uncle Bob's Organic Dried Pears",
|
||||
UnitPrice = 30,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 8,
|
||||
ProductName = "Northwoods Cranberry Sauce",
|
||||
UnitPrice = 40,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 9,
|
||||
ProductName = "Mishi Kobe Niku",
|
||||
UnitPrice = 97,
|
||||
|
||||
},
|
||||
new Product
|
||||
{
|
||||
ProductID = 10,
|
||||
ProductName = "Ikura",
|
||||
UnitPrice = 31,
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
[HttpPost]
|
||||
[Route("get-products")]
|
||||
public ActionResult GetProducts([DataSourceRequest] DataSourceRequest request)
|
||||
{
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request);
|
||||
_logger.LogInformation("Data requested: {RequestJson}", requestJson);
|
||||
|
||||
var result = Products.AsQueryable().ToDataSourceResult(request);
|
||||
|
||||
var resultJson = JsonConvert.SerializeObject(result);
|
||||
_logger.LogInformation("Data result: {ResultJson}", resultJson);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("create-product")]
|
||||
public IActionResult CreateProduct([FromBody] Product product)
|
||||
{
|
||||
Products.Add(product);
|
||||
|
||||
_logger.LogInformation("Product created: {Product}", JsonConvert.SerializeObject(product));
|
||||
return Ok(product);
|
||||
}
|
||||
|
||||
|
||||
[HttpPut]
|
||||
[Route("update-product/{originalProductId}")]
|
||||
public IActionResult UpdateProduct(int originalProductId, [FromBody] Product product)
|
||||
{
|
||||
var existingProduct = Products.FirstOrDefault(p => p.ProductID == originalProductId);
|
||||
if (existingProduct == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
existingProduct.ProductName = product.ProductName;
|
||||
existingProduct.UnitPrice = product.UnitPrice;
|
||||
existingProduct.ProductID = product.ProductID;
|
||||
|
||||
_logger.LogInformation("Product updated: {Product}", JsonConvert.SerializeObject(existingProduct));
|
||||
return Ok(existingProduct);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[Route("delete-product/{id:int}")]
|
||||
public IActionResult DeleteProduct(int id)
|
||||
{
|
||||
Console.WriteLine("Product deleted: {0}", id);
|
||||
var product = Products.FirstOrDefault(p => p.ProductID == id);
|
||||
if (product == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Products.Remove(product);
|
||||
_logger.LogInformation("Product deleted: {0}", id);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using System.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace upload.Controllers
|
||||
{
|
||||
public class StreamingController : Controller
|
||||
{
|
||||
private readonly IWebHostEnvironment _webHostingEnvironment;
|
||||
private readonly ILogger<StreamingController> _logger;
|
||||
|
||||
public StreamingController(IWebHostEnvironment webHostingEnvironment, ILogger<StreamingController> logger)
|
||||
{
|
||||
_webHostingEnvironment = webHostingEnvironment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Route("upload")]
|
||||
[HttpPost]
|
||||
public IActionResult OnPostUpload(List<IFormFile> files)
|
||||
{
|
||||
foreach (var formFile in files)
|
||||
{
|
||||
if (formFile.Length > 0)
|
||||
{
|
||||
var fileContent = ContentDispositionHeaderValue.Parse(formFile.ContentDisposition);
|
||||
|
||||
// Some browsers send file names with full path.
|
||||
// We are only interested in the file name.
|
||||
var fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
|
||||
|
||||
// Implement your own saving logic here
|
||||
// var physicalPath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", fileName);
|
||||
// _logger.LogInformation("Uploading file: {FileName} to {Path}", fileName, physicalPath);
|
||||
// using (var fileStream = new FileStream(physicalPath, FileMode.Create))
|
||||
// {
|
||||
// await formFile.CopyToAsync(fileStream);
|
||||
// }
|
||||
// _logger.LogInformation("File uploaded successfully: {FileName}", fileName);
|
||||
|
||||
_logger.LogInformation("Received file: {FileName}", fileName);
|
||||
}
|
||||
}
|
||||
|
||||
// Return OK to signify success
|
||||
return Ok();
|
||||
}
|
||||
[Route("/remove")]
|
||||
[HttpPost]
|
||||
public ActionResult Async_Remove(string[] fileNames)
|
||||
{
|
||||
if (fileNames != null)
|
||||
{
|
||||
foreach (var fullName in fileNames)
|
||||
{
|
||||
var fileName = Path.GetFileName(fullName);
|
||||
// Implement your own logic to delete the file
|
||||
// var physicalPath = Path.Combine(_webHostingEnvironment.WebRootPath, "Upload_Directory", fileName);
|
||||
|
||||
// TODO: Verify user permissions
|
||||
|
||||
// if (System.IO.File.Exists(physicalPath))
|
||||
// {
|
||||
// _logger.LogInformation("Deleting file: {FileName} from {Path}", fileName, physicalPath);
|
||||
// System.IO.File.Delete(physicalPath);
|
||||
// _logger.LogInformation("File deleted successfully: {FileName}", fileName);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _logger.LogWarning("File not found: {FileName}", fileName);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Return OK to signify success
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace Kendo_ASP.NET_Core_Angular;
|
||||
|
||||
public class Product
|
||||
{
|
||||
public int ProductID { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
}
|
||||
|
||||
// public class Category
|
||||
// {
|
||||
// public int CategoryID { get; set; }
|
||||
// public string CategoryName { 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,25 @@
|
|||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace kendoangular_aspnetcore.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 kendoangular_aspnetcore
|
||||
@namespace kendoangular_aspnetcore.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
@ -0,0 +1,27 @@
|
|||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
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.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:61909",
|
||||
"sslPort": 44303
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"kendoangular_aspnetcore": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7085;http://localhost:5235",
|
||||
"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,53 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<SpaRoot>ClientApp\</SpaRoot>
|
||||
<SpaProxyServerUrl>https://localhost:44406</SpaProxyServerUrl>
|
||||
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
|
||||
<RootNamespace>kendoangular_aspnetcore</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.16" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
|
||||
<PackageReference Include="Telerik.DataSource" Version="3.0.1" />
|
||||
<PackageReference Include="Telerik.UI.for.AspNet.Core" Version="2024.2.514" />
|
||||
</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 -- --configuration production" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kendoangular-aspnetcore", "kendoangular-aspnetcore.csproj", "{3052D6C6-5789-42F9-A50D-016C771978C3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3052D6C6-5789-42F9-A50D-016C771978C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3052D6C6-5789-42F9-A50D-016C771978C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3052D6C6-5789-42F9-A50D-016C771978C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3052D6C6-5789-42F9-A50D-016C771978C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {458A1771-17E0-4CF3-B1FF-8BA9094665EF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,50 @@
|
|||
# Using Kendo UI for Angular Grid and Upload component with ASP.NET Core 8.0
|
||||
|
||||
This is a sample project that demonstrates how to use Kendo UI for Angular [Grid](https://www.telerik.com/kendo-angular-ui/components/grid/) and [Upload](https://www.telerik.com/kendo-angular-ui/components/uploads/upload/) component with ASP.NET Core based on the Microsoft [ASP.NET Core template](https://learn.microsoft.com/en-us/aspnet/core/client-side/spa/angular?view=aspnetcore-8.0&tabs=visual-studio).
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Clone this repository by running the following command:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/telerik/kendo-angular/.git
|
||||
```
|
||||
|
||||
1. Make sure to have the [.NET Core 8 SDK](https://dotnet.microsoft.com/download) installed on your machine, along with [Angular CLI 18.0.6](https://v17.angular.io/guide/setup-local#install-the-angular-cli).
|
||||
|
||||
1. Navigate to the project folder:
|
||||
|
||||
```bash
|
||||
cd examples-standalone/kendoangular-aspnetcore-integration
|
||||
```
|
||||
|
||||
1. Build the project:
|
||||
|
||||
```bash
|
||||
dotnet build
|
||||
```
|
||||
|
||||
1. Run the project:
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
|
||||
1. ASP.NET Core will provide local host addresses where the project is running. Open the provided address in your browser.
|
||||
|
||||
1. Wait for the project to load and you will see the Kendo UI for Angular Grid and Upload component in action. This might take a few seconds as Angular CLI will be building the project.
|
||||
|
||||
## Additional Information
|
||||
|
||||
For more information on how to add Telerik Private NuGet feed to your project, refer to the [Adding the Telerik Private NuGet Feed to VS](https://docs.telerik.com/reporting/getting-started/installation/adding-private-nuget-feed) or [Blazor Private NuGet Source](https://docs.telerik.com/blazor-ui/installation/nuget#use-the-net-cli) articles.
|
||||
This will provide you access to use [ToDataSourceResult](https://docs.telerik.com/aspnet-mvc/api/kendo.mvc.extensions/queryableextensions#todatasourceresultsystemdatadatatablekendomvcuidatasourcerequest) method and other helpers for your own projects.
|
||||
|
||||
Do keep in mind that the ASP.NET Core template uses older version of Angular and ASP.NET Core. You can update both both frameworks to their latest version by following the [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80?view=aspnetcore-8.0&tabs=visual-studio) and [Angular](https://angular.dev/update-guide) update guides. This project was updated to use Angular 18 and ASP.NET Core 8.0.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Kendo UI for Angular Components](https://www.telerik.com/kendo-angular-ui)
|
||||
- [Kendo UI for Angular Documentation](https://www.telerik.com/kendo-angular-ui/components/)
|
||||
- [Kendo UI for Angular Grid Component](https://www.telerik.com/kendo-angular-ui/components/grid/)
|
||||
- [Kendo UI for Angular Upload Component](https://www.telerik.com/kendo-angular-ui/components/uploads/upload/)
|
||||
- [ASP.NET Core Documentation](https://learn.microsoft.com/en-us/aspnet/core/?view=aspnetcore-8.0)
|
Двоичные данные
examples-standalone/kendoangular-aspnetcore-integration/wwwroot/favicon.ico
Normal file
Двоичные данные
examples-standalone/kendoangular-aspnetcore-integration/wwwroot/favicon.ico
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 5.3 KiB |
Загрузка…
Ссылка в новой задаче