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:
Yanmario Menev 2024-07-29 23:36:20 -07:00 коммит произвёл GitHub
Родитель 30a4d913ce
Коммит 934194611c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
48 изменённых файлов: 24669 добавлений и 0 удалений

5
.github/workflows/ci.yml поставляемый
Просмотреть файл

@ -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
});
};

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

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

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

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

После

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